ecg 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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