ad_hoc_template 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'pseudohikiparser'
4
4
 
@@ -20,6 +20,8 @@ module AdHocTemplate
20
20
  private_class_method :choose_parser
21
21
  end
22
22
 
23
- assign_format("ph") {|var, record| PseudoHikiFormatter.to_xhtml(var, record) }
23
+ assign_format('ph') do |var, record|
24
+ PseudoHikiFormatter.to_xhtml(var, record)
25
+ end
24
26
  end
25
27
  end
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'ad_hoc_template'
4
4
  require 'ad_hoc_template/utils'
@@ -10,12 +10,12 @@ module AdHocTemplate
10
10
  attr_reader :output_file, :template_encoding, :template
11
11
  attr_reader :records, :recipe
12
12
 
13
- def self.update_output_files_in_recipe(recipe, force_update=false)
14
- recipe_source = open(File.expand_path(recipe)) {|file| file.read }
13
+ def self.update_output_files_in_recipe(recipe_file, force_update=false)
14
+ recipe_source = File.open(File.expand_path(recipe_file), &:read)
15
15
  recipes = YAML.load_stream(recipe_source)
16
16
  recipes.each do |recipe|
17
17
  manager = new(recipe)
18
- if manager.modified_after_last_output? or force_update
18
+ if manager.modified_after_last_output? || force_update
19
19
  manager.update_output_file
20
20
  end
21
21
  end
@@ -60,10 +60,8 @@ module AdHocTemplate
60
60
 
61
61
  def parse_template
62
62
  template_path = File.expand_path(@recipe['template'])
63
- template_source = open(template_path,
64
- open_mode(@template_encoding)) do |file|
65
- file.read
66
- end
63
+ template_source = File.open(template_path,
64
+ open_mode(@template_encoding), &:read)
67
65
  tag_type = @recipe['tag_type'] || :default
68
66
  tag_type = tag_type.to_sym unless tag_type.kind_of? Symbol
69
67
  @template = Parser.parse(template_source, tag_type)
@@ -75,7 +73,8 @@ module AdHocTemplate
75
73
  content = AdHocTemplate::DataLoader.format(@template, @records)
76
74
  mode = @template_encoding ? "wb:#{@template_encoding}" : 'wb'
77
75
  if @output_file
78
- open(File.expand_path(@output_file), mode) {|file| file.print content }
76
+ File.open(File.expand_path(@output_file),
77
+ mode) {|file| file.print content }
79
78
  else
80
79
  STDOUT.print content
81
80
  end
@@ -117,15 +116,14 @@ module AdHocTemplate
117
116
  end
118
117
 
119
118
  def setup_main_label
120
- if data_format = @default['data_format'] and
121
- [:csv, :tsv].include? data_format
119
+ if (data_format = @default['data_format']) && csv_or_tsv?(data_format)
122
120
  @default['label'] ||= RecordReader::CSVReader::HEADER_POSITION::LEFT
123
121
  end
124
122
  end
125
123
 
126
124
  def determine_data_format!(block)
127
125
  data_format = block['data_format']
128
- if not data_format and block['data']
126
+ if !data_format && block['data']
129
127
  data_format = guess_file_format(block['data'])
130
128
  end
131
129
 
@@ -133,31 +131,25 @@ module AdHocTemplate
133
131
  end
134
132
 
135
133
  def read_file(file_name, encoding)
136
- open(File.expand_path(file_name),
137
- open_mode(encoding)) do |file|
138
- file.read
139
- end
134
+ File.open(File.expand_path(file_name),
135
+ open_mode(encoding), &:read)
140
136
  end
141
137
 
142
138
  def open_mode(encoding)
143
139
  encoding ||= Encoding.default_external.names[0]
144
- mode = "rb"
145
- return mode unless encoding and not encoding.empty?
140
+ mode = 'rb'
141
+ return mode unless encoding && !encoding.empty?
146
142
  bom = /\AUTF/i =~ encoding ? 'BOM|' : ''
