eac_cli 0.9.0 → 0.10.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: 7d373480947d50978fb88bb6e55a1296d644fcae95eb3b39a7a4127250cf14e2
4
- data.tar.gz: 350598588e6cb04bf5fca9cc626f8a1d662167dc5dee9fbc60bd231e2279aa0b
3
+ metadata.gz: 607e900ac4f5c7b6e7531c4ce9f9f6cfc8fd8ffce30f7b4d2a22ff91cdfd99bb
4
+ data.tar.gz: 0f49af96a89a527390ba34d2e6ecc17d2942aea7200bebbab1ba1f540398f413
5
5
  SHA512:
6
- metadata.gz: 074cf7d6fe38050884bb0578d1dcd6a7b02bb384d0634bfec15d8d53b03945dc789bf7654aba4a491145e4c9bb8a7366e1a2a799ce00709e0af2940ca97004ba
7
- data.tar.gz: 4da66cb205f1e58cb0c4fffb491d0d4e7aa38f7a6eeb7d7d67caa7c0f0e2177e2e98366639de29f9e588fc329c21051334d6696515e1c82711d1b2b8ec6e95b7
6
+ metadata.gz: 1e2a23e42fb5df31819b499f8dc1448f6ecc9d095fc22c9a0ceabe1acadf9ba6398a62d04a09943b233feeeb5e07325f62069ee883fb388b29424e7656ebe500
7
+ data.tar.gz: 1fefcf204e43d2ccfcf45c8507be13be885d46da72f0d6eda2ea3c3e6b7b845592e3ed989fc29b8c687a3727b795f8ea3a2d2a6526c3cda61bbf6b6f7da8df86
@@ -8,6 +8,10 @@ require 'eac_ruby_utils/core_ext'
8
8
  module EacCli
9
9
  class Definition
10
10
  require_sub __FILE__
11
+
12
+ SUBCOMMAND_NAME_ARG = 'subcommand'
13
+ SUBCOMMAND_ARGS_ARG = 'subcommand_args'
14
+
11
15
  attr_accessor :description
12
16
  attr_accessor :options_argument
13
17
 
@@ -50,15 +54,27 @@ module EacCli
50
54
  end
51
55
 
52
56
  def pos_arg(name, arg_options = {})
53
- positional << ::EacCli::Definition::PositionalArgument.new(name, arg_options)
57
+ raise 'Positional arguments are blocked' if positional_arguments_blocked?
58
+
59
+ pos_set << ::EacCli::Definition::PositionalArgument.new(name, arg_options)
54
60
  end
55
61
 
56
62
  def positional
57
- @positional ||= []
63
+ pos_set.to_a
64
+ end
65
+
66
+ def positional_arguments_blocked?
67
+ pos_set.any? { |e| e.optional? || e.repeat? }
58
68
  end
59
69
 
60
70
  def subcommands
61
- positional << ::EacCli::Definition::PositionalArgument.new('subcommand', subcommand: true)
71
+ pos_arg(SUBCOMMAND_NAME_ARG, subcommand: true)
72
+ pos_set << ::EacCli::Definition::PositionalArgument.new(SUBCOMMAND_ARGS_ARG,
73
+ optional: true, repeat: true)
74
+ end
75
+
76
+ def subcommands?
77
+ pos_set.any?(&:subcommand?)
62
78
  end
63
79
 
64
80
  def options_first(enable = true)
@@ -68,5 +84,11 @@ module EacCli
68
84
  def options_first?
69
85
  @options_first ? true : false
70
86
  end
87
+
88
+ private
89
+
90
+ def pos_set
91
+ @pos_set ||= []
92
+ end
71
93
  end
72
94
  end
@@ -5,19 +5,35 @@ require 'eac_ruby_utils/core_ext'
5
5
  module EacCli
6
6
  class Definition
7
7
  class BaseOption
8
+ DEFAULT_REQUIRED = false
9
+
10
+ enable_listable
11
+ lists.add_symbol :option, :optional, :usage, :required
8
12
  attr_reader :short, :long, :description, :options
9
13
 
10
14
  def initialize(short, long, description, options = {})
11
15
  @short = short
12
16
  @long = long
13
17
  @description = description
