ultra_command_line 0.4.7

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b7cd80a8c2740619c3fbba59d3d76fee87f6763c
4
+ data.tar.gz: ab0a550b07ac26bfeae400c9d0a0002c563efe75
5
+ SHA512:
6
+ metadata.gz: c8a55f46171afd5a91ea53301915efb2f5f92f61cc87f91f2c84b7638ffebc5788b49a621c8023282549552ffc146b39f1c0c3d2d2e89f75b88f14682cbc0ff3
7
+ data.tar.gz: 979c60dffeb59988e17a490064352ee18601fc14a02d9fc4674362077bab055cef6433b760c51268faf0b131763d2454cc9648e90bf125f85b71ca890224ba48
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+
14
+ /.idea/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.15.4
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://gems.nanonet'
2
+
3
+ # Specify your gem's dependencies in ultra_command_line.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ UltraCommandLine
2
+ ================
3
+
4
+ This gem is basically a wrapper around the `slop` gem which generates slop options from a YAML definition file.
5
+ This is most probably only used by the `climatic` gem
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'ultra_command_line'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install ultra_command_line
23
+
24
+ ## Usage
25
+
26
+ TODO: Write usage instructions here
27
+
28
+ ## Development
29
+
30
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
31
+
32
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
33
+
34
+ ## Contributing
35
+
36
+ Bug reports and pull requests are welcome on GitHub at https://gitlab.com/lbriais/ultra_command_line.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'ultra_command_line'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,160 @@
1
+ @startuml
2
+
3
+
4
+ skinparam class {
5
+ backgroundColor<<module>> LightGray
6
+ backgroundColor<<external>> Gray
7
+ }
8
+
9
+ set namespaceSeparator ::
10
+
11
+
12
+ namespace Commands {
13
+
14
+ class OptionDefinition {
15
+ Defines one command line option,
16
+ wrapping around slop features.
17
+ +{method}:to_slop_options
18
+ }
19
+
20
+ class SubCommand {
21
+ Represents a a-la-git sub-command
22
+ ..
23
+ +{field}name
24
+ }
25
+
26
+ class MainCommand << (M, #ADD1B2) module>> {
27
+ Represents the main command.
28
+ Basically a Subcommand but
29
+ with an empty name.
30
+ ..
31
+ }
32
+
33
+ class Factory << (M, #ADD1B2) module>> {
34
+ How to create a SubCommand from
35
+ a definition hash.
36
+ ..
37
+ +{method}:from_hash
38
+ }
39
+
40
+ class HelpFormatter << (M, #ADD1B2) module>> {
41
+ Builds help for a command.
42
+ ..
43
+ +{field}help_template
44
+ +{method}:help
45
+ }
46
+
47
+ class Validation << (M, #ADD1B2) module>> {
48
+ Checks the validity of options
49
+ defined.
50
+ ..
51
+ +{method}:valid?
52
+ }
53
+
54
+
55
+ class CommandLineParser <<(M, #ADD1B2) module>> {
56
+ Parses command-line
57
+ ..
58
+ +{field}cmd_line_args
59
+ +{method}:extra_arguments
60
+ +{method}:params_hash
61
+ }
62
+
63
+
64
+ SubCommand "1" *-- "n" OptionDefinition : "contains\nand\ncreates"
65
+ SubCommand -|> Factory : extends
66
+ SubCommand --|> HelpFormatter : includes
67
+ SubCommand -|> Validation : includes
68
+ SubCommand --|> CommandLineParser : includes
69
+
70
+ MainCommand ..|> SubCommand : "included\nfor main command\nat runtime"
71
+
72
+ }
73
+
74
+
75
+ namespace Manager {
76
+
77
+
78
+ class Base {
79
+ Manages completely the list of
80
+ options definition and how to find
81
+ a processor for a list of command
82
+ line parameters
83
+ }
84
+
85
+ class LayeredDefinition <<(M, #ADD1B2) module>> {
86
+ Wrapper around SuperStack::Manager
87
+ to manage "contributions" to the definition
88
+ with cache management.
89
+ ..
90
+ +{method}:definition_hash
91
+ +{method}:contribute_to_definition
92
+ +{method}:[]=
93
+ }
94
+
95
+ class ProcessorMatcher <<(M, #ADD1B2) module>> {
96
+ Determines a suitable list of potential
97
+ processors from the command-line
98
+ parameters
99
+ ..
100
+ +{method}:candidates
101
+ }
102
+
103
+ class Commands <<(M, #ADD1B2) module>> {
104
+ Manages a main command
105
+ and optionally sub commands.
106
+ ..
107
+ +{field}commands
108
+ }
109
+
110
+ class Factory <<(M, #ADD1B2) module>> {
111
+ How to create a Manager from
112
+ a definition hash.
113
+ ..
114
+ +{method}:from_hash
115
+ }
116
+
117
+ Base -|> ProcessorMatcher : includes
118
+ Base ---|> LayeredDefinition : includes
119
+ Base --|> Commands : includes
120
+ Base --|> Factory : extends
121
+ }
122
+
123
+ namespace Processors {
124
+ class Base
125
+
126
+ class Registration<<(M, #ADD1B2) module>>
127
+
128
+ Base -|> Registration : includes
129
+
130
+ }
131
+
132
+
133
+ namespace Utils {
134
+ class BasicLogger<<(M, #ADD1B2) module>>
135
+ class ErrorPropagation<<(M, #ADD1B2) module>>
136
+ class YamlFactory<<(M, #ADD1B2) module>> {
137
+ If your module or class implements
138
+ #from_hash, then including this module
139
+ will bring the ability to load it from
140
+ a yaml string or a yaml file.
141
+ ..
142
+ +{method}:from_yaml
143
+ +{method}:from_yaml_file
144
+ }
145
+ }
146
+
147
+ Manager::Commands "1" *---- "n" Commands::SubCommand : "contains\nand\ncreates"
148
+ Manager::ProcessorMatcher "0" *- "n" Processors::Base
149
+
150
+ Manager::Factory -|> Utils::YamlFactory : includes
151
+ Commands::Factory -|> Utils::YamlFactory : includes
152
+
153
+ Manager::Base -|> Utils::ErrorPropagation : includes
154
+ Commands::SubCommand --|> Utils::ErrorPropagation : includes
155
+
156
+
157
+
158
+
159
+
160
+ @enduml
@@ -0,0 +1,23 @@
1
+ require 'slop'
2
+
3
+ require 'ultra_command_line/version'
4
+ require 'ultra_command_line/error'
5
+ require 'ultra_command_line/utils/basic_logger'
6
+ require 'ultra_command_line/utils/error_propagation'
7
+ require 'ultra_command_line/utils/yaml_factory'
8
+
9
+
10
+ module UltraCommandLine
11
+
12
+ extend UltraCommandLine::Utils::BasicLogger
13
+
14
+ def self.new_slop_options
15
+ Slop::Options.new underscore_flags: false
16
+ end
17
+
18
+ end
19
+
20
+
21
+ require 'ultra_command_line/commands/sub_command'
22
+ require 'ultra_command_line/manager/base'
23
+
@@ -0,0 +1,34 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ module CommandLineParser
5
+
6
+ def params_hash
7
+ parse_cmd_line_options.to_hash
8
+ end
9
+
10
+ def extra_arguments
11
+ parse_cmd_line_options.to_hash
12
+ @extra_arguments ||= []
13
+ end
14
+
15
+ private
16
+
17
+ def parse_cmd_line_options(options_definition = UltraCommandLine.new_slop_options)
18
+ parser = build_parser(options_definition)
19
+ hash = parser.parse cmd_line_args
20
+ # @cmd_line_args = cmd_line_args
21
+ @extra_arguments = parser.arguments
22
+ hash
23
+ end
24
+
25
+ def build_parser(options_definition)
26
+ options_definition.banner = banner
27
+ options.each { |option| option.to_slop_options options_definition }
28
+ Slop::Parser.new options_definition
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ module Factory
5
+
6
+ DEFAULT_BANNER = ''.freeze
7
+
8
+ include UltraCommandLine::Utils::YamlFactory
9
+
10
+ def from_hash(definition_hash, factory_options = {})
11
+ name = factory_options.fetch :name, ''
12
+ options_definition_hash = definition_hash.fetch(:options, {})
13
+ manager = factory_options[:manager]
14
+ banner = definition_hash.fetch :banner, DEFAULT_BANNER
15
+ create_command name, options_definition_hash, banner, manager
16
+ end
17
+
18
+ private
19
+
20
+ def create_command(name, options_definition_hash, banner, manager)
21
+ options = options_definition_hash.map do |option_name, option_definition_hash|
22
+ option_type = option_definition_hash[:type].to_sym
23
+ UltraCommandLine::Commands::OptionDefinition.new option_name, option_type, option_definition_hash
24
+ end
25
+ new manager, name, banner, options: options
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ module HelpFormatter
5
+
6
+ DEFAULT_SEPARATOR_WIDTH = 80
7
+ DEFAULT_SEPARATOR_FILLER = '-'.freeze
8
+ DEFAULT_TITLE = 'Options'.freeze
9
+
10
+ attr_writer :separator_width, :separator_filler
11
+
12
+ def separator_width
13
+ @separator_width ||= DEFAULT_SEPARATOR_WIDTH
14
+ end
15
+
16
+ def title
17
+ name.nil? || name.empty? ? DEFAULT_TITLE : name
18
+ end
19
+
20
+ def separator_filler
21
+ @separator_filler ||= DEFAULT_SEPARATOR_FILLER
22
+ end
23
+
24
+ def help
25
+ output = []
26
+ output << banner
27
+ unless options.empty?
28
+ output << build_separator(title)
29
+ output.concat options.map(&:help_line)
30
+ end
31
+ output
32
+ end
33
+
34
+ private
35
+
36
+ def build_separator(title)
37
+ "#{separator_filler * 2} #{title} ".ljust separator_width, separator_filler
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,10 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ module MainCommand
5
+
6
+
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,68 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ class OptionDefinition
5
+
6
+ DEFAULT_SUMMARY = 'Option summary not provided.'.freeze
7
+ attr_reader :name, :type
8
+ attr_reader :short_aliases, :long_aliases
9
+ attr_accessor :summary, :default, :description, :help, :dependencies, :incompatibilities
10
+
11
+ def initialize(name, type, options = {})
12
+ @name = name
13
+ @type = type
14
+ @summary = options.fetch(:summary, DEFAULT_SUMMARY)
15
+ @default = options.fetch(:default, nil)
16
+ @description = options.fetch(:description, summary)
17
+ @help = options.fetch(:help, description)
18
+ @global = options.fetch(:global, false)
19
+ @dependencies = options.fetch(:dependencies, [])
20
+ self.dependencies = dependencies.is_a?(Array) ? dependencies : [dependencies]
21
+ @incompatibilities = options.fetch(:incompatibilities, [])
22
+ self.incompatibilities = incompatibilities.is_a?(Array) ? incompatibilities : [incompatibilities]
23
+ short_aliases = options.fetch(:short_aliases, [])
24
+ long_aliases = options.fetch(:long_aliases, [])
25
+ self.short_aliases = (short_aliases.is_a?(Array) ? short_aliases : [short_aliases]).map &:to_sym
26
+ self.long_aliases = (long_aliases.is_a?(Array) ? long_aliases : [long_aliases]).map &:to_sym
27
+ end
28
+
29
+ def short_aliases=(options)
30
+ options.each do |option|
31
+ raise UltraCommandLine::Error, "Invalid short option defined for slop option '#{option}' !" unless option.to_s.size == 1
32
+ end
33
+ @short_aliases = options
34
+ end
35
+
36
+ def long_aliases=(options)
37
+ if options.include? name
38
+ raise UltraCommandLine::Error, "Inconsistent long options for slop option '#{name}' already defined as main option !"
39
+ end
40
+ @long_aliases = options
41
+ # long_aliases.unshift name
42
+ end
43
+
44
+ def global?
45
+ @global
46
+ end
47
+
48
+ def to_slop_options(slop_options = UltraCommandLine.new_slop_options)
49
+ one_letter_options = short_aliases.map.each { |option| "-#{option}" }
50
+ word_options = long_aliases.map.each { |option| "--#{option}" }
51
+ # The main option as last entry before description
52
+ option_def = word_options.concat(one_letter_options) << "--#{name}" << summary
53
+ option_def.concat [{ default: default }] unless default.nil?
54
+ slop_options.send type, *option_def
55
+ slop_options.banner = nil
56
+ slop_options
57
+ rescue NoMethodError => e
58
+ raise UltraCommandLine::Error, "Invalid option type '#{e.name}' for option '#{name}' !"
59
+ end
60
+
61
+ def help_line(slop_options = UltraCommandLine.new_slop_options)
62
+ to_slop_options(slop_options).to_s.gsub /\n/, ''
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,58 @@
1
+ require 'ultra_command_line/commands/validation'
2
+ require 'ultra_command_line/commands/command_line_parser'
3
+ require 'ultra_command_line/commands/main_command'
4
+ require 'ultra_command_line/commands/option_definition'
5
+ require 'ultra_command_line/commands/help_formatter'
6
+ require 'ultra_command_line/commands/factory'
7
+
8
+ module UltraCommandLine
9
+ module Commands
10
+
11
+ class SubCommand
12
+
13
+ include UltraCommandLine::Utils::ErrorPropagation
14
+
15
+ extend UltraCommandLine::Commands::Factory
16
+
17
+ include UltraCommandLine::Commands::Validation
18
+ include UltraCommandLine::Commands::HelpFormatter
19
+ include UltraCommandLine::Commands::CommandLineParser
20
+
21
+
22
+ attr_reader :name, :manager, :options, :aliases
23
+ attr_accessor :banner
24
+
25
+ def initialize(manager,
26
+ name = '',
27
+ banner = UltraCommandLine::Commands::Factory::DEFAULT_BANNER,
28
+ options: [])
29
+ @manager = manager
30
+ @name = name
31
+ if name.empty?
32
+ class << self; include UltraCommandLine::Commands::MainCommand; end
33
+ end
34
+ self.banner = banner
35
+ @options = options
36
+ @aliases = [self.name]
37
+ end
38
+
39
+ def root_command?
40
+ singleton_class.ancestors.include? UltraCommandLine::Commands::MainCommand
41
+ end
42
+
43
+ def cmd_line_args
44
+ self_view_of_manager_cmd_line_args
45
+ end
46
+
47
+ private
48
+
49
+ attr_writer :manager
50
+
51
+ def self_view_of_manager_cmd_line_args
52
+ manager.cmd_line_args_for_command self
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,80 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ module Validation
5
+
6
+
7
+ def valid?(raise_error: false)
8
+ # return false_or_raise 'You have to successfully parse cmd line parameters before checking its validity !', raise_error: raise_error if params_hash.nil?
9
+ begin
10
+ raise if params_hash.nil?
11
+ rescue
12
+ return false_or_raise 'You have to successfully parse cmd line parameters before checking its validity !', raise_error: raise_error
13
+ end
14
+ return false unless check_options_dependencies raise_error: raise_error
15
+ return false unless check_options_incompatibilities raise_error: raise_error
16
+ if name.empty?
17
+ unless root_command?
18
+ return false_or_raise 'Only a root command should not have a name !', raise_error: raise_error
19
+ end
20
+ end
21
+ true
22
+ end
23
+
24
+ private
25
+
26
+ def check_options_incompatibilities(raise_error: false)
27
+ options.each do |option|
28
+ if option_eligible_for_incompatibility_test? option
29
+ option.incompatibilities.each do |incompatibility|
30
+ unless params_hash[incompatibility.to_sym].nil? or params_hash[incompatibility.to_sym] == false
31
+ return false_or_raise "Command line option '#{option.name}' is incompatible with option '#{incompatibility}' !",
32
+ raise_error: raise_error,
33
+ error_type: UltraCommandLine::OptionDependencyError
34
+ end
35
+ end
36
+ end
37
+ end
38
+ true
39
+ end
40
+
41
+ def option_eligible_for_incompatibility_test?(option)
42
+ return false if option.incompatibilities.empty?
43
+ keys_str = params_hash.keys.map &:to_s
44
+ if keys_str.include? option.name
45
+ not (params_hash[option.name.to_sym].nil? or params_hash[option.name.to_sym] == false)
46
+ else
47
+ false
48
+ end
49
+ end
50
+
51
+
52
+ def check_options_dependencies(raise_error: false)
53
+ options.each do |option|
54
+ if option_eligible_for_dependency_test? option
55
+ option.dependencies.each do |dependency|
56
+ if params_hash[dependency.to_sym].nil? or params_hash[dependency.to_sym] == false
57
+ return false_or_raise "Command line option '#{option.name}' requires option '#{dependency}' !",
58
+ raise_error: raise_error,
59
+ error_type: UltraCommandLine::OptionDependencyError
60
+ end
61
+ end
62
+ end
63
+ end
64
+ true
65
+ end
66
+
67
+ def option_eligible_for_dependency_test?(option)
68
+ return false if option.dependencies.empty?
69
+ keys_str = params_hash.keys.map &:to_s
70
+ if keys_str.include? option.name
71
+ not (params_hash[option.name.to_sym].nil? or params_hash[option.name.to_sym] == false)
72
+ else
73
+ false
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,7 @@
1
+ module UltraCommandLine
2
+
3
+ class Error < StandardError; end
4
+
5
+ class OptionDependencyError < Error; end
6
+
7
+ end
@@ -0,0 +1,35 @@
1
+ require 'ultra_command_line/manager/factory'
2
+ require 'ultra_command_line/manager/layered_definition'
3
+ require 'ultra_command_line/manager/cmd_line_args'
4
+ require 'ultra_command_line/manager/commands'
5
+ require 'ultra_command_line/manager/processors'
6
+
7
+ module UltraCommandLine
8
+ module Manager
9
+
10
+ class Base
11
+
12
+ include UltraCommandLine::Utils::ErrorPropagation
13
+
14
+ extend UltraCommandLine::Manager::Factory
15
+
16
+ include UltraCommandLine::Manager::LayeredDefinition
17
+ include UltraCommandLine::Manager::Commands
18
+
19
+ include UltraCommandLine::Manager::Processors
20
+
21
+ def initialize(commands = [])
22
+ @commands = commands
23
+ end
24
+
25
+ def definition_hash_to_commands
26
+ self.class.from_hash(definition_hash) do |commands|
27
+ commands.each {|command| command.send :manager=, self }
28
+ @commands = commands
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+
2
+ module UltraCommandLine
3
+ module Manager
4
+
5
+ module CmdLineArgs
6
+
7
+ def cmd_line_args
8
+ @cmd_line_args ||= []
9
+ end
10
+
11
+ def cmd_line_args=(cmd_line_args)
12
+ cmd_line_args = case cmd_line_args
13
+ when Array
14
+ cmd_line_args
15
+ when String
16
+ cmd_line_args.split ' '
17
+ end
18
+ UltraCommandLine.logger.debug "Cmd line: #{cmd_line_args.inspect}"
19
+ @cmd_line_args = cmd_line_args
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,67 @@
1
+ module UltraCommandLine
2
+ module Manager
3
+
4
+ module Commands
5
+
6
+ include UltraCommandLine::Manager::CmdLineArgs
7
+
8
+ def commands
9
+ @commands ||= []
10
+ end
11
+
12
+ def root_command
13
+ return nil if commands.empty?
14
+ l = commands.select {|c| c.root_command? && c.name.empty? }
15
+ raise UltraCommandLine::Error, 'Wrong commands definition !' unless l.count == 1
16
+ l.first
17
+ end
18
+
19
+ def aliases_consistent?
20
+ # including aliases
21
+ full_names_list = []
22
+ commands.each do |command|
23
+ if command.root_command?
24
+ return false unless command.name.empty? && command.aliases == [command.name]
25
+ else
26
+ command.aliases.each do |alias_name|
27
+ return false if full_names_list.include? alias_name.to_s
28
+ full_names_list << alias_name.to_s
29
+ end
30
+
31
+ end
32
+ end
33
+ true
34
+ end
35
+
36
+ def command(cmd_line_args = self.cmd_line_args)
37
+ command_alias = cmd_line_args.empty? ? '' : cmd_line_args.first
38
+ command_by_alias(command_alias) || root_command
39
+ end
40
+
41
+ def command_by_alias(command_alias)
42
+ raise UltraCommandLine::Error, 'command names/aliases are not consistent !' unless aliases_consistent?
43
+ candidates = commands.select do |command|
44
+ command.aliases.map(&:to_s).include? command_alias.to_s
45
+ end
46
+ # raise UltraCommandLine::Error, "There is no command named '#{command_alias}'!" unless candidates.siez == 1
47
+ candidates.size == 1 ? candidates.first : nil
48
+ end
49
+
50
+ def cmd_line_args_for_command(command)
51
+ if command.root_command?
52
+ cmd_line_args
53
+ else
54
+ clfc = cmd_line_args.dup
55
+ clfc.shift
56
+ clfc
57
+ end
58
+ end
59
+
60
+ protected
61
+
62
+ attr_writer :commands
63
+
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,50 @@
1
+ module UltraCommandLine
2
+ module Manager
3
+
4
+ module Factory
5
+
6
+ include UltraCommandLine::Utils::YamlFactory
7
+
8
+ def from_hash(definition_hash, factory_options = {})
9
+ UltraCommandLine.logger.debug 'Starting commands analysis from definition hash.'
10
+ commands = []
11
+ # Create main command
12
+ UltraCommandLine.logger.debug 'Defining main command.'
13
+ main_command = create_command '', definition_hash
14
+ main_command.banner = definition_hash.fetch :banner, ''
15
+ commands << main_command
16
+ # Create sub-commands
17
+ definition_hash.fetch(:subcommands, {}).each do |subcommand_name, subcommand_definition|
18
+ UltraCommandLine.logger.debug "Defining sub-command '#{subcommand_name}'."
19
+ # subcommand_definition ||= {}
20
+ subcommand = create_command subcommand_name, subcommand_definition
21
+ subcommand.banner = subcommand_definition.fetch :banner, ''
22
+ # Let's add global options
23
+ main_command.options.select(&:global?).each do |global_option|
24
+ subcommand.options << global_option
25
+ end
26
+ commands << subcommand
27
+ end
28
+ if block_given?
29
+ yield commands
30
+ else
31
+ # Return a new object supporting a list of commands as parameter
32
+ new_manager = new commands
33
+ new_manager.initialize_definition definition_hash
34
+ commands.each {|c| c.send :manager=, new_manager}
35
+ new_manager
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def create_command(name, options_definition)
42
+ UltraCommandLine::Commands::SubCommand.from_hash options_definition, name: name, manager: self
43
+ end
44
+
45
+
46
+
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,86 @@
1
+ require 'super_stack'
2
+
3
+ module UltraCommandLine
4
+ module Manager
5
+
6
+ module LayeredDefinition
7
+
8
+ def definition_hash
9
+ @cached_definition ||= layers_manager[].to_hash
10
+ end
11
+
12
+ def initialize_definition(hash)
13
+ reset_cached_configuration
14
+ @layers_manager = setup_layers_manager hash
15
+ self
16
+ end
17
+
18
+ def contribute_to_definition(hash, layer_name: :unknown_layer)
19
+ reset_cached_configuration
20
+ layers_manager
21
+ new_layer = SuperStack::LayerWrapper.from_hash hash
22
+ new_layer.name = layer_name
23
+ new_layer.priority = definition_counter
24
+ layers_manager.add_layer new_layer
25
+ end
26
+
27
+ def []=(key, value)
28
+ layers_manager[key] = value
29
+ end
30
+
31
+ def refresh
32
+ reset_cached_configuration
33
+ end
34
+
35
+ def clear
36
+ reset_definition_hash
37
+ end
38
+
39
+ private
40
+
41
+ BASE_LAYER_PRIORITY = 10
42
+ DEFINITION_LAYERS_BASE_PRIORITY = 100
43
+ UPDATE_LAYER_PRIORITY = 1000
44
+
45
+ def reset_cached_configuration
46
+ @cached_definition = nil
47
+ end
48
+
49
+ def reset_definition_hash
50
+ reset_cached_configuration
51
+ @layers_manager = nil
52
+ end
53
+
54
+ def definition_counter
55
+ raise UltraCommandLine::Error, 'Maximum number of definition reached !' if @definition_counter+1 >= UPDATE_LAYER_PRIORITY
56
+ @definition_counter += 1
57
+ end
58
+
59
+ def layers_manager
60
+ @layers_manager ||= setup_layers_manager
61
+ end
62
+
63
+ def setup_layers_manager(hash = nil)
64
+ merger = SuperStack::Manager.new
65
+ merger.merge_policy = SuperStack::MergePolicies::FullMergePolicy
66
+ base_layer = if hash.nil?
67
+ SuperStack::Layer.new
68
+ else
69
+ SuperStack::LayerWrapper.from_hash(hash)
70
+ end
71
+ base_layer.name = :base
72
+ base_layer.priority = BASE_LAYER_PRIORITY
73
+ merger.add_layer base_layer
74
+ update_layer = SuperStack::Layer.new
75
+ update_layer.name = :manual_updates
76
+ update_layer.priority = UPDATE_LAYER_PRIORITY
77
+ merger.add_layer update_layer
78
+ merger.write_layer = update_layer
79
+ @definition_counter = DEFINITION_LAYERS_BASE_PRIORITY
80
+ merger
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,75 @@
1
+ module UltraCommandLine
2
+ module Manager
3
+
4
+ module Processors
5
+
6
+ include UltraCommandLine::Manager::Commands
7
+
8
+ MANDATORY_PROCESSOR_METHODS = %i[check_params execute].freeze
9
+
10
+ def processors(for_command: nil)
11
+ if for_command.nil?
12
+ processors_hash.values.inject([]) do |res, value|
13
+ res.concat value
14
+ res
15
+ end .uniq
16
+ else
17
+ processors_hash[for_command]
18
+ end
19
+ end
20
+
21
+ def register_processor(command_name_or_command, processor)
22
+ verify_processor_implementation processor
23
+ if processors.include? processor
24
+ msg = 'Trying to insert a processor already registered: %s' % [processor.inspect]
25
+ UltraCommandLine.logger.debug msg
26
+ end
27
+ command = command_name_or_command_to_command command_name_or_command
28
+ processors_hash[command.name] ||= []
29
+ processors_hash[command.name] << processor
30
+ command_name = command.name.empty? ? 'Root' : "#{command.name}"
31
+ UltraCommandLine.logger.debug "Registered Processor #{processor.inspect} to '#{command_name}' command"
32
+ end
33
+
34
+ def clear_processors
35
+ @processors_hash = nil
36
+ end
37
+
38
+ def processor
39
+ processor_candidates = processors_hash.fetch(command(cmd_line_args).name, []) .select do |processor|
40
+ processor.check_params command.cmd_line_args
41
+ end
42
+ raise UltraCommandLine::Error, 'No processor found for this command line' if processor_candidates.empty?
43
+ raise UltraCommandLine::Error, 'Too many possible processors' unless processor_candidates.size == 1
44
+ processor_candidates.first
45
+ end
46
+
47
+ private
48
+
49
+ def command_name_or_command_to_command(command_name_or_command)
50
+ case command_name_or_command
51
+ when String
52
+ command_by_alias command_name_or_command
53
+ when UltraCommandLine::Commands::SubCommand
54
+ command_name_or_command
55
+ else
56
+ raise UltraCommandLine::Error, "Invalid command or command name: '#{command_name_or_command.inspect}'"
57
+ end
58
+ end
59
+
60
+ def verify_processor_implementation(processor)
61
+ MANDATORY_PROCESSOR_METHODS.each do |method_name|
62
+ unless processor.respond_to? method_name
63
+ raise UltraCommandLine::Error, "Invalid processor '#{processor.inspect}' missing method '#{method_name}'!"
64
+ end
65
+ end
66
+ end
67
+
68
+ def processors_hash
69
+ @processors_hash ||= {}
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,23 @@
1
+ module UltraCommandLine
2
+ module Utils
3
+
4
+ module BasicLogger
5
+
6
+ class NullLogger
7
+ def method_missing(*args)
8
+ # Do nothing
9
+ end
10
+ end
11
+
12
+ def logger=(logger)
13
+ @logger = logger
14
+ end
15
+
16
+ def logger
17
+ @logger ||= NullLogger.new
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module UltraCommandLine
2
+ module Utils
3
+
4
+ module ErrorPropagation
5
+
6
+ DEFAULT_ERROR_MESSAGE = 'Error message not provided!'.freeze
7
+
8
+ private
9
+
10
+ def false_or_raise(message = DEFAULT_ERROR_MESSAGE, raise_error: false, error_type: UltraCommandLine::Error)
11
+ if raise_error
12
+ raise error_type, message
13
+ else
14
+ UltraCommandLine.logger.warn message
15
+ end
16
+ false
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ module UltraCommandLine
2
+ module Utils
3
+
4
+ module YamlFactory
5
+
6
+ def from_yaml_file(yaml_file, factory_options = {}, &block)
7
+ yaml_file = File.expand_path yaml_file
8
+ UltraCommandLine.logger.debug "Loading commands definitions from '#{yaml_file}'."
9
+ raise UltraCommandLine::Error, 'Invalid Yaml command file specified !' unless File.exists? yaml_file
10
+ raise UltraCommandLine::Error, 'Cannot read Yaml command file !' unless File.readable? yaml_file
11
+ from_yaml File.read(yaml_file), factory_options, &block
12
+ end
13
+
14
+ def from_yaml(yaml, factory_options = {}, &block)
15
+ from_hash YAML.load(yaml), factory_options, &block
16
+ rescue => e
17
+ UltraCommandLine.logger.error "#{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
18
+ raise UltraCommandLine::Error, 'Invalid Yaml content. Parser error !'
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module UltraCommandLine
2
+ VERSION = '0.4.7'.freeze
3
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ultra_command_line/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ultra_command_line'
8
+ spec.version = UltraCommandLine::VERSION
9
+ spec.authors = ['Laurent B.']
10
+ spec.email = ['lbnetid+rb@gmail.com']
11
+
12
+ spec.summary = %q{Manage complex sub_command line options.}
13
+ spec.description = %q{Allows to handle complex sub_command line options with subcommands a-la-git.}
14
+ spec.homepage = 'https://gitlab.com/lbriais/ultra_command_line'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.15'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec', '~> 3.0'
26
+ spec.add_development_dependency 'simplecov'
27
+
28
+ spec.add_dependency 'super_stack', '~> 1.0'
29
+ spec.add_dependency 'slop', '~> 4.5'
30
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ultra_command_line
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.7
5
+ platform: ruby
6
+ authors:
7
+ - Laurent B.
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-08-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: super_stack
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: slop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.5'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.5'
97
+ description: Allows to handle complex sub_command line options with subcommands a-la-git.
98
+ email:
99
+ - lbnetid+rb@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - Gemfile
108
+ - README.md
109
+ - Rakefile
110
+ - bin/console
111
+ - bin/setup
112
+ - docs/command_definition.puml
113
+ - lib/ultra_command_line.rb
114
+ - lib/ultra_command_line/commands/command_line_parser.rb
115
+ - lib/ultra_command_line/commands/factory.rb
116
+ - lib/ultra_command_line/commands/help_formatter.rb
117
+ - lib/ultra_command_line/commands/main_command.rb
118
+ - lib/ultra_command_line/commands/option_definition.rb
119
+ - lib/ultra_command_line/commands/sub_command.rb
120
+ - lib/ultra_command_line/commands/validation.rb
121
+ - lib/ultra_command_line/error.rb
122
+ - lib/ultra_command_line/manager/base.rb
123
+ - lib/ultra_command_line/manager/cmd_line_args.rb
124
+ - lib/ultra_command_line/manager/commands.rb
125
+ - lib/ultra_command_line/manager/factory.rb
126
+ - lib/ultra_command_line/manager/layered_definition.rb
127
+ - lib/ultra_command_line/manager/processors.rb
128
+ - lib/ultra_command_line/utils/basic_logger.rb
129
+ - lib/ultra_command_line/utils/error_propagation.rb
130
+ - lib/ultra_command_line/utils/yaml_factory.rb
131
+ - lib/ultra_command_line/version.rb
132
+ - ultra_command_line.gemspec
133
+ homepage: https://gitlab.com/lbriais/ultra_command_line
134
+ licenses: []
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.5.1
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Manage complex sub_command line options.
156
+ test_files: []