147
- mode += ":#{bom}#{encoding}"
143
+ "#{mode}:#{bom}#{encoding}"
148
144
  end
149
145
 
150
146
  def prepare_data_format(block)
151
147
  data_format = block['data_format']
152
- if not data_format or data_format.empty?
153
- data_format = :default
154
- end
148
+ data_format = :default if !data_format || data_format.empty?
155
149
  data_format = data_format.to_sym
156
- return data_format unless [:csv, :tsv].include? data_format
157
- if label = block['label']
158
- label = label.sub(/\A#/, '')
159
- data_format = { data_format => label }
160
- end
150
+ return data_format unless csv_or_tsv? data_format
151
+ label = block['label']
152
+ return { data_format => label.sub(/\A#/, '') } if label
161
153
  data_format
162
154
  end
163
155
 
@@ -1,14 +1,17 @@
1
- #!/usr/bin/env ruby
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
4
  require 'json'
5
5
  require 'csv'
6
6
 
7
+ require 'ad_hoc_template/shim'
8
+
7
9
  module AdHocTemplate
8
10
  module RecordReader
9
11
  module YAMLReader
10
12
  def self.read_record(yaml_data)
11
- RecordReader.convert_values_to_string(YAML.load(yaml_data))
13
+ RecordReader.convert_values_to_string(YAML.safe_load(yaml_data,
14
+ [Symbol]))
12
15
  end
13
16
 
14
17
  def self.dump(config_data)
@@ -29,10 +32,12 @@ module AdHocTemplate
29
32
  end
30
33
 
31
34
  module CSVReader
35
+ using Shim unless ''.respond_to?(:+@)
36
+
32
37
  COL_SEP = {
33
38
  csv: CSV::DEFAULT_OPTIONS[:col_sep],
34
- tsv: "\t"
35
- }
39
+ tsv: "\t",
40
+ }.freeze
36
41
 
37
42
  module HEADER_POSITION
38
43
  TOP = '__header_top__'
@@ -42,10 +47,10 @@ module AdHocTemplate
42
47
  class NotSupportedError < StandardError; end
43
48
 
44
49
  def self.read_record(csv_data, config={ csv: nil })
45
- label, sep = parse_config(config)
50
+ label, sep = parse_config(config)
46
51
  header, *data = csv_to_array(csv_data, sep, label)
47
52
  csv_records = data.map {|row| convert_to_hash(header, row) }
48
- if label and label.index('|')
53
+ if label && label.index('|')
49
54
  return compose_inner_iteration_records(csv_records, label)
50
55
  end
51
56
  compose_record(csv_records, label)
@@ -55,20 +60,15 @@ module AdHocTemplate
55
60
  data = RecordReader.parse_if_necessary(config_data)
56
61
  raise NotSupportedError unless csv_compatible_format?(data)
57
62
 
58
- if kv_pairs = find_sub_records(data)
59
- records = hashes_to_arrays(kv_pairs)
60
- else
61
- records = data.to_a.transpose
62
- end
63
+ kv_pairs = find_sub_records(data)
64
+ records = kv_pairs ? hashes_to_arrays(kv_pairs) : data.to_a.transpose
63
65
 
64
66
  array_to_csv(records, col_sep)
65
67
  end
66
68
 
67
69
  def self.convert_to_hash(header, row_array)
68
70
  {}.tap do |record|
69
- header.zip(row_array).each do |key, value|
70
- record[key] = value
71
- end
71
+ header.zip(row_array).each {|key, value| record[key] = value }
72
72
  end
73
73
  # if RUBY_VERSION >= 2.1.0: header.zip(row_array).to_h
74
74
  end
@@ -82,15 +82,13 @@ module AdHocTemplate
82
82
  when Hash
83
83
  format, label = config.to_a[0]
84
84
  end
85
- col_sep = COL_SEP[format||:csv]
86
- return label, col_sep
85
+ col_sep = COL_SEP[format || :csv]
86
+ [label, col_sep]
87
87
  end
88
88
 
89
89
  def self.csv_to_array(csv_data, col_sep, label)
90
90
  array = CSV.new(csv_data, col_sep: col_sep).to_a
91
- if not label or label == HEADER_POSITION::LEFT
92
- array = array.transpose
93
- end
91
+ array = array.transpose if !label || label == HEADER_POSITION::LEFT
94
92
  array
95
93
  end
96
94
 
@@ -123,15 +121,15 @@ module AdHocTemplate
123
121
  end
124
122
 
125
123
  def self.inner_iteration_labels(outer_label, inner_label, keys)
126
- labels = keys.inject({}) do |h, key|
127
- h[key] = [outer_label, inner_label, key].join('|')
128
- h
124
+ keys.each_with_object({}) do |key, labels|
125
+ labels[key] = [outer_label, inner_label, key].join('|')
129
126
  end
130
127
  end
131
128
 
132
129
  def self.csv_compatible_format?(data)
133
130
  iteration_blocks_count = data.values.count {|v| v.kind_of? Array }
134
- iteration_blocks_count == 0 or (iteration_blocks_count == 1 && data.size == 1)
131
+ iteration_blocks_count.zero? ||
132
+ (iteration_blocks_count == 1 && data.size == 1)
135
133
  end
136
134
 
137
135
  def self.hashes_to_arrays(data)
@@ -147,10 +145,11 @@ module AdHocTemplate
147
145
  def self.array_to_csv(records, col_sep)
148
146
  # I do not adopt "records.map {|rec| rec.to_csv }.join",
149
147
  # because I'm not sure if it is sufficient for certain data or not.
150
- # For example, a field value may contain carriage returns or line feeds,
151
- # and in that case, improper handling of the end of record would be damaging.
148
+ # For example, a field value may contain carriage returns or line
149
+ # feeds, and in that case, improper handling of the end of record
150
+ # would be damaging.
152
151
 
153
- CSV.generate('', col_sep: col_sep) do |csv|
152
+ CSV.generate(+'', col_sep: col_sep) do |csv|
154
153
  records.each {|record| csv << record }
155
154
  end
156
155
  end
@@ -179,17 +178,17 @@ module AdHocTemplate
179
178
 
180
179
  module DefaultFormReader
181
180
  SEPARATOR = /:\s*/o
182
- BLOCK_HEAD = /\A\/\/\/@/o
183
- ITERATION_HEAD = /\A\/\/\/@#/o
181
+ BLOCK_HEAD = %r{\A///@}
182
+ ITERATION_HEAD = %r{\A///@#}
184
183
  EMPTY_LINE = /\A#{LINE_END_STR}\Z/o
185
184
  ITERATION_MARK = /\A#/o
186
- COMMENT_HEAD = /\A\/\/\/\//
185
+ COMMENT_HEAD = %r{\A////}
187
186
  READERS_RE = {
188
187
  key_value: SEPARATOR,
189
188
  iteration: ITERATION_HEAD,
190
189
  block: BLOCK_HEAD,
191
190
  empty_line: EMPTY_LINE,
192
- }
191
+ }.freeze
193
192
 
194
193
  class ReaderState
195
194
  attr_accessor :current_block_label
@@ -266,6 +265,8 @@ module AdHocTemplate
266
265
  end
267
266
 
268
267
  class Reader
268
+ using Shim unless //.respond_to? :match?
269
+
269
270
  def self.setup_reader(stack)
270
271
  readers = {}
271
272
  {
@@ -273,9 +274,7 @@ module AdHocTemplate
273
274
  key_value: KeyValueReader,
274
275
  block: BlockReader,
275
276
  iteration: IterationReader,
276
- }.each do |k, v|
277
- readers[k] = v.new(stack, readers)
278
- end
277
+ }.each {|k, v| readers[k] = v.new(stack, readers) }
279
278
  stack.push readers[:base]
280
279
  readers
281
280
  end
@@ -289,28 +288,28 @@ module AdHocTemplate
289
288
  @stack.pop
290
289
  end
291
290
 
292
- def read(line)
293
- end
291
+ def read(line); end
294
292
 
295
293
  private
296
294
 
297
295
  def push_reader_if_match(line, readers)
298
296
  readers.each do |reader|
299
- return @stack.push(@readers[reader]) if READERS_RE[reader] === line
297
+ if READERS_RE[reader].match?(line)
298
+ return @stack.push(@readers[reader])
299
+ end
300
300
  end
301
301
  end
302
302
 
303
303
  def setup_new_block(line, initial_value)
304
- label = line.sub(BLOCK_HEAD, "").chomp
304
+ label = line.sub(BLOCK_HEAD, '').chomp
305
305
  @stack.current_record[label] ||= initial_value
306
306
  @stack.current_block_label = label
307
307
  end
308
308
  end
309
309
 
310
-
311
310
  class BaseReader < Reader
312
311
  def setup_stack(line)
313
- push_reader_if_match(line, [:iteration, :block, :key_value])
312
+ push_reader_if_match(line, %i[iteration block key_value])
314
313
  end
315
314
  end
316
315
 
@@ -320,7 +319,7 @@ module AdHocTemplate
320
319
  when EMPTY_LINE, ITERATION_HEAD, BLOCK_HEAD
321
320
  pop_stack
322
321
  end
323
- push_reader_if_match(line, [:iteration, :block])
322
+ push_reader_if_match(line, %i[iteration block])
324
323
  end
325
324
 
326
325
  def read(line)
@@ -331,20 +330,22 @@ module AdHocTemplate
331
330
  end
332
331
 
333
332
  class BlockReader < Reader
333
+ using Shim unless ''.respond_to?(:+@)
334
+
334
335
  def setup_stack(line)
335
336
  case line
336
337
  when ITERATION_HEAD, BLOCK_HEAD
337
338
  @stack.remove_trailing_empty_lines_from_last_block!
338
339
  pop_stack
339
340
  end
340
- push_reader_if_match(line, [:iteration, :block])
341
+ push_reader_if_match(line, %i[iteration block])
341
342
  end
342
343
 
343
344
  def read(line)
344
345
  block_value = @stack.last_block_value
345
346
  case line
346
347
  when BLOCK_HEAD
347
- setup_new_block(line, String.new)
348
+ setup_new_block(line, +'')
348
349
  when EMPTY_LINE, COMMENT_HEAD
349
350
  block_value << line unless block_value.empty?
350
351
  else
@@ -389,7 +390,12 @@ module AdHocTemplate
389
390
  iteration_part = format_iteration_block(iteration_keys, labels)
390
391
  block_part = format_key_value_block(block_keys, labels)
391
392
 
392
- [key_value_part, iteration_part, block_part].join($/).sub(/(#{$/}+)\Z/, $/)
393
+ all_parts = [key_value_part, iteration_part, block_part].join($/)
394
+ remove_redundant_newlines(all_parts)
395
+ end
396
+
397
+ def self.remove_redundant_newlines(str)
398
+ str.sub(/(#{$/}+)\Z/, $/)
393
399
  end
394
400
 
395
401
  def self.format_key_value_pairs(key_names, labels={})
@@ -399,33 +405,32 @@ module AdHocTemplate
399
405
  def self.format_key_value_block(key_names, labels)
400
406
  [].tap do |blocks|
401
407
  key_names.each do |key|
402
- blocks.push "///@#{key}#{$/*2}#{labels[key]}"
408
+ blocks.push "///@#{key}#{$/ * 2}#{labels[key]}"
403
409
  end
404
410
  end.join($/)
405
411
  end
406
412
 
407
413
  def self.format_iteration_block(key_names, labels)
408
414
  key_names.map do |iteration_label|
409
- iteration_block = ["///@#{iteration_label}#{$/}"]
410
- labels[iteration_label].each do |sub_record|
411
- iteration_block.push format_key_value_pairs(sub_record.keys, sub_record)
415
+ iteration_block = labels[iteration_label].map do |sub_record|
416
+ format_key_value_pairs(sub_record.keys, sub_record)
412
417
  end
413
- iteration_block.join($/)
418
+ iteration_block.unshift("///@#{iteration_label}#{$/}")
414
419
  end.join($/)
415
420
  end
416
421
 
417
422
  def self.categorize_keys(labels)
418
- iteration_part, rest = labels.partition do |e|
419
- e[1].kind_of? Array
420
- end.map {|e| e.map(&:first) }
423
+ iteration_part, rest = labels.partition {|e| e[1].kind_of? Array }
424
+ .map {|e| e.map(&:first) }
421
425
 
422
426
  block_part, key_value_part = rest.partition do |e|
423
427
  LINE_END_RE =~ labels[e]
424
428
  end
425
429
 
426
- return iteration_part, key_value_part, block_part
430
+ [iteration_part, key_value_part, block_part]
427
431
  end
428
432
 
433
+ private_class_method :remove_redundant_newlines
429
434
  private_class_method :format_key_value_pairs
430
435
  private_class_method :format_key_value_block
431
436
  private_class_method :format_iteration_block
@@ -438,9 +443,7 @@ module AdHocTemplate
438
443
  csv: CSVReader,
439
444
  tsv: TSVReader,
440
445
  default: DefaultFormReader,
441
- }
442
-
443
- FORMAT_NAME_TO_READER.default = DefaultFormReader
446
+ }.tap {|h| h.default = DefaultFormReader }.freeze
444
447
 
445
448
  def self.dump(data_source, target_format=:default)
446
449
  FORMAT_NAME_TO_READER[target_format].dump(data_source)
@@ -463,7 +466,7 @@ module AdHocTemplate
463
466
  data.each do |k, v|
464
467
  if v.kind_of? Array
465
468
  v.each {|sub_rec| convert_values_to_string(sub_rec) }
466
- elsif v and not v.kind_of? String
469
+ elsif v && !v.kind_of?(String)
467
470
  data[k] = v.to_s
468
471
  end
469
472
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdHocTemplate
4
+ ##
5
+ # Provide methods that are not availabe in older versions of Ruby.
6
+
7
+ module Shim
8
+ refine Regexp do
9
+ ##
10
+ # Regexp.match?() is available for Ruby >= 2.4,
11
+ # and the following implementation does not satisfy
12
+ # the full specification of the original method.
13
+
14
+ alias_method(:match?, :===)
15
+ end
16
+
17
+ refine String do
18
+ ##
19
+ # Regexp.match?() is available for Ruby >= 2.3,
20
+ # and the following implementation does not satisfy
21
+ # the full specification of the original method.
22
+
23
+ alias_method(:+@, :dup)
24
+ end
25
+ end
26
+ end
@@ -1,16 +1,16 @@
1
- #!/usr/bin/env ruby
1
+ # frozen_string_literal: true
2
2
 
3
3
  module AdHocTemplate
4
4
  module Utils
5
- FILE_EXTENTIONS = {
5
+ FILE_EXTENTIONS = {
6
6
  /\.ya?ml\Z/i => :yaml,
7
7
  /\.json\Z/i => :json,
8
8
  /\.csv\Z/i => :csv,
9
9
  /\.tsv\Z/i => :tsv,
10
- }
10
+ }.freeze
11
11
 
12
12
  def guess_file_format(filename)
13
- if_any_regex_match(FILE_EXTENTIONS, filename) do |ext_re, format|
13
+ if_any_regex_match(FILE_EXTENTIONS, filename) do |_, format|
14
14
  return format
15
15
  end
16
16
  end
@@ -19,11 +19,17 @@ module AdHocTemplate
19
19
  regex_table.each do |re, paired_value|
20
20
  if re =~ target
21
21
  yield re, paired_value
22
- return
22
+ return nil
23
23
  end
24
24
  end
25
25
  STDERR.puts failure_message if failure_message
26
26
  nil
27
27
  end
28
+
29
+ private
30
+
31
+ def csv_or_tsv?(format)
32
+ %i[csv tsv].include? format
33
+ end
28
34
  end
29
35
  end