rake-commander 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +12 -8
  4. data/CHANGELOG.md +69 -4
  5. data/LICENSE +21 -0
  6. data/README.md +94 -2
  7. data/Rakefile +11 -13
  8. data/examples/01_basic_example.rb +28 -0
  9. data/examples/02_a_chainer_example.rb +66 -0
  10. data/examples/02_a_chainer_options_set.rb +8 -0
  11. data/examples/02_b_chained_example.rb +13 -0
  12. data/examples/03_a_chainer_plus_example.rb +34 -0
  13. data/examples/03_b_chained_plus_example.rb +17 -0
  14. data/examples/Examples.rake +7 -0
  15. data/examples/README.md +79 -0
  16. data/examples/libs/shell_helpers.rb +81 -0
  17. data/lib/rake-commander/base/class_auto_loader.rb +45 -7
  18. data/lib/rake-commander/base/class_helpers.rb +16 -61
  19. data/lib/rake-commander/base/class_inheritable.rb +122 -0
  20. data/lib/rake-commander/base/custom_error.rb +52 -0
  21. data/lib/rake-commander/base/object_helpers.rb +42 -0
  22. data/lib/rake-commander/base.rb +16 -2
  23. data/lib/rake-commander/option.rb +115 -25
  24. data/lib/rake-commander/options/arguments.rb +206 -94
  25. data/lib/rake-commander/options/description.rb +17 -0
  26. data/lib/rake-commander/options/error/base.rb +86 -0
  27. data/lib/rake-commander/options/error/handling.rb +106 -0
  28. data/lib/rake-commander/options/error/invalid_argument.rb +21 -0
  29. data/lib/rake-commander/options/error/invalid_option.rb +9 -0
  30. data/lib/rake-commander/options/error/missing_argument.rb +10 -0
  31. data/lib/rake-commander/options/error/missing_option.rb +48 -0
  32. data/lib/rake-commander/options/error/unknown_argument.rb +32 -0
  33. data/lib/rake-commander/options/error.rb +75 -10
  34. data/lib/rake-commander/options/name.rb +67 -23
  35. data/lib/rake-commander/options/result.rb +107 -0
  36. data/lib/rake-commander/options/set.rb +7 -1
  37. data/lib/rake-commander/options.rb +175 -98
  38. data/lib/rake-commander/patcher/README.md +79 -0
  39. data/lib/rake-commander/patcher/application/run_method.rb +46 -0
  40. data/lib/rake-commander/patcher/application/top_level_method.rb +74 -0
  41. data/lib/rake-commander/patcher/application.rb +16 -0
  42. data/lib/rake-commander/patcher/base.rb +45 -0
  43. data/lib/rake-commander/patcher/debug.rb +32 -0
  44. data/lib/rake-commander/patcher/helpers.rb +44 -0
  45. data/lib/rake-commander/patcher.rb +26 -0
  46. data/lib/rake-commander/rake_context/wrapper.rb +2 -0
  47. data/lib/rake-commander/rake_task.rb +49 -54
  48. data/lib/rake-commander/version.rb +1 -1
  49. data/lib/rake-commander.rb +4 -0
  50. data/rake-commander.gemspec +4 -1
  51. metadata +74 -6
  52. data/examples/basic.rb +0 -30
  53. data/lib/rake-commander/options/error_rely.rb +0 -58