14
- @options = options.with_indifferent_access
18
+ @options = options.symbolize_keys
19
+ @options.assert_valid_keys(::EacCli::Definition::BaseOption.lists.option.values)
15
20
  end
16
21
 
17
22
  def identifier
18
23
  long.to_s.variableize.to_sym
19
24
  end
20
25
 
26
+ def required?
27
+ return true if options.key?(:required) && options.fetch(:required)
28
+ return false if options.key?(:optional) && options.fetch(:optional)
29
+
30
+ DEFAULT_REQUIRED
31
+ end
32
+
33
+ def to_s
34
+ "#{self.class.name.demodulize}[#{identifier}]"
35
+ end
36
+
21
37
  def show_on_usage?
22
38
  options[:usage]
23
39
  end
@@ -5,22 +5,26 @@ require 'eac_ruby_utils/core_ext'
5
5
  module EacCli
6
6
  class Definition
7
7
  class PositionalArgument
8
- common_constructor :name, :options, default: [{}]
8
+ enable_listable
9
+ lists.add_symbol :option, :optional, :repeat, :subcommand
10
+ common_constructor :name, :options, default: [{}] do
11
+ options.assert_valid_keys(self.class.lists.option.values)
12
+ end
9
13
 
10
14
  def identifier
11
15
  name.to_s.variableize.to_sym
12
16
  end
13
17
 
14
18
  def optional?
15
- options[:optional]
19
+ options[OPTION_OPTIONAL]
16
20
  end
17
21
 
18
22
  def repeat?
19
- options[:repeat]
23
+ options[OPTION_REPEAT]
20
24
  end
21
25
 
22
26
  def subcommand?
23
- options[:subcommand]
27
+ options[OPTION_SUBCOMMAND]
24
28
  end
25
29
  end
26
30
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'eac_cli/docopt/doc_builder'
4
+ require 'eac_cli/runner'
4
5
  require 'eac_ruby_utils/console/docopt_runner'
5
6
 
6
7
  module EacCli
@@ -31,6 +31,10 @@ module EacCli
31
31
  end
32
32
  end
33
33
 
34
+ def supplied?(option)
35
+ data[option].present?
36
+ end
37
+
34
38
  private
35
39
 
36
40
  def data
@@ -14,18 +14,40 @@ module EacCli
14
14
  common_constructor(:definition, :argv, :collector) { collect }
15
15
  attr_reader :arguments
16
16
 
17
+ def options_first?
18
+ definition.options_first? || definition.subcommands?
19
+ end
20
+
17
21
  private
18
22
 
23
+ def check_required_options
24
+ definition.options.each do |option|
25
+ next unless option.required?
26
+ next if collector.supplied?(option)
27
+
28
+ raise ::EacCli::Parser::Error.new(
29
+ definition, argv, "Option \"#{option}\" is required and a value was not supplied"
30
+ )
31
+ end
32
+ end
33
+
19
34
  def collect
20
35
  build_banner
21
36
  build_options
22
- @arguments = option_parser.parse(argv)
37
+ parse_argv
38
+ check_required_options
23
39
  end
24
40
 
25
41
  def option_parser_uncached
26
42
  ::OptionParser.new
27
43
  end
28
44
 
45
+ def parse_argv
46
+ @arguments = options_first? ? option_parser.order(argv) : option_parser.parse(argv)
47
+ rescue ::OptionParser::InvalidOption => e
48
+ raise ::EacCli::Parser::Error.new(definition, argv, e.message)
49
+ end
50
+
29
51
  def build_banner
30
52
  option_parser.banner = "#{definition.description}\n\n#{section('usage')}"
31
53
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'eac_cli/parser/error'
3
4
  require 'eac_ruby_utils/core_ext'
4
5
 
5
6
  module EacCli
@@ -8,6 +9,20 @@ module EacCli
8
9
  common_constructor :definition, :argv
9
10
 
10
11
  def result
