ecg 0.2.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d22e44c0a03b9a540006d7c0502e8c3c73101f7c446c9c05393ba5319bdc57ae
4
- data.tar.gz: 03e5ed33aacd6ef40dfb7d7c0da2533d572259f444ce08e57d6a077bf4a64436
3
+ metadata.gz: 260ccf7bd2c398c65719d3f5f9b58ca7665a50921440e05a3be2937873f8b2d0
4
+ data.tar.gz: e98ff38808434920d59cd1d1ce9b32e8dab5e3df756f6a32dbbed8db3ef30bd9
5
5
  SHA512:
6
- metadata.gz: a6177f797de5728e5daf1f30b2178cb4f25c6281dc1cf2c9452afa88c9d1f5de54eabd3aa5afb12a40a05512e819a551bc052a92d7bbbeb17f3b35b008c37272
7
- data.tar.gz: 59e1caa2a49b47af62ce27ee6b7b008ad3c8df286d7e84a77d0224892b9dd14dc9226d3586a50f637efe13e55e6869055797ec9b78673172168bd6fbbe506c74
6
+ metadata.gz: 560ea77d18fcc83091068017084cdc86e4b6ae660dfe8a2f4d703e38ef2c561a073b5560a7b57ea27d4b39a998fecc2927df3255397b0db859f9c231a163437c
7
+ data.tar.gz: 473ceabbce68c12f01e99499bcfe128815fcbbfa502bd6a4d5dc6fea760875ec48cce840f7704a38a8f25af89e0d6302144be49d278ee9962dd2fd6b6f0d65e0
data/bin/ecg CHANGED
@@ -4,5 +4,5 @@
4
4
  $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
5
5
  require 'ecg'
6
6
 
7
- cmd = ECG::Command.new(ARGV)
7
+ cmd = ECG::Command.new
8
8
  cmd.execute
data/lib/ecg/command.rb CHANGED
@@ -5,16 +5,18 @@ require_relative 'option_parser'
5
5
 
6
6
  module ECG
7
7
  class Command
8
- def initialize(args)
9
- @parser = OptionParser.new(args)
8
+ def initialize
9
+ @context = Context.instance
10
10
  end
11
11
 
12
- def execute(input = $stdin, output = $stdout)
13
- context = @parser.context
12
+ def execute
13
+ @context.parser.parse!
14
14
  # NOTE: ERB.new's non-keyword arguments are deprecated in 2.6
15
15
  # erb = ERB.new(input.read, trim_mode: context.trim_mode)
16
- erb = ERB.new(input.read, nil, context.trim_mode)
17
- output.puts erb.result(context.values.binding)
16
+ @context.targets.each do |input, output|
17
+ erb = ERB.new(input.read, nil, @context.trim_mode)
18
+ output.puts erb.result(@context.binding)
19
+ end
18
20
  end
19
21
  end
20
22
  end
data/lib/ecg/context.rb CHANGED
@@ -1,42 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'singleton'
3
4
  require_relative 'store'
5
+ require_relative 'plugin/load_file_values'
6
+ require_relative 'plugin/load_option_values'
4
7
 
5
8
  module ECG
6
9
  class Context
7
- attr_reader :trim_mode
10
+ include Singleton
11
+ prepend Plugin::LoadFileValues
12
+ prepend Plugin::LoadOptionValues
8
13
 
9
- def initialize(filepath:, trim_mode:, values:)
10
- @trim_mode = trim_mode
11
- @values_file = filepath
12
- @values_option = values
13
- end
14
+ attr_reader :parser, :targets, :values
14
15
 
15
- def values
16
- Store.new(merged_hash)
16
+ def initialize
17
+ @parser = OptionParser.instance
18
+ @targets = { $stdin => $stdout }
19
+ @values = Store.new
17
20
  end
18
21
 
19
- private
22
+ def binding
23
+ setup
24
+ super # NOTE: Kernel.#binding is private
25
+ end
20
26
 