@@ -0,0 +1,81 @@
1
+ module Examples
2
+ module Libs
3
+ module ShellHelpers
4
+ SHELL_METHODS = %I[system back_quotes x spawn exec fork_exec pipe].freeze
5
+
6
+ # https://stackoverflow.com/a/37329716/4352306
7
+ def shell(cmd, method: :system)
8
+ method = method.to_sym
9
+ case method
10
+ when :system
11
+ success = system(cmd)
12
+ puts "* #{success ? "Succeded in" : "Failed to"} running '#{cmd}'"
13
+ when :back_quotes, :x
14
+ result = `#{cmd}`
15
+ puts result
16
+ #%x(#{cmd})
17
+ when :spawn # like Kernel.system but with no wait
18
+ pid = Process.spawn(cmd)
19
+ puts "* new child process (pid: #{pid}). Will wait..."
20
+ Process.wait(pid)
21
+ puts "* child process finished (pid: #{pid})"
22
+ when :exec
23
+ exec cmd
24
+ # Flow does not reach here
25
+ when :fork_exec
26
+ for_exec(cmd)
27
+ when :pipe # I/O of new process
28
+ pipe(cmd)
29
+ else
30
+ raise "* unknown shell method '#{method}'"
31
+ end
32
+ end
33
+
34
+ def fork_exec(cmd)
35
+ pid = fork do
36
+ shell(cmd, method: :exec)
37
+ end
38
+ puts "* new child process (pid: #{pid})"
39
+ rescue NotImplementedError => e
40
+ puts e
41
+ puts "Redirecting to 'spawn'"
42
+ shell(cmd, method: :spawn)
43
+ end
44
+
45
+ def pipe(cmd)
46
+ IO.popen(host_shell_command, 'r+') do |pipe|
47
+ puts "* new child process (pid: #{pipe.pid})"
48
+ prompt = pipe_prompt(pipe)
49
+ pipe.puts cmd
50
+ pipe.close_write # prevent `gets` to get stuck
51
+ lines = pipe.readlines
52
+ if index = lines.index {|ln| ln.include?(cmd)}
53
+ lines = lines[index+1..]
54
+ end
55
+ lines.reject! {|ln| ln.start_with?(prompt)}
56
+ lines = lines.map {|ln| ">> (pid: #{pipe.pid}) #{ln}"}
57
+ puts lines
58
+ end
59
+ end
60
+
61
+ # Changes the commandline prompt, so we can discard those.
62
+ def pipe_prompt(pipe, prompt: ':>$ ')
63
+ if Gem::Platform.local.os == "mingw32"
64
+ pipe.puts "function prompt {\"#{prompt}\"}"
65
+ else
66
+ pipe.puts "PS1=\"#{prompt}\""
67
+ end
68
+ prompt
69
+ end
70
+
71
+ # Command to open a new shell for `pipe`
72
+ def host_shell_command
73
+ if Gem::Platform.local.os == "mingw32"
74
+ "powershell -noprofile -noninteractive"
75
+ else
76
+ 'sh'
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -25,17 +25,25 @@ class RakeCommander
25
25
  end
26
26
 
27
27
  # To restrict which namespaces it is allowed to load from
28
+ # @note this deletes from the `:ignore` namespaces
28
29
  def autoload_namespace(*namespaces)
29
30
  _autoload_namespace(:include, *namespaces)
30
31
  end
31
32
 
32
33
  # To ignore certain namespaces this class should not autoload from
34
+ # @note this deletes from the `:include` namespaces
33
35
  def autoload_namespace_ignore(*namespaces)
34
36
  _autoload_namespace(:ignore, *namespaces)
35
37
  end
36
38
 
39
+ # Applies a change to the `autoloaded_namespaces`
37
40
  def _autoload_namespace(type, *namespaces)
38
- autoloaded_namespaces(type).concat(namespaces) unless namespaces.empty?
41
+ autoloaded_namespaces(type).tap do |target|
42
+ next if namespaces.empty?
43
+ other_type = type == :include ? :ignore : :include
44
+ namespaces.each {|nm_sp| autoloaded_namespace(other_type).delete(nm_sp)}
45
+ target.concat(namespaces)
46
+ end
39
47
  end
40
48
 
41
49
  # @param constant [Class, String] a class or namespace we want to check auto-load entitlement thereof.
@@ -61,14 +69,36 @@ class RakeCommander
61
69
  @autoloaded_children ||= []
62
70
  end
63
71
 