12
+ result_or_error = parse(definition)
13
+ return result_or_error unless result_or_error.is_a?(::Exception)
14
+
15
+ definition.alternatives.each do |alternative|
16
+ alt_result_or_error = parse(alternative)
17
+ return alt_result_or_error unless alt_result_or_error.is_a?(::Exception)
18
+ end
19
+
20
+ raise result_or_error
21
+ end
22
+
23
+ private
24
+
25
+ def parse(definition)
11
26
  ::EacCli::Parser::Collector.to_data(definition) do |collector|
12
27
  ::EacCli::Parser::PositionalCollection.new(
13
28
  definition,
@@ -15,6 +30,8 @@ module EacCli
15
30
  collector
16
31
  )
17
32
  end
33
+ rescue ::EacCli::Parser::Error => e
34
+ e
18
35
  end
19
36
  end
20
37
  end
@@ -10,39 +10,67 @@ module EacCli
10
10
 
11
11
  private
12
12
 
13
+ def argv_enum
14
+ @argv_enum ||= argv.each
15
+ end
16
+
17
+ def argv_pending?
18
+ argv_enum.ongoing?
19
+ end
20
+
21
+ def argv_pending_check
22
+ return unless argv_pending?
23
+ return unless positional_enum.stopped?
24
+
25
+ raise ::EacCli::Parser::Error.new(
26
+ definition, argv, "No positional left for argv \"#{argv_enum.current}\""
27
+ )
28
+ end
29
+
13
30
  def collected
14
31
  @collected ||= ::Set.new
15
32
  end
16
33
 
17
34
  def collect
18
- argv.each { |argv_value| colect_argv_value(argv_value) }
19
- return unless pending_required_positional?
35
+ loop do
36
+ break unless enums('pending?').any?
20
37
 
21
- raise ::EacCli::Parser::Error.new(
22
- definition, argv, 'No value for required positional ' \
23
- "\"#{current_positional.identifier}\""
24
- )
38
+ enums('pending_check')
39
+ collect_argv_value
40
+ end
41
+ end
42
+
43
+ def collect_argv_value
44
+ collector.collect(*enums('enum', &:peek))
45
+ collected << positional_enum.peek
46
+ positional_enum.next unless positional_enum.peek.repeat?
47
+ argv_enum.next
25
48
  end
26
49
 
27
- def colect_argv_value(argv_value)
28
- collector.collect(current_positional, argv_value)
29
- collected << current_positional
30
- positional_enumerator.next unless current_positional.repeat?
50
+ def enums(method_suffix, &block)
51
+ %w[positional argv].map do |method_prefix|
52
+ v = send("#{method_prefix}_#{method_suffix}")
53
+ block ? block.call(v) : v
54
+ end
31
55
  end
32
56
 
33
- def pending_required_positional?
34
- !(current_positional.blank? || current_positional.optional? ||
35
- collected.include?(current_positional))
57
+ def positional_enum
58
+ @positional_enum ||= definition.positional.each
36
59
  end
37
60
 
38
- def positional_enumerator
39
- @positional_enumerator ||= definition.positional.each
61
+ def positional_pending?
62
+ !(positional_enum.stopped? || positional_enum.current.optional? ||
63
+ collected.include?(positional_enum.current))
40
64
  end
41
65
 
42
- def current_positional
43
- positional_enumerator.peek
44
- rescue ::StopIteration
45
- nil
66
+ def positional_pending_check
67
+ return unless positional_pending?
68
+ return unless argv_enum.stopped?
69
+
70
+ raise ::EacCli::Parser::Error.new(
71
+ definition, argv, 'No value for required positional ' \
72
+ "\"#{positional_enum.current.identifier}\""
73
+ )
46
74
  end
47
75
  end
48
76
  end
@@ -5,13 +5,14 @@ require 'eac_cli/runner'
5
5
  require 'eac_cli/runner_with'
6
6
 
7
7
  class Object
8
- def runner_with(*runners)
8
+ def runner_with(*runners, &block)
9
9
  include ::EacCli::Runner
10
10
  enable_simple_cache
11
11
  enable_console_speaker
12
12
  runners.each do |runner|
13
13
  include runner_with_to_module(runner)
14
14
  end
15
+ runner_definition(&block) if block
15
16
  end
16
17
 
17
18
  private
@@ -18,6 +18,10 @@ module EacCli
18
18
  end
19
19
  end
20
20
 