21
- def load_file(path)
22
- case File.extname(path)
23
- when '.json'
24
- require 'json'
25
- File.open(path) { |f| JSON.parse(f.read) }
26
- when /\A\.ya?ml\z/
27
- require 'yaml'
28
- # NOTE: YAML.safe_load's non-keyword arguments are deprecated in 2.6
29
- # File.open(path) { |f| YAML.safe_load(f.read, aliases: true) }
30
- File.open(path) { |f| YAML.safe_load(f.read, [], [], true) }
27
+ def method_missing(name, *args, &block)
28
+ if @values.respond_to?(name)
29
+ @values.public_send(name, *args, &block)
31
30
  else
32
- raise ArgumentError,
33
- "Cannot load file `#{path}`. Only JSON/YAML are allowed."
31
+ super
34
32
  end
35
33
  end
36
34
 
37
- def merged_hash
38
- hash = @values_file.nil? ? {} : load_file(@values_file)
39
- hash.merge(@values_option)
35
+ def respond_to_missing?(name, include_private = false)
36
+ @values.respond_to?(name) || super
37
+ end
38
+
39
+ def trim_mode
40
+ @parser.trim_mode
40
41
  end
42
+
43
+ private
44
+
45
+ def setup; end
41
46
  end
42
47
  end
@@ -1,115 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'optparse'
4
- require_relative 'context'
4
+ require 'singleton'
5
5
  require_relative 'version'
6
6
 
7
7
  module ECG
8
- class OptionParser
9
- def initialize(args = [])
10
- @args = args
11
- @options = { filepath: nil, trim_mode: '<>', values: {} }
12
- end
8
+ class OptionParser < ::OptionParser
9
+ include Singleton
10
+ attr_reader :args, :trim_mode
13
11
 
14
- def context
15
- return @context if instance_variable_defined?(:@context)
12
+ def initialize
13
+ super
16
14
 
17
- parse!
18
- @context = Context.new(@options)
19
- end
15
+ @args = ARGV.dup
16
+ @trim_mode = '<>'
20
17
 