72
+ # Keep track on actual children that have been ignored by `autoload_namespace_ignore`.
73
+ def ignored_children
74
+ @ignored_children ||= []
75
+ end
76
+
77
+ # Allows to reload
78
+ # @note it may be handy some times.
79
+ def clear_autoloaded_children
80
+ forget_class!(*autoloaded_children, *ignored_children)
81
+ @ignored_children = []
82
+ @autoloaded_children = []
83
+ end
84
+
85
+ # Prevents already excluded children to enter into the loop again.
86
+ def excluded_children
87
+ @excluded_children ||= []
88
+ end
89
+
64
90
  # Children classes of `autoloader_class` that have not been created an instance of.
65
91
  def unloaded_children
66
92
  return [] unless autoloaded_class
67
93
  new_detected = new_classes
68
94
  known_class!(*new_detected)
69
95
  descendants(parent_class: autoloaded_class, scope: new_detected).select do |child_class|
70
- !autoloaded_children.include?(child_class) && autoload_class?(child_class)
71
- end.sort
96
+ !autoloaded_children.include?(child_class) && \
97
+ !excluded_children.include?(child_class) && \
98
+ autoload_class?(child_class).tap do |ignored|
99
+ ignored_children.push(child_class) if ignored
100
+ end
101
+ end
72
102
  end
73
103
 
74
104
  # It loads/creates a new instance of children classes pending to be loaded.
@@ -79,14 +109,15 @@ class RakeCommander
79
109
  return false if pending_children.empty?
80
110
  @loading_children = true
81
111
  pending_children.each do |klass|
82
- child = object ? klass.new(object) : klass.new
112
+ exclude = false
113
+ child = object ? klass.new(object) : klass.new
83
114
  yield(child) if block_given?
84
-
85
115
  rescue TypeError
86
116
  # Can't create from this class (must be the singleton class)
87
- # Just ignore
117
+ exclude = true
118
+ excluded_children.push(klass)
88
119
  ensure
89
- autoloaded_children.push(klass)
120
+ autoloaded_children.push(klass) unless exclude
90
121
  end
91
122
  @loading_children = false
92
123
  true
@@ -101,6 +132,13 @@ class RakeCommander
101
132
  # Add to known namespaces
102
133
  def known_class!(*classes)
103
134
  known_classes.concat(classes)
135
+ self
136
+ end
137
+
138
+ # Forget namespaces
139
+ def forget_class!(*classes)
140
+ @known_classes = known_classes - classes
141
+ self
104
142
  end
105
143
 
106
144
  # List all new namespaces
@@ -1,7 +1,7 @@
1
1
  class RakeCommander
2
2
  module Base
3
3
  module ClassHelpers
4
- NOT_USED = 'no_used!'.freeze
4
+ NOT_USED = :not_used!
5
5
 
6
6
  # Helper to determine if a paramter has been used
7
7
  # @note to effectivelly use this helper, you should initialize your target
@@ -65,15 +65,6 @@ class RakeCommander
65
65
  end.join("")
66
66
  end
67
67
 
68
- # Helper to create an instance variable `name`
69
- # @param [String, Symbol] the name of the variable
70
- # @reutrn [String] the name of the created instance variable
71
- def instance_variable_name(name)
72
- str = name.to_s
73
- str = "@#{str}" unless str.start_with?("@")
74
- str
75
- end
76
-
77
68
  # If the class for `name` exists, it returns it. Otherwise it generates it.
78
69
  # @param name [String, Symbol] the name of the new class
79
70
  # @param inherits [Class] the parent class to _inherit_ from
@@ -95,6 +86,16 @@ class RakeCommander
95
86
  end
96
87
  end
97
88
 
89
+ # @param klasses [Arrary<Class>] the classes to sort.
90
+ # @return [Arrary<Class>] the classes in hierarchy order.
91
+ def sort_classes(*klasses)
92
+ klasses.sort do |k_1, k_2|
93
+ next -1 if k_2 < k_1
94
+ next 1 if k_1 < k_2
95
+ 0
96
+ end
97
+ end
98
+
98
99
  # Finds all child classes of the current class.
99
100
  # @param parent_class [Class] the parent class we want to find children of.
100
101
  # @param direct [Boolean] it will only include direct child classes.
@@ -103,18 +104,11 @@ class RakeCommander
103
104
  def descendants(parent_class: self, direct: false, scope: nil)