21
+ def runner?(object)
22
+ object.is_a?(::Class) && object.included_modules.include?(::EacCli::Runner)
23
+ end
24
+
21
25
  private
22
26
 
23
27
  def alias_class_method(klass, from, to)
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'eac_cli/runner'
4
+ require 'eac_ruby_utils/core_ext'
5
+
6
+ module EacCli
7
+ module RunnerWith
8
+ module Subcommands
9
+ common_concern do
10
+ include ::EacCli::Runner
11
+ end
12
+
13
+ EXTRA_AVAILABLE_SUBCOMMANDS_METHOD_NAME = :extra_available_subcommands
14
+
15
+ def available_subcommands
16
+ @available_subcommands ||= available_subcommands_auto.merge(available_subcommands_extra)
17
+ end
18
+
19
+ def available_subcommands_auto
20
+ self.class.constants
21
+ .map { |name| [name.to_s.underscore.gsub('_', '-'), self.class.const_get(name)] }
22
+ .select { |c| ::EacCli::Runner.runner?(c[1]) }
23
+ .to_h.with_indifferent_access
24
+ end
25
+
26
+ def available_subcommands_extra
27
+ if respond_to?(EXTRA_AVAILABLE_SUBCOMMANDS_METHOD_NAME, true)
28
+ send(EXTRA_AVAILABLE_SUBCOMMANDS_METHOD_NAME)
29
+ else
30
+ {}
31
+ end
32
+ end
33
+
34
+ def method_missing(method_name, *arguments, &block)
35
+ return run_with_subcommand(*arguments, &block) if
36
+ run_with_subcommand_alias_run?(method_name)
37
+
38
+ super
39
+ end
40
+
41
+ def respond_to_missing?(method_name, include_private = false)
42
+ run_with_subcommand_alias_run?(method_name) || super
43
+ end
44
+
45
+ def run_with_subcommand
46
+ if subcommand_name
47
+ subcommand_runner.run
48
+ else
49
+ run_without_subcommand
50
+ end
51
+ end
52
+
53
+ def run_with_subcommand_alias_run?(method_name)
54
+ subcommands? && method_name.to_sym == :run
55
+ end
56
+
57
+ def run_without_subcommand
58
+ "Method #{__method__} should be overrided in #{self.class.name}"
59
+ end
60
+
61
+ def subcommands?
62
+ self.class.runner_definition.subcommands?
63
+ end
64
+
65
+ def subcommand_args
66
+ parsed.fetch(::EacCli::Definition::SUBCOMMAND_ARGS_ARG)
67
+ end
68
+
69
+ def subcommand_class
70
+ available_subcommands[subcommand_name].if_present { |v| return v }
71
+
72
+ raise(::EacCli::Parser::Error.new(
73
+ self.class.runner_definition, runner_context.argv,
74
+ "Subcommand \"#{subcommand_name}\" not found " \
75
+ "(Available: #{available_subcommands.keys}"
76
+ ))
77
+ end
78
+
79
+ def subcommand_name
80
+ parsed.fetch(::EacCli::Definition::SUBCOMMAND_NAME_ARG)
81
+ end
82
+
83
+ def subcommand_program
84
+ subcommand_name
85
+ end
86
+
87
+ def subcommand_runner
88
+ @subcommand_runner ||= subcommand_class.create(
89
+ argv: subcommand_args,
90
+ program_name: subcommand_program,
91
+ parent: self
92
+ )
93
+ end
94
+ end
95
+ end
96
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EacCli
4
- VERSION = '0.9.0'
4
+ VERSION = '0.10.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eac_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esquilo Azul Company
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-14 00:00:00.000000000 Z
11
+ date: 2020-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eac_ruby_utils
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.45'
19
+ version: '0.50'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.45'
26
+ version: '0.50'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: eac_ruby_gem_support
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -75,6 +75,7 @@ files:
75
75
  - lib/eac_cli/runner_with.rb
76
76
  - lib/eac_cli/runner_with/help.rb
77
77
  - lib/eac_cli/runner_with/output_file.rb
78
+ - lib/eac_cli/runner_with/subcommands.rb
78
79
  - lib/eac_cli/version.rb
79
80
  homepage:
80
81
  licenses: []