build-text 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +4 -0
- data/.travis.yml +19 -0
- data/Gemfile +13 -0
- data/Guardfile +9 -0
- data/README.md +121 -0
- data/Rakefile +8 -0
- data/build-text.gemspec +21 -0
- data/lib/build/text.rb +24 -0
- data/lib/build/text/indentation.rb +67 -0
- data/lib/build/text/merge.rb +144 -0
- data/lib/build/text/substitutions.rb +200 -0
- data/lib/build/text/version.rb +25 -0
- data/spec/build/text/merge_spec.rb +46 -0
- data/spec/build/text/substitutions_spec.rb +61 -0
- data/spec/spec_helper.rb +29 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8e56e9d24d3f0ec039b6380bb7e921f9ef7713c5
|
4
|
+
data.tar.gz: fd4360cd52c71b0474f94fb4e4c6d1d6517a637c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 887249e37b4e6df5368acbd13c3733997e9095ae3ddb6f8fa58b8604881c2229bce72cae0cd2740f7dcc03d8ef70da42a805fd7f9b3bc6a04687a4c9098f1e59
|
7
|
+
data.tar.gz: ee42f4d82827b140c98e4a47c2efbd123881c195b0bbb0d1fa84536daa95c681bcb8016c6a9babf0db066397d949971e70fed170439f6fb3d1c6ca34ccce9df0
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.rspec_status
|
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
language: ruby
|
2
|
+
sudo: false
|
3
|
+
dist: trusty
|
4
|
+
cache: bundler
|
5
|
+
rvm:
|
6
|
+
- 2.1
|
7
|
+
- 2.2
|
8
|
+
- 2.3
|
9
|
+
- 2.4
|
10
|
+
- ruby-head
|
11
|
+
- jruby-head
|
12
|
+
matrix:
|
13
|
+
allow_failures:
|
14
|
+
- rvm: "ruby-head"
|
15
|
+
- rvm: "jruby-head"
|
16
|
+
addons:
|
17
|
+
apt:
|
18
|
+
packages:
|
19
|
+
- graphviz
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# Build::Text
|
2
|
+
|
3
|
+
`Build::Text` provides substitutions and merging for textual files, typically code.
|
4
|
+
|
5
|
+
[![Build Status](https://secure.travis-ci.org/ioquatix/build-text.svg)](http://travis-ci.org/ioquatix/build-text)
|
6
|
+
[![Code Climate](https://codeclimate.com/github/ioquatix/build-text.svg)](https://codeclimate.com/github/ioquatix/build-text)
|
7
|
+
[![Coverage Status](https://coveralls.io/repos/ioquatix/build-text/badge.svg)](https://coveralls.io/r/ioquatix/build-text)
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'build-text'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install build-text
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Substitutions
|
26
|
+
|
27
|
+
Provides a very simple template language for doing text substitutions:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'build/text'
|
31
|
+
|
32
|
+
substitutions = Build::Text::Substitutions.new
|
33
|
+
substitutions['ANIMAL'] = 'cat'
|
34
|
+
substitutions['SOUND'] = 'meow'
|
35
|
+
|
36
|
+
puts substitutions.apply("The $ANIMAL says $SOUND.")
|
37
|
+
# Prints "The cat says meow."
|
38
|
+
```
|
39
|
+
|
40
|
+
It can also do indented expressions.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
require 'build/text'
|
44
|
+
|
45
|
+
substitutions = Build::Text::Substitutions.new
|
46
|
+
substitutions['MODULE'] = ["module Meow\n", "end\n"]
|
47
|
+
|
48
|
+
puts substitutions.apply("<MODULE>\ndef function\nend\n</MODULE>")
|
49
|
+
# Prints:
|
50
|
+
# module Meow
|
51
|
+
# def function
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
```
|
55
|
+
|
56
|
+
### Merging
|
57
|
+
|
58
|
+
A best effort merging strategy to combine two files. It uses LCS to match up sections, and then appends segments together.
|
59
|
+
|
60
|
+
Given the following input file:
|
61
|
+
|
62
|
+
```c++
|
63
|
+
class Bob {
|
64
|
+
int alice();
|
65
|
+
}
|
66
|
+
```
|
67
|
+
|
68
|
+
and the following template:
|
69
|
+
|
70
|
+
```c++
|
71
|
+
class Bob {
|
72
|
+
int bob();
|
73
|
+
}
|
74
|
+
```
|
75
|
+
|
76
|
+
it can be merged
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
merged = Build::Text::Merge.combine(input.lines, template.lines)
|
80
|
+
```
|
81
|
+
|
82
|
+
and the merged result will be:
|
83
|
+
|
84
|
+
```c++
|
85
|
+
class Bob {
|
86
|
+
int alice();
|
87
|
+
int bob();
|
88
|
+
}
|
89
|
+
```
|
90
|
+
|
91
|
+
## Contributing
|
92
|
+
|
93
|
+
1. Fork it
|
94
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
95
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
96
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
97
|
+
5. Create new Pull Request
|
98
|
+
|
99
|
+
## License
|
100
|
+
|
101
|
+
Released under the MIT license.
|
102
|
+
|
103
|
+
Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
|
104
|
+
|
105
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
106
|
+
of this software and associated documentation files (the "Software"), to deal
|
107
|
+
in the Software without restriction, including without limitation the rights
|
108
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
109
|
+
copies of the Software, and to permit persons to whom the Software is
|
110
|
+
furnished to do so, subject to the following conditions:
|
111
|
+
|
112
|
+
The above copyright notice and this permission notice shall be included in
|
113
|
+
all copies or substantial portions of the Software.
|
114
|
+
|
115
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
116
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
117
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
118
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
119
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
120
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
121
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
data/build-text.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require_relative 'lib/build/text/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "build-text"
|
6
|
+
spec.version = Build::Text::VERSION
|
7
|
+
spec.authors = ["Samuel Williams"]
|
8
|
+
spec.email = ["samuel.williams@oriontransfer.co.nz"]
|
9
|
+
spec.summary = %q{Text substitutions and merging.}
|
10
|
+
spec.homepage = ""
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
spec.files = `git ls-files -z`.split("\x0")
|
14
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
15
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
|
+
spec.require_paths = ["lib"]
|
17
|
+
|
18
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
19
|
+
spec.add_development_dependency "rspec", "~> 3.6"
|
20
|
+
spec.add_development_dependency "rake"
|
21
|
+
end
|
data/lib/build/text.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'text/version'
|
22
|
+
|
23
|
+
require_relative 'text/merge'
|
24
|
+
require_relative 'text/substitutions'
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Copyright, 2013, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Build
|
22
|
+
module Text
|
23
|
+
class Indentation
|
24
|
+
TAB = "\t".freeze
|
25
|
+
|
26
|
+
def initialize(prefix, level, indent)
|
27
|
+
@prefix = prefix
|
28
|
+
@level = level
|
29
|
+
@indent = indent
|
30
|
+
end
|
31
|
+
|
32
|
+
def freeze
|
33
|
+
indentation
|
34
|
+
|
35
|
+
@prefix.freeze
|
36
|
+
@level.freeze
|
37
|
+
@indent.freeze
|
38
|
+
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def indentation
|
43
|
+
@indentation ||= @prefix + (@indent * @level)
|
44
|
+
end
|
45
|
+
|
46
|
+
def + other
|
47
|
+
indentation + other
|
48
|
+
end
|
49
|
+
|
50
|
+
def << text
|
51
|
+
text.gsub(/^/){|m| m + indentation}
|
52
|
+
end
|
53
|
+
|
54
|
+
def by(depth)
|
55
|
+
Indentation.new(@prefix, @level + depth, @indent)
|
56
|
+
end
|
57
|
+
|
58
|
+
def with_prefix(prefix)
|
59
|
+
Indentation.new(prefix, @level, @indent)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.none
|
63
|
+
self.new('', 0, TAB)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright, 2013, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
|
22
|
+
module Build
|
23
|
+
module Text
|
24
|
+
module Merge
|
25
|
+
Difference = Struct.new(:type, :value)
|
26
|
+
|
27
|
+
def self.combine(old_text, new_text)
|
28
|
+
lcs = lcs(old_text, new_text)
|
29
|
+
changes = []
|
30
|
+
|
31
|
+
n = 0; o = 0; l = 0
|
32
|
+
while o < old_text.size and n < new_text.size and l < lcs.size
|
33
|
+
if !similar(old_text[o], lcs[l])
|
34
|
+
changes << Difference.new(:old, old_text[o])
|
35
|
+
o+=1
|
36
|
+
elsif !similar(new_text[n], lcs[l])
|
37
|
+
changes << Difference.new(:new, new_text[n])
|
38
|
+
n+=1
|
39
|
+
else
|
40
|
+
changes << Difference.new(:both, lcs[l])
|
41
|
+
o+=1; n+=1; l+=1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
changes.map do |change|
|
46
|
+
change.value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# This code is based directly on the Text gem implementation
|
51
|
+
# Returns a value representing the "cost" of transforming str1 into str2
|
52
|
+
def self.levenshtein_distance(s, t)
|
53
|
+
n = s.length
|
54
|
+
m = t.length
|
55
|
+
|
56
|
+
return m if n == 0
|
57
|
+
return n if m == 0
|
58
|
+
|
59
|
+
d = (0..m).to_a
|
60
|
+
x = nil
|
61
|
+
|
62
|
+
n.times do |i|
|
63
|
+
e = i+1
|
64
|
+
|
65
|
+
m.times do |j|
|
66
|
+
cost = (s[i] == t[j]) ? 0 : 1
|
67
|
+
x = [
|
68
|
+
d[j+1] + 1, # insertion
|
69
|
+
e + 1, # deletion
|
70
|
+
d[j] + cost # substitution
|
71
|
+
].min
|
72
|
+
d[j] = e
|
73
|
+
e = x
|
74
|
+
end
|
75
|
+
|
76
|
+
d[m] = x
|
77
|
+
end
|
78
|
+
|
79
|
+
return x
|
80
|
+
end
|
81
|
+
|
82
|
+
# Calculate the similarity of two sequences, return true if they are with factor% similarity.
|
83
|
+
def self.similar(s, t, factor = 0.15)
|
84
|
+
return true if s == t
|
85
|
+
|
86
|
+
distance = levenshtein_distance(s, t)
|
87
|
+
average_length = (s.length + t.length) / 2.0
|
88
|
+
|
89
|
+
proximity = (distance.to_f / average_length)
|
90
|
+
|
91
|
+
return proximity <= factor
|
92
|
+
end
|
93
|
+
|
94
|
+
LCSNode = Struct.new(:value, :previous)
|
95
|
+
|
96
|
+
# Find the Longest Common Subsequence in the given sequences x, y.
|
97
|
+
def self.lcs(x, y)
|
98
|
+
# Create the lcs matrix:
|
99
|
+
m = Array.new(x.length + 1) do
|
100
|
+
Array.new(y.length + 1) do
|
101
|
+
LCSNode.new(nil, nil)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# LCS(i, 0) and LCS(0, j) are always 0:
|
106
|
+
for i in 0..x.length do m[i][0].value = 0 end
|
107
|
+
for j in 0..y.length do m[0][j].value = 0 end
|
108
|
+
|
109
|
+
# Main algorithm, solve row by row:
|
110
|
+
for i in 1..x.length do
|
111
|
+
for j in 1..y.length do
|
112
|
+
if similar(x[i-1], y[j-1])
|
113
|
+
# Value is based on maximizing the length of the matched strings:
|
114
|
+
m[i][j].value = m[i-1][j-1].value + (x[i-1].chomp.length + y[j-1].chomp.length) / 2.0
|
115
|
+
m[i][j].previous = [-1, -1]
|
116
|
+
else
|
117
|
+
if m[i-1][j].value >= m[i][j-1].value
|
118
|
+
m[i][j].value = m[i-1][j].value
|
119
|
+
m[i][j].previous = [-1, 0]
|
120
|
+
else
|
121
|
+
m[i][j].value = m[i][j-1].value
|
122
|
+
m[i][j].previous = [0, -1]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Get the solution by following the path backwards from m[x.length][y.length]
|
129
|
+
lcs = []
|
130
|
+
|
131
|
+
i = x.length; j = y.length
|
132
|
+
until m[i][j].previous == nil do
|
133
|
+
if m[i][j].previous == [-1, -1]
|
134
|
+
lcs << x[i-1]
|
135
|
+
end
|
136
|
+
|
137
|
+
i, j = i + m[i][j].previous[0], j + m[i][j].previous[1]
|
138
|
+
end
|
139
|
+
|
140
|
+
return lcs.reverse!
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# Copyright, 2013, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'indentation'
|
22
|
+
|
23
|
+
module Build
|
24
|
+
module Text
|
25
|
+
class Substitutions
|
26
|
+
def initialize(ordered = [])
|
27
|
+
@ordered = ordered
|
28
|
+
end
|
29
|
+
|
30
|
+
def freeze
|
31
|
+
@ordered.freeze
|
32
|
+
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def []= keyword, value
|
37
|
+
if Array === value
|
38
|
+
open, close = *value.each_slice(value.length / 2)
|
39
|
+
@ordered << NestedSubstitution.new(keyword, open, close)
|
40
|
+
else
|
41
|
+
@ordered << SymbolicSubstitution.new('$' + keyword, value.to_s)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def << substitution
|
46
|
+
@ordered << substition
|
47
|
+
end
|
48
|
+
|
49
|
+
def + other
|
50
|
+
Substitutions.new(@ordered + other.ordered)
|
51
|
+
end
|
52
|
+
|
53
|
+
attr :ordered
|
54
|
+
|
55
|
+
def call(text)
|
56
|
+
apply(text)
|
57
|
+
end
|
58
|
+
|
59
|
+
def apply(text)
|
60
|
+
return text unless @ordered.count > 0
|
61
|
+
|
62
|
+
grouped = [[@ordered.first]]
|
63
|
+
|
64
|
+
@ordered.drop(1).each do |substitution|
|
65
|
+
if grouped.last[0].class == substitution.class
|
66
|
+
grouped.last << substitution
|
67
|
+
else
|
68
|
+
grouped << [substitution]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
grouped.each do |group|
|
73
|
+
text = group.first.class.apply(text, group)
|
74
|
+
end
|
75
|
+
|
76
|
+
return text
|
77
|
+
end
|
78
|
+
|
79
|
+
class SymbolicSubstitution
|
80
|
+
def initialize(keyword, value)
|
81
|
+
@keyword = keyword
|
82
|
+
@value = value
|
83
|
+
end
|
84
|
+
|
85
|
+
attr :keyword
|
86
|
+
attr :value
|
87
|
+
|
88
|
+
def freeze
|
89
|
+
@keyword.freeze
|
90
|
+
@value.freeze
|
91
|
+
|
92
|
+
super
|
93
|
+
end
|
94
|
+
|
95
|
+
def apply(text)
|
96
|
+
text.gsub(@keyword, @value)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.apply(text, group)
|
100
|
+
substitutions = Hash[group.collect{|substitution| [substitution.keyword, substitution.value]}]
|
101
|
+
|
102
|
+
pattern = Regexp.new(substitutions.keys.map{|key| Regexp.escape(key)}.join('|'))
|
103
|
+
|
104
|
+
text.gsub(pattern) {|key| substitutions[key]}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class NestedSubstitution
|
109
|
+
def initialize(keyword, open, close, indent = "\t")
|
110
|
+
@keyword = keyword
|
111
|
+
|
112
|
+
@open = open
|
113
|
+
@close = close
|
114
|
+
|
115
|
+
@indent = indent
|
116
|
+
end
|
117
|
+
|
118
|
+
def freeze
|
119
|
+
@keyword.freeze
|
120
|
+
@open.freeze
|
121
|
+
@close.freeze
|
122
|
+
@indent.freeze
|
123
|
+
|
124
|
+
super
|
125
|
+
end
|
126
|
+
|
127
|
+
def line_pattern(prefix = '')
|
128
|
+
tag_pattern = Regexp.escape('<' + prefix + @keyword + '>')
|
129
|
+
|
130
|
+
# Line matching pattern:
|
131
|
+
Regexp.new('^(.*?)' + tag_pattern + '(.*)$', Regexp::MULTILINE | Regexp::EXTENDED)
|
132
|
+
end
|
133
|
+
|
134
|
+
def write_open(prefix, postfix, output, indentation)
|
135
|
+
depth = @open.size
|
136
|
+
indentation = indentation.with_prefix(prefix)
|
137
|
+
|
138
|
+
#output.write(prefix)
|
139
|
+
(0...depth).each do |i|
|
140
|
+
chunk = @open[i]
|
141
|
+
chunk.chomp! if i == depth-1
|
142
|
+
|
143
|
+
output.write(indentation.by(i) << chunk)
|
144
|
+
end
|
145
|
+
output.write(postfix)
|
146
|
+
end
|
147
|
+
|
148
|
+
def write_close(prefix, postfix, output, indentation)
|
149
|
+
depth = @close.size
|
150
|
+
indentation = indentation.with_prefix(prefix)
|
151
|
+
|
152
|
+
#output.write(prefix)
|
153
|
+
(0...depth).reverse_each do |i|
|
154
|
+
chunk = @close[-1 - i]
|
155
|
+
chunk.chomp! if i == 0
|
156
|
+
|
157
|
+
output.write(indentation.by(i) << chunk)
|
158
|
+
end
|
159
|
+
output.write(postfix)
|
160
|
+
end
|
161
|
+
|
162
|
+
def apply(text, level = 0)
|
163
|
+
open_pattern = line_pattern
|
164
|
+
close_pattern = line_pattern('/')
|
165
|
+
|
166
|
+
lines = text.each_line
|
167
|
+
output = StringIO.new
|
168
|
+
|
169
|
+
indent = lambda do |level, indentation|
|
170
|
+
while line = lines.next rescue nil
|
171
|
+
if line =~ open_pattern
|
172
|
+
write_open($1, $2, output, indentation)
|
173
|
+
|
174
|
+
indent[level + @open.count, indentation.by(@open.count)]
|
175
|
+
|
176
|
+
write_close($1, $2, output, indentation)
|
177
|
+
elsif line =~ close_pattern
|
178
|
+
break
|
179
|
+
else
|
180
|
+
output.write(indentation + line)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
indent[0, Indentation.none]
|
186
|
+
|
187
|
+
return output.string
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.apply(text, group)
|
191
|
+
group.each do |substitution|
|
192
|
+
text = substitution.apply(text)
|
193
|
+
end
|
194
|
+
|
195
|
+
return text
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Build
|
22
|
+
module Text
|
23
|
+
VERSION = "1.0.0"
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
RSpec.describe Build::Text::Merge do
|
22
|
+
TARGET_FILE = <<-EOF
|
23
|
+
class Bob {
|
24
|
+
int alice();
|
25
|
+
}
|
26
|
+
EOF
|
27
|
+
|
28
|
+
TEMPLATE = <<-EOF
|
29
|
+
class Bob {
|
30
|
+
int bob();
|
31
|
+
}
|
32
|
+
EOF
|
33
|
+
|
34
|
+
MERGED = <<-EOF
|
35
|
+
class Bob {
|
36
|
+
int alice();
|
37
|
+
int bob();
|
38
|
+
}
|
39
|
+
EOF
|
40
|
+
|
41
|
+
it "can merge two files" do
|
42
|
+
merged = Build::Text::Merge.combine(TARGET_FILE.lines, TEMPLATE.lines)
|
43
|
+
|
44
|
+
expect(merged).to be == MERGED.lines
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
RSpec.describe Build::Text::Substitutions do
|
22
|
+
it "should substitute symbolic expressions" do
|
23
|
+
input = <<-EOF
|
24
|
+
$FOO $BAR
|
25
|
+
EOF
|
26
|
+
|
27
|
+
output = <<-EOF
|
28
|
+
baz bar
|
29
|
+
EOF
|
30
|
+
|
31
|
+
substitutions = Build::Text::Substitutions.new
|
32
|
+
substitutions['FOO'] = 'baz'
|
33
|
+
substitutions['BAR'] = 'bar'
|
34
|
+
|
35
|
+
expect(substitutions.apply(input)).to be == output
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should indent nestest expressions" do
|
39
|
+
input = <<-EOF
|
40
|
+
<FOO>
|
41
|
+
$BAR
|
42
|
+
</FOO>
|
43
|
+
EOF
|
44
|
+
|
45
|
+
output = <<-EOF
|
46
|
+
enter
|
47
|
+
{
|
48
|
+
foo
|
49
|
+
Hello World
|
50
|
+
bar
|
51
|
+
}
|
52
|
+
exit
|
53
|
+
EOF
|
54
|
+
|
55
|
+
substitutions = Build::Text::Substitutions.new
|
56
|
+
substitutions['FOO'] = ["enter\n{\n", "foo\n", "bar\n", "}\nexit\n"]
|
57
|
+
substitutions['BAR'] = 'Hello World'
|
58
|
+
|
59
|
+
expect(substitutions.apply(input)).to be == output
|
60
|
+
end
|
61
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
if ENV['COVERAGE'] || ENV['TRAVIS']
|
3
|
+
begin
|
4
|
+
require 'simplecov'
|
5
|
+
|
6
|
+
SimpleCov.start do
|
7
|
+
add_filter "/spec/"
|
8
|
+
end
|
9
|
+
|
10
|
+
if ENV['TRAVIS']
|
11
|
+
require 'coveralls'
|
12
|
+
Coveralls.wear!
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
warn "Could not load simplecov: #{$!}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require "bundler/setup"
|
20
|
+
require "build/text"
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
# Enable flags like --only-failures and --next-failure
|
24
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
25
|
+
|
26
|
+
config.expect_with :rspec do |c|
|
27
|
+
c.syntax = :expect
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: build-text
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Samuel Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- samuel.williams@oriontransfer.co.nz
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- Guardfile
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- build-text.gemspec
|
70
|
+
- lib/build/text.rb
|
71
|
+
- lib/build/text/indentation.rb
|
72
|
+
- lib/build/text/merge.rb
|
73
|
+
- lib/build/text/substitutions.rb
|
74
|
+
- lib/build/text/version.rb
|
75
|
+
- spec/build/text/merge_spec.rb
|
76
|
+
- spec/build/text/substitutions_spec.rb
|
77
|
+
- spec/spec_helper.rb
|
78
|
+
homepage: ''
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata: {}
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 2.6.12
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Text substitutions and merging.
|
102
|
+
test_files:
|
103
|
+
- spec/build/text/merge_spec.rb
|
104
|
+
- spec/build/text/substitutions_spec.rb
|
105
|
+
- spec/spec_helper.rb
|