104
105
  scope ||= ObjectSpace.each_object(::Class)
105
106
  return [] if scope.empty?
106
- scope.select do |klass|
107
- klass < parent_class
108
- end.sort do |k_1, k_2|
109
- next -1 if k_2 < k_1
110
- next 1 if k_1 < k_2
111
- 0
112
- end.tap do |siblings|
113
- if direct
114
- siblings.reject! do |si|
115
- siblings.any? {|s| si < s}
116
- end
117
- end
107
+ siblings = scope.select {|klass| klass < parent_class}
108
+ siblings = sort_classes(*siblings)
109
+ return siblings unless direct
110
+ siblings.reject! do |si|
111
+ siblings.any? {|s| si < s}
118
112
  end
119
113
  end
120
114
 
@@ -124,45 +118,6 @@ class RakeCommander
124
118
  def descendants?(parent_class: self, direct: false)
125
119
  !descendants(parent_class: parent_class, direct: direct).empty?
126
120
  end
127
-
128
- # Keeps track on class instance variables that should be inherited by child classes.
129
- # @note
130
- # - subclasses will inherit the value as is at that moment
131
- # - any change afterwards will be only on the specific class (in line with class instance variables)
132
- # - adapted from https://stackoverflow.com/a/10729812/4352306
133
- # TODO: this separates the logic of the method to the instance var. Think if would be possible to join them somehow.
134
- def inheritable_class_vars(*vars)
135
- @inheritable_class_vars ||= [:inheritable_class_vars]
136
- @inheritable_class_vars += vars
137
- end
138
-
139
- # Builds the attr_reader and attr_writer of `attrs` and registers the associated instance variable as inheritable.
140
- def inheritable_attrs(*attrs, add_accessors: false)
141
- if add_accessors
142
- attrs.each do |attr|
143
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
144
- class << self; attr_accessor :#{attr} end
145
- RUBY
146
- end
147
- end
148
- inheritable_class_vars(*attrs)
149
- end
150
-
151
- # This callback method is called whenever a subclass of the current class is created.
152
- # @note
153
- # - values of the instance variables are copied as they are (no dups or clones)
154
- # - the above means: avoid methods that change the state of the mutable object on it
155
- # - mutating methods would reflect the changes on other classes as well
156
- # - therefore, `freeze` will be called on the values that are inherited.
157
- def inherited(subclass)
158
- super.tap do
159
- inheritable_class_vars.each do |var|
160
- instance_var = instance_variable_name(var)
161
- value = instance_variable_get(instance_var)
162
- subclass.instance_variable_set(instance_var, value.freeze)
163
- end
164
- end
165
- end
166
121
  end
167
122
  end
168
123
  end
