ad_hoc_template 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +6 -0
- data/ad_hoc_template.gemspec +25 -0
- data/bin/ad_hoc_template +6 -0
- data/lib/ad_hoc_template/command_line_interface.rb +70 -0
- data/lib/ad_hoc_template/version.rb +3 -0
- data/lib/ad_hoc_template.rb +239 -0
- data/spec/ad_hoc_template_spec.rb +249 -0
- data/spec/command_line_interface_spec.rb +130 -0
- data/spec/spec_helper.rb +13 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ac72ef7ef92a4a6aa9be31762b2cc29177cecd44
|
4
|
+
data.tar.gz: 6fd32a26232fbb869f73632610c301e470e3178b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e6935f638dd7c1cb8979c86bbfbd653ec75a7a4aca0d7579391026b47e60840b55a4444696cef0f2203bf6a5460b95822324b7322754cd9a215fb92e357a76a7
|
7
|
+
data.tar.gz: 42d8b17110f59b24fe529029da629fa4150ce4c531b15721d47e7783da12edee4acf56086a6f3ec86b03d84197bdcf71967a9c989d4a0a784972682e6c27fe13
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in ad_hoc_template.gemspec
|
4
|
+
|
5
|
+
gem 'pseudohikiparser', :git => 'https://github.com/nico-hn/PseudoHikiParser.git', :tag => '0.0.2'
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem "bundler", "~> 1.3"
|
9
|
+
gem "rake"
|
10
|
+
gem "rspec"
|
11
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 HASHIMOTO, Naoki
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# AdHocTemplate
|
2
|
+
|
3
|
+
AdHocTemplate is a template processor with simple but sufficent rules for some ad hoc tasks.
|
4
|
+
|
5
|
+
I conceived this template as a workaroud for some tasks in a working environment completely left behind the times (maybe 10-15 years or so?), where they don't seem to know what a database is.
|
6
|
+
|
7
|
+
And I hope this tool saves you from meaningless tasks when you have to face such a situation.
|
8
|
+
|
9
|
+
## Installation (not published to RubyGems.org yet)
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'ad_hoc_template'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install ad_hoc_template
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
The following is an example of template format:
|
26
|
+
|
27
|
+
```
|
28
|
+
a test string with tags (<%= key1 %> and <%= key2 %>) in it
|
29
|
+
|
30
|
+
<%#iteration_block
|
31
|
+
the value of sub_key1 is <%= sub_key1 %>
|
32
|
+
the value of sub_key2 is <%= sub_key2 %>
|
33
|
+
|
34
|
+
#%>
|
35
|
+
<%= block %>
|
36
|
+
```
|
37
|
+
|
38
|
+
And suppose you want to fill the template with sample data below:
|
39
|
+
|
40
|
+
```
|
41
|
+
key1: value1
|
42
|
+
key2: value2
|
43
|
+
key3: value3
|
44
|
+
|
45
|
+
//@#iteration_block
|
46
|
+
|
47
|
+
sub_key1: value1-1
|
48
|
+
sub_key2: value1-2
|
49
|
+
|
50
|
+
sub_key1: value2-1
|
51
|
+
sub_key2: value2-2
|
52
|
+
|
53
|
+
//@block
|
54
|
+
|
55
|
+
the first line of block
|
56
|
+
the second line of block
|
57
|
+
|
58
|
+
the second paragraph in block
|
59
|
+
|
60
|
+
```
|
61
|
+
|
62
|
+
1. Save the template and sample data above as 'template.txt' and 'sample\_data.txt' respectively.
|
63
|
+
2. Execute the following at the command line:
|
64
|
+
|
65
|
+
```
|
66
|
+
ad_hoc_template template.txt sample_data.txt
|
67
|
+
```
|
68
|
+
|
69
|
+
Then you will get the following result:
|
70
|
+
|
71
|
+
```
|
72
|
+
a test string with tags (value1 and value2) in it
|
73
|
+
|
74
|
+
the value of sub_key1 is value1-1
|
75
|
+
the value of sub_key2 is value1-2
|
76
|
+
|
77
|
+
the value of sub_key1 is value2-1
|
78
|
+
the value of sub_key2 is value2-2
|
79
|
+
|
80
|
+
|
81
|
+
the first line of block
|
82
|
+
the second line of block
|
83
|
+
|
84
|
+
the second paragraph in block
|
85
|
+
|
86
|
+
```
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
1. Fork it
|
91
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
92
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
93
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
94
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ad_hoc_template/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ad_hoc_template"
|
8
|
+
spec.version = AdHocTemplate::VERSION
|
9
|
+
spec.authors = ["HASHIMOTO, Naoki"]
|
10
|
+
spec.email = ["hashimoto.naoki@gmail.com"]
|
11
|
+
spec.description = %q{AdHocTemplate is a template processor with simple but sufficient rules for some ad hoc tasks.}
|
12
|
+
spec.summary = %q{A tiny template processor}
|
13
|
+
spec.homepage = "https://github.com/nico-hn/AdHocTemplate"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
spec.add_runtime_dependency "pseudohikiparser", "0.0.2"
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.1"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
25
|
+
end
|
data/bin/ad_hoc_template
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ad_hoc_template'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
module AdHocTemplate
|
7
|
+
class CommandLineInterface
|
8
|
+
attr_accessor :output_filename, :template_data, :record_data
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@formatter = AdHocTemplate::DefaultTagFormatter.new
|
12
|
+
@output_filename = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_encoding(given_opt)
|
16
|
+
external, internal = given_opt.split(/:/o, 2)
|
17
|
+
Encoding.default_external = external if external and not external.empty?
|
18
|
+
Encoding.default_internal = internal if internal and not internal.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_command_line_options
|
22
|
+
OptionParser.new do |opt|
|
23
|
+
opt.on("-E [ex[:in]]", "--encoding [=ex[:in]]",
|
24
|
+
"Specify the default external and internal character encodings (same as the option of MRI") do |given_opt|
|
25
|
+
self.set_encoding(given_opt)
|
26
|
+
end
|
27
|
+
|
28
|
+
opt.on("-o [output_file]", "--output [=output_file]",
|
29
|
+
"Save the result into the specified file.") do |output_file|
|
30
|
+
@output_filename = File.expand_path(output_file)
|
31
|
+
end
|
32
|
+
|
33
|
+
opt.parse!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def read_input_files
|
38
|
+
template, record = ARGV.map {|arg| File.expand_path(arg) if arg }
|
39
|
+
if template
|
40
|
+
@template_data = File.read(template)
|
41
|
+
else
|
42
|
+
STDERR.puts "No template file is given."
|
43
|
+
end
|
44
|
+
|
45
|
+
@record_data = record ? File.read(record) : ARGF.read
|
46
|
+
end
|
47
|
+
|
48
|
+
def convert
|
49
|
+
AdHocTemplate::Converter.convert(@record_data, @template_data, @formatter)
|
50
|
+
end
|
51
|
+
|
52
|
+
def open_output
|
53
|
+
if @output_filename
|
54
|
+
open(@output_filename, "wb") do |out|
|
55
|
+
yield out
|
56
|
+
end
|
57
|
+
else
|
58
|
+
yield STDOUT
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def execute
|
63
|
+
parse_command_line_options
|
64
|
+
read_input_files
|
65
|
+
open_output do |out|
|
66
|
+
out.print convert
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require "ad_hoc_template/version"
|
2
|
+
require "pseudohiki/inlineparser"
|
3
|
+
require "htmlelement"
|
4
|
+
|
5
|
+
module AdHocTemplate
|
6
|
+
class Parser < TreeStack
|
7
|
+
class TagNode < Parser::Node
|
8
|
+
attr_reader :type
|
9
|
+
|
10
|
+
def push(node=TreeStack::Node.new)
|
11
|
+
node[0] = assign_type(node[0]) if self.empty?
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def assign_type(first_leaf)
|
16
|
+
return first_leaf unless first_leaf.kind_of? String and /^\S/o.match(first_leaf)
|
17
|
+
@type, first_leaf_content = first_leaf.split(/\s+/o, 2)
|
18
|
+
first_leaf_content||""
|
19
|
+
end
|
20
|
+
private :assign_type
|
21
|
+
|
22
|
+
def contains_any_value_assigned_tag_node?(record)
|
23
|
+
self.select {|n| n.kind_of?(TagNode) }.each do |node|
|
24
|
+
val = record[node.join.strip]
|
25
|
+
return true if val and not val.empty?
|
26
|
+
end
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class IterationTagNode < TagNode; end
|
32
|
+
class Leaf < Parser::Leaf; end
|
33
|
+
|
34
|
+
HEAD, TAIL = {}, {}
|
35
|
+
|
36
|
+
[[TagNode, "<%", "%>"],
|
37
|
+
[IterationTagNode, "<%#", "#%>"]].each do |type, head, tail|
|
38
|
+
HEAD[head] = type
|
39
|
+
TAIL[tail] = type
|
40
|
+
end
|
41
|
+
|
42
|
+
TOKEN_PAT = PseudoHiki.compile_token_pat(HEAD.keys, TAIL.keys)
|
43
|
+
|
44
|
+
def self.split_into_tokens(str)
|
45
|
+
tokens = []
|
46
|
+
|
47
|
+
while m = TOKEN_PAT.match(str)
|
48
|
+
tokens.push m.pre_match unless m.pre_match.empty?
|
49
|
+
tokens.push m[0]
|
50
|
+
str = m.post_match
|
51
|
+
end
|
52
|
+
|
53
|
+
tokens.push str unless str.empty?
|
54
|
+
tokens
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.parse(str)
|
58
|
+
new(str).parse.tree
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(str)
|
62
|
+
@tokens = Parser.split_into_tokens(str)
|
63
|
+
super()
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse
|
67
|
+
while token = @tokens.shift
|
68
|
+
next if TAIL[token] == current_node.class and self.pop
|
69
|
+
next if HEAD[token] and self.push HEAD[token].new
|
70
|
+
self.push Leaf.create(token)
|
71
|
+
end
|
72
|
+
|
73
|
+
self
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
module RecordReader
|
78
|
+
SEPARATOR = /:\s*/o
|
79
|
+
BLOCK_HEAD = /\A\/\/@/o
|
80
|
+
EMPTY_LINE = /\A\r?\n\Z/o
|
81
|
+
ITERATION_MARK = /\A#/o
|
82
|
+
|
83
|
+
def self.remove_leading_empty_lines(lines)
|
84
|
+
until lines.empty? or /\S/o.match(lines.first)
|
85
|
+
lines.shift
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.strip_blank_lines(block)
|
90
|
+
remove_leading_empty_lines(block)
|
91
|
+
block.pop while not block.empty? and EMPTY_LINE.match(block.last)
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.read_key_value_list(lines, record)
|
95
|
+
while line = lines.shift and not EMPTY_LINE.match(line)
|
96
|
+
key, val = line.chomp.split(SEPARATOR, 2)
|
97
|
+
record[key] = val
|
98
|
+
end
|
99
|
+
|
100
|
+
record
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.read_block(lines, record, block_head)
|
104
|
+
block = []
|
105
|
+
|
106
|
+
while line = lines.shift
|
107
|
+
if m = BLOCK_HEAD.match(line)
|
108
|
+
strip_blank_lines(block)
|
109
|
+
record[block_head] = block.join
|
110
|
+
return m.post_match.chomp
|
111
|
+
end
|
112
|
+
|
113
|
+
block.push(line)
|
114
|
+
end
|
115
|
+
|
116
|
+
strip_blank_lines(block)
|
117
|
+
record[block_head] = block.join
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.read_block_part(lines, record, block_head)
|
121
|
+
until lines.empty? or not block_head
|
122
|
+
block_head = read_block(lines, record, block_head)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.read_iteration_block(lines, record, block_head)
|
127
|
+
records = []
|
128
|
+
|
129
|
+
while line = lines.shift
|
130
|
+
if m = BLOCK_HEAD.match(line)
|
131
|
+
record[block_head] = records
|
132
|
+
return m.post_match.chomp
|
133
|
+
elsif EMPTY_LINE.match(line)
|
134
|
+
next
|
135
|
+
else
|
136
|
+
lines.unshift line
|
137
|
+
records.push read_key_value_list(lines, {})
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
record[block_head] = records
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.read_iteration_block_part(lines, record, block_head)
|
146
|
+
while not lines.empty? and block_head and ITERATION_MARK.match(block_head)
|
147
|
+
block_head = read_iteration_block(lines, record, block_head)
|
148
|
+
end
|
149
|
+
|
150
|
+
block_head
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.read_record(input)
|
154
|
+
lines = input.each_line.to_a
|
155
|
+
record = read_key_value_list(lines, {})
|
156
|
+
remove_leading_empty_lines(lines)
|
157
|
+
|
158
|
+
unless lines.empty?
|
159
|
+
m = BLOCK_HEAD.match(lines.shift)
|
160
|
+
block_head = read_iteration_block_part(lines, record, m.post_match.chomp)
|
161
|
+
read_block_part(lines, record, block_head) if block_head
|
162
|
+
end
|
163
|
+
|
164
|
+
record
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class DefaultTagFormatter
|
169
|
+
def find_function(tag_type)
|
170
|
+
FUNCTION_TABLE[tag_type]||:default
|
171
|
+
end
|
172
|
+
|
173
|
+
def format(tag_type, var, record)
|
174
|
+
self.send(find_function(tag_type), var, record)
|
175
|
+
end
|
176
|
+
|
177
|
+
def default(var, record)
|
178
|
+
record[var]||"[#{var}]"
|
179
|
+
end
|
180
|
+
|
181
|
+
def html_encode(var ,record)
|
182
|
+
HtmlElement.escape(record[var]||var)
|
183
|
+
end
|
184
|
+
|
185
|
+
FUNCTION_TABLE = {
|
186
|
+
"=" => :default,
|
187
|
+
"h" => :html_encode
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
class Converter
|
192
|
+
def self.convert(record_data, template, formatter=DefaultTagFormatter.new)
|
193
|
+
tree = AdHocTemplate::Parser.parse(template)
|
194
|
+
record = AdHocTemplate::RecordReader.read_record(record_data)
|
195
|
+
AdHocTemplate::Converter.new(record, formatter).format(tree)
|
196
|
+
end
|
197
|
+
|
198
|
+
def initialize(record, formatter=DefaultTagFormatter.new)
|
199
|
+
@record = record
|
200
|
+
@formatter = formatter
|
201
|
+
end
|
202
|
+
|
203
|
+
def visit(tree)
|
204
|
+
case tree
|
205
|
+
when Parser::IterationTagNode
|
206
|
+
format_iteration_tag(tree)
|
207
|
+
when Parser::TagNode
|
208
|
+
format_tag(tree)
|
209
|
+
when Parser::Leaf
|
210
|
+
tree.join
|
211
|
+
else
|
212
|
+
tree.map {|node| node.accept(self) }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def format_iteration_tag(tag_node)
|
217
|
+
sub_records = @record["#"+(tag_node.type||"".freeze)]||[@record]
|
218
|
+
tag_node = Parser::TagNode.new.concat(tag_node.clone)
|
219
|
+
|
220
|
+
sub_records.map do |record|
|
221
|
+
if tag_node.contains_any_value_assigned_tag_node?(record)
|
222
|
+
converter = AdHocTemplate::Converter.new(record, @formatter)
|
223
|
+
tag_node.map {|leaf| leaf.accept(converter) }.join
|
224
|
+
else
|
225
|
+
"".freeze
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def format_tag(tag_node)
|
231
|
+
leafs = tag_node.map {|leaf| leaf.accept(self) }
|
232
|
+
@formatter.format(tag_node.type, leafs.join.strip, @record)
|
233
|
+
end
|
234
|
+
|
235
|
+
def format(tree)
|
236
|
+
tree.accept(self).join
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ad_hoc_template'
|
3
|
+
|
4
|
+
describe AdHocTemplate do
|
5
|
+
it 'should have a version number' do
|
6
|
+
expect(AdHocTemplate::VERSION).to_not be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
describe AdHocTemplate::Parser do
|
10
|
+
it "returns a tree of TagNode and Leaf" do
|
11
|
+
expect(AdHocTemplate::Parser.parse("a test string with tags (<% the first tag %> and <% the second tag %>) in it")).to eq([["a test string with tags ("],
|
12
|
+
[[" the first tag "]],
|
13
|
+
[" and "],
|
14
|
+
[[" the second tag "]],
|
15
|
+
[") in it"]])
|
16
|
+
end
|
17
|
+
|
18
|
+
it "allows to have a nested tag" do
|
19
|
+
expect(AdHocTemplate::Parser.parse("a test string with a nested tag; <% an outer tag and <% an inner tag %> %>")).to eq([["a test string with a nested tag; "],
|
20
|
+
[[" an outer tag and "],
|
21
|
+
[[" an inner tag "]],
|
22
|
+
[" "]]])
|
23
|
+
end
|
24
|
+
|
25
|
+
it "may have iteration tags." do
|
26
|
+
tree = AdHocTemplate::Parser.parse("a test string with a nested tag: <%# an iteration tag and <% an inner tag %> #%> and <% another tag %>")
|
27
|
+
expect(tree).to eq([["a test string with a nested tag: "],
|
28
|
+
[[" an iteration tag and "],
|
29
|
+
[[" an inner tag "]],
|
30
|
+
[" "]],
|
31
|
+
[" and "],
|
32
|
+
[[" another tag "]]])
|
33
|
+
expect(tree[1]).to be_a_kind_of(AdHocTemplate::Parser::IterationTagNode)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe AdHocTemplate::RecordReader do
|
38
|
+
it "can read several header type configurations at once." do
|
39
|
+
data = <<CONFIGS
|
40
|
+
|
41
|
+
key1-1: value1-1
|
42
|
+
key1-2: value1-2
|
43
|
+
|
44
|
+
key2-1: value2-1
|
45
|
+
key2-2: value2-2
|
46
|
+
|
47
|
+
key3-1: value3-1
|
48
|
+
key3-2: value3-2
|
49
|
+
|
50
|
+
CONFIGS
|
51
|
+
|
52
|
+
config = {}
|
53
|
+
AdHocTemplate::RecordReader.read_iteration_block(data.each_line.to_a, config, "#configs")
|
54
|
+
expect(config).to eq({"#configs"=>[{"key1-1"=>"value1-1", "key1-2"=>"value1-2"},
|
55
|
+
{"key2-1"=>"value2-1", "key2-2"=>"value2-2"},
|
56
|
+
{"key3-1"=>"value3-1", "key3-2"=>"value3-2"}]})
|
57
|
+
end
|
58
|
+
|
59
|
+
it "reads configuration data and turns them into a hash object" do
|
60
|
+
data = <<CONFIG
|
61
|
+
key1: value1
|
62
|
+
key2: value2
|
63
|
+
key3: value3
|
64
|
+
|
65
|
+
//@block1
|
66
|
+
|
67
|
+
the first line of block1
|
68
|
+
the second line of block1
|
69
|
+
|
70
|
+
the second paragraph in block1
|
71
|
+
|
72
|
+
//@block2
|
73
|
+
the first line of block2
|
74
|
+
the second line of block2
|
75
|
+
|
76
|
+
the second paragraph of block2
|
77
|
+
CONFIG
|
78
|
+
|
79
|
+
expected_config = {
|
80
|
+
"key1" => "value1",
|
81
|
+
"key2" => "value2",
|
82
|
+
"key3" => "value3",
|
83
|
+
"block1" => "the first line of block1\nthe second line of block1\n\nthe second paragraph in block1\n",
|
84
|
+
"block2" => "the first line of block2\nthe second line of block2\n\nthe second paragraph of block2\n"
|
85
|
+
}
|
86
|
+
expect(AdHocTemplate::RecordReader.read_record(data)).to eq(expected_config)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "can read configuration data with 3 different kind of sections" do
|
90
|
+
data = <<CONFIG
|
91
|
+
key1: value1
|
92
|
+
key2: value2
|
93
|
+
key3: value3
|
94
|
+
|
95
|
+
//@#subconfigs
|
96
|
+
|
97
|
+
key1-1: value1-1
|
98
|
+
key1-2: value1-2
|
99
|
+
|
100
|
+
key2-1: value2-1
|
101
|
+
key2-2: value2-2
|
102
|
+
|
103
|
+
//@block
|
104
|
+
|
105
|
+
the first line of block
|
106
|
+
the second line of block
|
107
|
+
|
108
|
+
the second paragraph in block
|
109
|
+
|
110
|
+
CONFIG
|
111
|
+
|
112
|
+
expected_config = {
|
113
|
+
"key1" => "value1",
|
114
|
+
"key2" => "value2",
|
115
|
+
"key3" => "value3",
|
116
|
+
"#subconfigs" => [{"key1-1"=>"value1-1", "key1-2"=>"value1-2"}, {"key2-1"=>"value2-1", "key2-2"=>"value2-2"}],
|
117
|
+
"block" => "the first line of block\nthe second line of block\n\nthe second paragraph in block\n"
|
118
|
+
}
|
119
|
+
expect(AdHocTemplate::RecordReader.read_record(data)).to eq(expected_config)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe AdHocTemplate::Converter do
|
124
|
+
it "returns the result of conversion." do
|
125
|
+
template = "a test string with tags (<%= key1 %> and <%= key2 %>) in it"
|
126
|
+
config_data = <<CONFIG
|
127
|
+
key1: value1
|
128
|
+
key2: value2
|
129
|
+
CONFIG
|
130
|
+
|
131
|
+
tree = AdHocTemplate::Parser.parse(template)
|
132
|
+
config = AdHocTemplate::RecordReader.read_record(config_data)
|
133
|
+
expect(AdHocTemplate::Converter.new(config).format(tree)).to eq("a test string with tags (value1 and value2) in it")
|
134
|
+
end
|
135
|
+
|
136
|
+
it "accepts a template with an iteration block and evaluate repeatedly the block" do
|
137
|
+
template = <<TEMPLATE
|
138
|
+
a test string with tags (<%= key1 %> and <%= key2 %>) in it
|
139
|
+
|
140
|
+
<%#iteration_block
|
141
|
+
the value of sub_key1 is <%= sub_key1 %>
|
142
|
+
the value of sub_key2 is <%= sub_key2 %>
|
143
|
+
|
144
|
+
#%>
|
145
|
+
<%= block %>
|
146
|
+
TEMPLATE
|
147
|
+
|
148
|
+
config_data = <<CONFIG
|
149
|
+
key1: value1
|
150
|
+
key2: value2
|
151
|
+
key3: value3
|
152
|
+
|
153
|
+
//@#iteration_block
|
154
|
+
|
155
|
+
sub_key1: value1-1
|
156
|
+
sub_key2: value1-2
|
157
|
+
|
158
|
+
sub_key1: value2-1
|
159
|
+
sub_key2: value2-2
|
160
|
+
|
161
|
+
//@block
|
162
|
+
|
163
|
+
the first line of block
|
164
|
+
the second line of block
|
165
|
+
|
166
|
+
the second paragraph in block
|
167
|
+
|
168
|
+
CONFIG
|
169
|
+
|
170
|
+
expected_result = <<RESULT
|
171
|
+
a test string with tags (value1 and value2) in it
|
172
|
+
|
173
|
+
the value of sub_key1 is value1-1
|
174
|
+
the value of sub_key2 is value1-2
|
175
|
+
|
176
|
+
the value of sub_key1 is value2-1
|
177
|
+
the value of sub_key2 is value2-2
|
178
|
+
|
179
|
+
|
180
|
+
the first line of block
|
181
|
+
the second line of block
|
182
|
+
|
183
|
+
the second paragraph in block
|
184
|
+
|
185
|
+
RESULT
|
186
|
+
tree = AdHocTemplate::Parser.parse(template)
|
187
|
+
config = AdHocTemplate::RecordReader.read_record(config_data)
|
188
|
+
expect(AdHocTemplate::Converter.new(config).format(tree)).to eq(expected_result)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "may contains iteration blocks without key label." do
|
192
|
+
template = <<TEMPLATE
|
193
|
+
a test string with tags (<%= key1 %> and <%= key2 %>) in it
|
194
|
+
|
195
|
+
<%#
|
196
|
+
the value of key1 is <%= key1 %>
|
197
|
+
the value of key2 is <%= key2 %>
|
198
|
+
|
199
|
+
#%>
|
200
|
+
<%#
|
201
|
+
the value of key2 is <%= non-existent-key %>
|
202
|
+
the value of key2 is <%= key-without-value %>
|
203
|
+
#%>
|
204
|
+
<%# the value of key2 is <%= non-existent-key %> #%>
|
205
|
+
<%= block %>
|
206
|
+
TEMPLATE
|
207
|
+
|
208
|
+
config_data = <<CONFIG
|
209
|
+
key1: value1
|
210
|
+
key2: value2
|
211
|
+
key3: value3
|
212
|
+
key-without-value:
|
213
|
+
|
214
|
+
//@block
|
215
|
+
|
216
|
+
the first line of block
|
217
|
+
the second line of block
|
218
|
+
|
219
|
+
the second paragraph in block
|
220
|
+
|
221
|
+
CONFIG
|
222
|
+
|
223
|
+
expected_result = <<RESULT
|
224
|
+
a test string with tags (value1 and value2) in it
|
225
|
+
|
226
|
+
the value of key1 is value1
|
227
|
+
the value of key2 is value2
|
228
|
+
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
the first line of block
|
233
|
+
the second line of block
|
234
|
+
|
235
|
+
the second paragraph in block
|
236
|
+
|
237
|
+
RESULT
|
238
|
+
tree = AdHocTemplate::Parser.parse(template)
|
239
|
+
config = AdHocTemplate::RecordReader.read_record(config_data)
|
240
|
+
expect(AdHocTemplate::Converter.new(config).format(tree)).to eq(expected_result)
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'can convert &"<> into character entities' do
|
244
|
+
result = AdHocTemplate::Converter.convert('characters: &, ", < and >',
|
245
|
+
'a string with characters (<%h characters %>) that should be represented as character entities.')
|
246
|
+
expect(result).to eq('a string with characters (&, ", < and >) that should be represented as character entities.')
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'shellwords'
|
4
|
+
require 'stringio'
|
5
|
+
require 'spec_helper'
|
6
|
+
require 'ad_hoc_template'
|
7
|
+
require 'ad_hoc_template/command_line_interface'
|
8
|
+
|
9
|
+
describe AdHocTemplate do
|
10
|
+
describe AdHocTemplate::CommandLineInterface do
|
11
|
+
it "can set the input/output encoding" do
|
12
|
+
command_line_interface = AdHocTemplate::CommandLineInterface.new
|
13
|
+
command_line_interface.set_encoding("UTF-8:Shift_JIS")
|
14
|
+
expect(command_line_interface.class::Encoding.default_external.names).to include("UTF-8")
|
15
|
+
expect(command_line_interface.class::Encoding.default_internal.names).to include("Shift_JIS")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "accepts an internal only argument" do
|
19
|
+
command_line_interface = AdHocTemplate::CommandLineInterface.new
|
20
|
+
command_line_interface.set_encoding(":UTF-8")
|
21
|
+
expect(command_line_interface.class::Encoding.default_internal.names).to include("UTF-8")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "accepts also an external only argument" do
|
25
|
+
command_line_interface = AdHocTemplate::CommandLineInterface.new
|
26
|
+
command_line_interface.set_encoding("Shift_JIS")
|
27
|
+
expect(command_line_interface.class::Encoding.default_external.names).to include("Shift_JIS")
|
28
|
+
command_line_interface.set_encoding("UTF-8:")
|
29
|
+
expect(command_line_interface.class::Encoding.default_external.names).to include("UTF-8")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can set the internal/external encoding from the command line" do
|
33
|
+
command_line_interface = AdHocTemplate::CommandLineInterface.new
|
34
|
+
set_argv("-E UTF-8:Shift_JIS")
|
35
|
+
command_line_interface.parse_command_line_options
|
36
|
+
expect(command_line_interface.class::Encoding.default_external.names).to include("UTF-8")
|
37
|
+
expect(command_line_interface.class::Encoding.default_internal.names).to include("Shift_JIS")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can specify the output file from command line" do
|
41
|
+
pwd = File.expand_path(".")
|
42
|
+
output_filename = "file_for_saving_result.txt"
|
43
|
+
command_line_interface = AdHocTemplate::CommandLineInterface.new
|
44
|
+
set_argv("-o #{output_filename}")
|
45
|
+
command_line_interface.parse_command_line_options
|
46
|
+
expect(command_line_interface.output_filename).to eq(File.join(pwd, output_filename))
|
47
|
+
end
|
48
|
+
|
49
|
+
it "reads input data from command line" do
|
50
|
+
template_filename = "template.txt"
|
51
|
+
record_filename = "record.txt"
|
52
|
+
template = "a dummy content of template file"
|
53
|
+
record = "a dummy content of record file"
|
54
|
+
|
55
|
+
allow(File).to receive(:read).with(File.expand_path(template_filename)).and_return(template)
|
56
|
+
allow(File).to receive(:read).with(File.expand_path(record_filename)).and_return(record)
|
57
|
+
|
58
|
+
set_argv("#{template_filename} #{record_filename}")
|
59
|
+
command_line_interface = AdHocTemplate::CommandLineInterface.new
|
60
|
+
command_line_interface.read_input_files
|
61
|
+
|
62
|
+
expect(command_line_interface.template_data).to eq(template)
|
63
|
+
expect(command_line_interface.record_data).to eq(record)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "returns the result to the standard output unless an output file is specified." do
|
67
|
+
template_filename = "template.txt"
|
68
|
+
record_filename = "record.txt"
|
69
|
+
|
70
|
+
template = <<TEMPLATE
|
71
|
+
a test string with tags (<%= key1 %> and <%= key2 %>) in it
|
72
|
+
|
73
|
+
<%#iteration_block
|
74
|
+
the value of sub_key1 is <%= sub_key1 %>
|
75
|
+
the value of sub_key2 is <%= sub_key2 %>
|
76
|
+
|
77
|
+
#%>
|
78
|
+
<%= block %>
|
79
|
+
TEMPLATE
|
80
|
+
|
81
|
+
record = <<CONFIG
|
82
|
+
key1: value1
|
83
|
+
key2: value2
|
84
|
+
key3: value3
|
85
|
+
|
86
|
+
//@#iteration_block
|
87
|
+
|
88
|
+
sub_key1: value1-1
|
89
|
+
sub_key2: value1-2
|
90
|
+
|
91
|
+
sub_key1: value2-1
|
92
|
+
sub_key2: value2-2
|
93
|
+
|
94
|
+
//@block
|
95
|
+
|
96
|
+
the first line of block
|
97
|
+
the second line of block
|
98
|
+
|
99
|
+
the second paragraph in block
|
100
|
+
|
101
|
+
CONFIG
|
102
|
+
|
103
|
+
expected_result = <<RESULT
|
104
|
+
a test string with tags (value1 and value2) in it
|
105
|
+
|
106
|
+
the value of sub_key1 is value1-1
|
107
|
+
the value of sub_key2 is value1-2
|
108
|
+
|
109
|
+
the value of sub_key1 is value2-1
|
110
|
+
the value of sub_key2 is value2-2
|
111
|
+
|
112
|
+
|
113
|
+
the first line of block
|
114
|
+
the second line of block
|
115
|
+
|
116
|
+
the second paragraph in block
|
117
|
+
|
118
|
+
RESULT
|
119
|
+
|
120
|
+
|
121
|
+
allow(File).to receive(:read).with(File.expand_path(template_filename)).and_return(template)
|
122
|
+
allow(File).to receive(:read).with(File.expand_path(record_filename)).and_return(record)
|
123
|
+
allow(STDOUT).to receive(:print).with(expected_result)
|
124
|
+
|
125
|
+
set_argv("#{template_filename} #{record_filename}")
|
126
|
+
command_line_interface = AdHocTemplate::CommandLineInterface.new
|
127
|
+
command_line_interface.execute
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'shellwords'
|
3
|
+
require 'ad_hoc_template'
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
def set_argv(command_line_str)
|
7
|
+
ARGV.replace Shellwords.split(command_line_str)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec.configure do |c|
|
12
|
+
c.include Helpers
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ad_hoc_template
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- HASHIMOTO, Naoki
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pseudohikiparser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.1'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.2'
|
69
|
+
description: AdHocTemplate is a template processor with simple but sufficient rules
|
70
|
+
for some ad hoc tasks.
|
71
|
+
email:
|
72
|
+
- hashimoto.naoki@gmail.com
|
73
|
+
executables:
|
74
|
+
- ad_hoc_template
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- ".gitignore"
|
79
|
+
- ".rspec"
|
80
|
+
- ".travis.yml"
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- ad_hoc_template.gemspec
|
86
|
+
- bin/ad_hoc_template
|
87
|
+
- lib/ad_hoc_template.rb
|
88
|
+
- lib/ad_hoc_template/command_line_interface.rb
|
89
|
+
- lib/ad_hoc_template/version.rb
|
90
|
+
- spec/ad_hoc_template_spec.rb
|
91
|
+
- spec/command_line_interface_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
homepage: https://github.com/nico-hn/AdHocTemplate
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 2.2.2
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: A tiny template processor
|
117
|
+
test_files:
|
118
|
+
- spec/ad_hoc_template_spec.rb
|
119
|
+
- spec/command_line_interface_spec.rb
|
120
|
+
- spec/spec_helper.rb
|
121
|
+
has_rdoc:
|