21
- private
18
+ on_tail(
19
+ '-t', '--trim-mode MODE', %(Set ERB's trim_mode. Default is "<>".)
20
+ ) do |v|
21
+ @trim_mode = v
22
+ end
23
+ on_tail('-V', '--version', 'Print version information') do
24
+ puts ver
25
+ exit
26
+ end
27
+ on_tail('-h', '--help', 'Print this help message', &method(:print_help))
28
+ end
22
29
 
23
30
  def banner
24
- <<~__BANNER__
25
- Usage: ecg [config.{json,yaml}] [options]
26
- __BANNER__
31
+ 'Usage: ecg [config.{json,yaml}] [options]'
27
32
  end
28
33
 
29
- def filepath
30
- print_help(false) if @args.length > 1
31
- return nil if @args.empty?
32
-
33
- path = @args.first
34
- extname = File.extname(path)
35
- if %w[.json .yml .yaml].none? { |ext| ext == extname }
36
- raise ArgumentError,
37
- "Cannot load file `#{path}`. Only JSON/YAML are allowed."
38
- end
39
-
40
- path
41
- end
34
+ undef_method :parse
42
35
 
43
36
  def parse!
44
37
  print_help(false) if @args.empty?
45
38
 
46
- parser.parse!(@args)
47
- @options[:filepath] = filepath
48
- self
39
+ super(@args)
49
40
  end
50
41
 
51
- def parser # rubocop:disable Style/MethodLength
52
- ::OptionParser.new do |opt|
53
- opt.banner = banner
54
- opt.version = VERSION
55
-
56
- opt.on('-v', '--values Key=Value', <<~__DESC__) do |str|
57
- Set the key-value mapping to be embedded in the template.
58
- __DESC__
59
-
60
- set_option_value(*str.split('=', 2))
61
- end
62
- opt.on('-t', '--trim-mode MODE', <<~__DESC__) do |v|
63
- Set ERB's trim_mode. Default is "<>".
64
- __DESC__
65
- @options[:trim_mode] = v
66
- end
67
- opt.on('-V', '--version', 'Print version information') do
68
- puts opt.ver
69
- exit
70
- end
71
- opt.on('-h', '--help', 'Print this help message', &method(:print_help))
72
- end
42
+ def version
43
+ VERSION
73
44
  end
74
45
 
46
+ private
47
+
75
48
  def print_help(bool)
76
- warn parser.help
49
+ Kernel.warn help
77
50
  exit bool
78
51
  end
79
-
80
- def set_option_value(key, value)
81
- keys = key.split('.').map(&:to_sym)
82
- if keys.count == 1
83
- set_single_option_value(keys.first, value)
84
- else
85
- set_nested_option_value(keys, value)
86
- end
87
- end
88
-
89
- def set_single_option_value(key, value)
90
- values = @options[:values]
91
- if values[key].is_a? Hash
92
- raise ArgumentError, 'Reaf keys cannot have own value.'
93
- end
94
-
95
- values[key] = value
96
- values
97
- end
98
-
99
- def set_nested_option_value(keys, value)
100
- values = @options[:values]
101
- last_key = keys.pop
102
-
103
- nested = keys.inject(values) do |result, key|
104
- result[key] ||= {}
105
- unless result[key].is_a? Hash
106
- raise ArgumentError, 'Reaf keys cannot have own value.'
107
- end
108
-
109
- result[key]
110
- end
111
- nested[last_key] = value
112
- values
113
- end
114
52
  end
115
53
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../option_parser'
4
+
5
+ module ECG
6
+ module Plugin
7
+ module LoadFileValues
8
+ class LoadError < ::StandardError; end
9
+
10
+ private
11
+
12
+ def setup
13
+ super
14
+
15
+ parser = OptionParser.instance
16
+ parser.args.inject(@values) do |result, path|
17
+ result.merge!(load_file(path))
18
+ end
19
+ self
20
+ end
21
+
22
+ def load_file(path)
23
+ case File.extname(path)
24
+ when '.json'
25
+ require 'json'
26
+ File.open(path) { |f| JSON.parse(f.read) }
27
+ when /\A\.ya?ml\z/
28
+ require 'yaml'
29
+ # NOTE: YAML.safe_load's non-keyword arguments are deprecated in 2.6
30
+ # File.open(path) { |f| YAML.safe_load(f.read, aliases: true) }
31
+ File.open(path) { |f| YAML.safe_load(f.read, [], [], true) }
32
+ else
33
+ raise LoadError,
34
+ "Cannot load file `#{path}`. Only JSON/YAML are allowed."
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../option_parser'
4
+
5
+ module ECG
6
+ module Plugin
7
+ module LoadOptionValues
8
+ class LoadError < ::StandardError; end
9
+
10
+ class << self
11
+ attr_reader :option_values
12
+
13
+ def prepended(_mod)
14
+ return if instance_variable_defined?(:@option_values)
15
+
16
+ @option_values = {}
17
+ parser = OptionParser.instance
18
+
19
+ parser.on('-v', '--values Key=Value', <<~__DESC__) do |str|
20
+ Set the key-value mapping to be embedded in the template.
21
+ __DESC__
22
+
23
+ @option_values.store(*str.split('=', 2))
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def setup
31
+ super
32
+
33
+ @values.merge!(
34
+ transform_values(LoadOptionValues.option_values)
35
+ )
36
+ self
37
+ end
38
+
39
+ def transform_values(option_values)
40
+ option_values.inject({}) do |result, (key, value)|
41
+ keys = key.split('.').map(&:to_sym)
42
+
43
+ if keys.count == 1
44
+ set_single_option_value(result, keys.first, value)
45
+ else
46
+ set_nested_option_value(result, keys, value)
47
+ end
48
+ end
49
+ end
50
+
51
+ def set_single_option_value(option_values, key, value)
52
+ if option_values[key].is_a? Hash
53
+ raise LoadError, 'Reaf keys cannot have own value.'
54
+ end
55
+
56
+ option_values[key] = value
57
+ option_values
58
+ end
59
+
60
+ def set_nested_option_value(option_values, keys, value)
61
+ last_key = keys.pop
62
+
63
+ nested = keys.inject(option_values) do |result, key|
64
+ result[key] ||= {}
65
+ unless result[key].is_a? Hash
66
+ raise LoadError, 'Reaf keys cannot have own value.'
67
+ end
68
+
69
+ result[key]
70
+ end
71
+ nested[last_key] = value
72
+ option_values
73
+ end
74
+ end
75
+ end
76
+ end
data/lib/ecg/store.rb CHANGED
@@ -2,45 +2,49 @@
2
2
 
3
3
  module ECG
4
4
  class Store
5
- def initialize(hash)
6
- map = hash.map { |k, v| [k.to_sym, transform_value(v)] }
7
- @store = map.to_h
5
+ def initialize(values = {})
6
+ @values = values.map { |k, v| [k.to_sym, transform_value(v)] }.to_h
8
7
  end
9
8
 
10
9
  def initialize_copy(obj)
11
10
  super
12
- @store = @store.dup
13
- end
14
-
15
- def binding
16
- super # NOTE: Kernel.#binding is private
11
+ @values = @values.dup
17
12
  end
18
13
 
19
14
  def inspect
20
- detail = @store.map { |k, v| " @#{k}=#{v.inspect}" }.join(',')
15
+ detail = @values.map { |k, v| " @#{k}=#{v.inspect}" }.join(',')
21
16
  "#<#{self.class}:#{object_id << 1}#{detail}>"
22
17
  end
23
18
 
24
- def method_missing(name, *args)
25
- name = name.to_sym
19
+ def merge(*others)
20
+ others.inject(dup, &method(:deep_merge))
21
+ end
22
+
23
+ def merge!(*others)
24
+ others.inject(self, &method(:deep_merge))
25
+ end
26
26
 
27
- if @store.key?(name)
28
- transform_value(@store[name])
29
- elsif @store.respond_to?(name)
30
- @store.public_send(name, *args)
27
+ def method_missing(name, *args, &block)
28
+ if @values.key?(name)
29
+ @values[name]
30
+ elsif @values.respond_to?(name)
31
+ @values.public_send(name, *args, &block)
31
32
  else
32
33
  super
33
34
  end
34
35
  end
35
36
 
36
- def respond_to_missing?(symbol, include_private = false)
37
- symbol = symbol.to_sym
37
+ def respond_to_missing?(name, include_private = false)
38
+ @values.key?(name) || @values.respond_to?(name) || super
39
+ end
38
40
 
39
- @store.key?(symbol) || @store.respond_to?(symbol) || super
41
+ def store(key, value)
42
+ @values.store(key.to_sym, transform_value(value))
40
43
  end
44
+ alias []= store
41
45
 
42
46
  def to_h
43
- @store.transform_values(&method(:intransform_value))
47
+ @values.transform_values(&method(:intransform_value))
44
48
  end
45
49
 
46
50
  def to_json(*args)
@@ -56,6 +60,20 @@ module ECG
56
60
 
57
61
  private
58
62
 
63
+ def deep_merge(source, other)
64
+ other.keys.each do |key|
65
+ value = transform_value(other[key])
66
+
67
+ if source[key].is_a?(Store) && value.is_a?(Store)
68
+ source[key].merge!(value)
69
+ elsif !value.nil?
70
+ source[key] = value
71
+ end
72
+ end
73
+
74
+ source
75
+ end
76
+
59
77
  def intransform_value(value)
60
78
  case value
61
79
  when self.class
data/lib/ecg/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ECG
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - epaew
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-20 00:00:00.000000000 Z
11
+ date: 2019-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -138,6 +138,8 @@ files:
138
138
  - lib/ecg/command.rb
139
139
  - lib/ecg/context.rb
140
140
  - lib/ecg/option_parser.rb
141
+ - lib/ecg/plugin/load_file_values.rb
142
+ - lib/ecg/plugin/load_option_values.rb
141
143
  - lib/ecg/store.rb
142
144
  - lib/ecg/version.rb
143
145
  homepage: https://github.com/epaew/ecg
@@ -160,8 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
162
  - !ruby/object:Gem::Version
161
163
  version: '0'
162
164
  requirements: []
163
- rubyforge_project:
164
- rubygems_version: 2.7.6.2
165
+ rubygems_version: 3.0.3
165
166
  signing_key:
166
167
  specification_version: 4
167
168
  summary: ecg is an ERB(eRuby) based, simple and powerful configration file generator