@@ -0,0 +1,122 @@
1
+ class RakeCommander
2
+ module Base
3
+ module ClassInheritable
4
+ include RakeCommander::Base::ObjectHelpers
5
+
6
+ # Builds the attr_reader and attr_writer of `attrs` and registers the associated
7
+ # instance variable as inheritable.
8
+ # @yield [value]
9
+ # @yieldparam value [Variant] the value of the parent class
10
+ # @yieldreturn the value that will be inherited by the child class
11
+ # @param attrs [Array <Symbol>] the variable names that should be inheritable.
12
+ # @param add_accessors [Boolean] whether attr_accessor should be invoked
13
+ # @param deep_dup [Boolean] whether the value of the instance var should be `deep_dup`ed.
14
+ def attr_inheritable(*attrs, add_accessors: false, deep_dup: true, &block)
15
+ attrs = attrs.map(&:to_sym)
16
+ inheritable_class_var(*attrs, deep_dup: deep_dup, &block)
17
+ return unless add_accessors
18
+ attrs.each do |attr|
19
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
+ class << self; attr_accessor :#{attr} end
21
+ RUBY
22
+ end
23
+ self
24
+ end
25
+
26
+ # @return [Boolean] whether an `var` has been declared as inheritable
27
+ def inheritable_class_var?(var)
28
+ inheritable_class_var.any? do |_method, definitions|
29
+ definitions.key?(var.to_sym)
30
+ end
31
+ end
32
+
33
+ # Removes from the inheritance some class variables.
34
+ # @param attrs [Array <Symbol>] the instance variable names of the class
35
+ # that should NOT be inheritable.
36
+ def attr_not_inheritable(*attrs)
37
+ attrs.each do |attr|
38
+ next unless method = inheritable_class_var_method(attr)
39
+ inheritable_class_var[method].delete(attr)
40
+ end
41
+ self
42
+ end
43
+
44
+ private
45
+
46
+ # This callback method is called whenever a subclass of the current class is created.
47
+ def inherited(subclass)
48
+ super.tap do
49
+ inheritable_class_var.each do |method, definitions|
50
+ definitions.each do |var, action|
51
+ instance_var = instance_variable_name(var)
52
+ value = instance_variable_get(instance_var)
53
+ child_value = inherited_class_value(value, method, action)
54
+ subclass.instance_variable_set(instance_var, child_value)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ # @return [Variant] the value that the child class will inherit
61
+ def inherited_class_value(value, method, action)
62
+ case method
63
+ when :mirror
64
+ value
65
+ when :deep_dup
66
+ case action
67
+ when Proc
68
+ action.call(value)
69
+ when :default
70
+ custom_deep_dup(value)
71
+ end
72
+ end
73
+ end
74
+
75
+ # Keeps track on class instance variables that should be inherited by child classes.
76
+ # @note
77
+ # - adapted from https://stackoverflow.com/a/10729812/4352306
78
+ # - subclasses will inherit the value depending on `depep_dup` and `block` if enabled or given (respectivelly)
79
+ # - any change afterwards will be only on the specific class (in line with class instance variables)
80
+ # @param see `#attr_inheritable`
81
+ # @return [Hash] methods and variables to be inherited.
82
+ def inheritable_class_var(*vars, deep_dup: true, &block)
83
+ @inheritable_class_var ||= {
84
+ mirror: {},
85
+ deep_dup: {}
86
+ }.tap do |hash|
87
+ hash[:deep_dup][:inheritable_class_var] = :default
88
+ end
89
+ @inheritable_class_var.tap do |_methods|
90
+ vars.each {|var| inheritable_class_var_add(var, deep_dup: deep_dup, &block)}
91
+ end
92
+ end
93
+
94
+ # Adds var to the `inheritable_class_var`
95
+ # @param var [Symbol] the name of an instance variable of this class that should be inherited.
96
+ def inheritable_class_var_add(var, deep_dup: true, &block)
97
+ # Remove previous definition if present
98
+ attr_not_inheritable(var)
99
+ method = deep_dup || block ? :deep_dup : :mirror
100
+ inheritable_class_var[method][var] = block || :default
101
+ self
102
+ end
103
+
104
+ # @return [Symbol, NilClass]
105
+ def inheritable_class_var_method(var)
106
+ inheritable_class_var.each do |method, definitions|
107
+ return method if definitions.key?(var.to_sym)
108
+ end
109
+ nil
110
+ end
111
+
112
+ # Helper to create an instance variable `name`
113
+ # @param [String, Symbol] the name of the variable
114
+ # @return [String] the name of the created instance variable
115
+ def instance_variable_name(name)
116
+ str = name.to_s
117
+ str = "@#{str}" unless str.start_with?("@")
118
+ str
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,52 @@
1
+ class RakeCommander
2
+ module Base
3
+ # This class allows to use both method calls to `raise` by using additional parameters.
4
+ # @note Although not clearly explained, this is somehow captured here https://stackoverflow.com/a/32481520/4352306
5
+ class CustomError < StandardError
6
+ def initialize(value = nil)
7
+ super(@message = to_message(value)) if @value = value
8
+ end
9
+
10
+ # If @value was already set, ignore the latest message and
11
+ # just return @message
12
+ def to_s
13
+ return @message if @message
14
+ unclassed(super)
15
+ end
16
+
17
+ # If @value was already set, ignore the latest message
18
+ # just return @message
19
+ def message
20
+ return @message if @message
21
+ to_message(unclassed(super))
22
+ end
23
+
24
+ protected
25
+
26
+ # Any **children classes** that want to extend how `value` is transformed
27
+ # into a `message` **should extend this method**.
28
+ # @return [String] `message`
29
+ def to_message(value)
30
+ case value
31
+ when StandardError
32
+ to_message(value.message)
33
+ when String
34
+ value
35
+ when NilClass
36
+ value
37
+ else
38
+ raise ArgumentError, "Expecting String, StandardError or NilClass. Given: #{value.class}"
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # When `value` is `nil` **methods** `to_s` and `message` return the error class of `self`.
45
+ # This helper allows to remove that part to know if the error was raised with `nil`
46
+ # @return [String]
47
+ def unclassed(str)
48
+ str.to_s.gsub(self.class.to_s, '').strip
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ class RakeCommander
2
+ module Base
3
+ module ObjectHelpers
4
+ private
5
+
6
+ # Custom Object#deep_dup for rake commander
7
+ def custom_deep_dup(value, dup_objects: true, &dup_block)
8
+ case value
9
+ when Hash
10
+ custom_hash_deep_dup(value, dup_objects: dup_objects, &dup_block)
11
+ when Array
12
+ value.map {|v| custom_deep_dup(v, dup_objects: dup_objects, &dup_block)}
13
+ else
14
+ custom_object_deep_dup(value, dup_objects: true, &dup_block)
15
+ end
16
+ end
17
+
18
+ # Does the copy of the final object
19
+ def custom_object_deep_dup(value, dup_objects: true)
20
+ return yield(value) if block_given?
21
+ return value unless dup_objects
22
+ return value.deep_dup if value.respond_to?(:deep_dup)
23
+ value.dup
24
+ end
25
+
26
+ # Custom Hash#deep_dup for rake commander
27
+ def custom_hash_deep_dup(original, dup_objects: true, &dup_block)
28
+ raise ArgumentError, "Expecting Hash. Given: #{original.class}" unless original.is_a?(Hash)
29
+ hash = original.dup
30
+ original.each_pair do |key, value|
31
+ unless key.frozen? && key.is_a?(::String)
32
+ hash.delete(key)
33
+ key = custom_deep_dup(key, dup_objects: dup_objects, &dup_block) if dup_objects
34
+ end
35
+ value = dup_objects ? custom_deep_dup(value, dup_objects: dup_objects, &dup_block) : value
36
+ hash[key] = value
37
+ end
38
+ hash
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,5 +1,8 @@
1
+ require_relative 'base/object_helpers'
1
2
  require_relative 'base/class_helpers'
3
+ require_relative 'base/class_inheritable'
2
4
  require_relative 'base/class_auto_loader'
5
+ require_relative 'base/custom_error'
3
6
  require_relative 'rake_task'
4
7
  require_relative 'options'
5
8
 
@@ -8,22 +11,33 @@ class RakeCommander
8
11
  class << self
9
12
  def included(base)
10
13
  super(base)
11
- base.extend RakeCommander::Base::ClassHelpers
12
14
  base.extend RakeCommander::Base::ClassAutoLoader
13
15
  base.autoloads_children_of RakeCommander
14
16
 
15
17
  base.extend ClassMethods
16
18
  base.send :include, RakeTask
17
-
18
19
  base.send :include, Options
19
20
  #autoload_namespace_ignore "RakeCommander::Samples"
20
21
  end
21
22
  end
22
23
 
23
24
  module ClassMethods
25
+ # Loads children classes by keeping a cache.
24
26
  def self_load
25
27
  autoload_children
26
28
  end
29
+
30
+ # Clears track on any auto-loaded children
31
+ # @note required for reload.
32
+ def self_load_reset
33
+ clear_autoloaded_children
34
+ end
35
+
36
+ # Clears the cache of autoloaded children classes and loads them again.
37
+ def self_reload
38
+ self_load_reset
39
+ autoload_children
40
+ end
27
41
  end
28
42
  end
29
43
  end