quickl 0.2.2 → 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.
Files changed (53) hide show
  1. data/CHANGELOG.md +82 -0
  2. data/Gemfile.lock +14 -14
  3. data/bin/quickl +2 -2
  4. data/lib/quickl.rb +162 -2
  5. data/lib/quickl/command.rb +22 -32
  6. data/lib/quickl/command/builder.rb +3 -3
  7. data/lib/quickl/command/delegator.rb +22 -12
  8. data/lib/quickl/command/options.rb +29 -4
  9. data/lib/quickl/command/robustness.rb +5 -12
  10. data/lib/quickl/command/single.rb +4 -6
  11. data/lib/quickl/errors.rb +2 -2
  12. data/lib/quickl/version.rb +2 -2
  13. data/quickl.gemspec +4 -4
  14. data/quickl.noespec +13 -37
  15. data/spec/command/delegator/test_split_argv.rb +41 -0
  16. data/spec/command/{command_building_spec.rb → test_command_building.rb} +4 -4
  17. data/spec/command/{command_name_spec.rb → test_command_name.rb} +1 -6
  18. data/spec/command/{documentation_spec.rb → test_documentation.rb} +1 -1
  19. data/spec/command/{overview_spec.rb → test_overview.rb} +1 -5
  20. data/spec/command/test_parse_options.rb +112 -0
  21. data/spec/command/{requester_spec.rb → test_requester.rb} +1 -1
  22. data/spec/command/test_run.rb +40 -0
  23. data/spec/command/{subcommand_by_name_spec.rb → test_subcommand_by_name.rb} +3 -3
  24. data/spec/command/{subcommands_spec.rb → test_subcommands.rb} +8 -7
  25. data/spec/command/test_usage.rb +11 -0
  26. data/spec/mini_client.rb +36 -6
  27. data/spec/naming/test_command2module.rb +17 -0
  28. data/spec/naming/test_module2command.rb +21 -0
  29. data/spec/quickl/test_command_name.rb +16 -0
  30. data/spec/quickl/test_documentation.rb +32 -0
  31. data/spec/quickl/test_overview.rb +16 -0
  32. data/spec/quickl/test_parse_commandline_args.rb +52 -0
  33. data/spec/quickl/test_split_commandline_args.rb +39 -0
  34. data/spec/quickl/test_sub_command.rb +48 -0
  35. data/spec/quickl/test_super_command.rb +21 -0
  36. data/spec/quickl/test_usage.rb +16 -0
  37. data/spec/{command/robustness/valid_read_file_spec.rb → quickl/test_valid_read_file.rb} +4 -4
  38. data/spec/ruby_tools/fixtures.rb +1 -1
  39. data/spec/ruby_tools/{class_unqualified_name_spec.rb → test_class_unqualified_name.rb} +0 -0
  40. data/spec/ruby_tools/{extract_file_rdoc_spec.rb → test_extract_file_rdoc.rb} +0 -0
  41. data/spec/ruby_tools/{optional_args_block_call_spec.rb → test_optional_args_block.rb} +0 -0
  42. data/spec/ruby_tools/{parent_module_spec.rb → test_parent_module.rb} +0 -0
  43. data/spec/spec_helper.rb +1 -0
  44. data/spec/{quickl_spec.rb → test_quickl.rb} +1 -1
  45. data/tasks/debug_mail.rake +1 -1
  46. data/tasks/spec_test.rake +2 -2
  47. data/tasks/unit_test.rake +2 -2
  48. data/templates/single.erb +1 -1
  49. metadata +96 -76
  50. data/spec/command/run_spec.rb +0 -22
  51. data/spec/command/usage_spec.rb +0 -16
  52. data/spec/naming/command2module_spec.rb +0 -17
  53. data/spec/naming/module2command_spec.rb +0 -21
@@ -1,3 +1,85 @@
1
+ # 0.3.0 / 2011-07-29
2
+
3
+ * Upgrading from 0.2.x
4
+
5
+ This release is mostly compatible with the 0.2.x branch, but may require a few
6
+ changes to your code to avoid deprecation warnings and prepare for 0.4.0 that
7
+ will break if yout don't. From the most to the less likely:
8
+
9
+ * If you originally copied the Help command from examples, please do it again
10
+ on the new version. The execute method of Help should be:
11
+
12
+ sup = Quickl.super_command(self)
13
+ sub = (args.size != 1) ? sup : Quickl.sub_command!(sup, args.first)
14
+ puts Quickl.help(sub)
15
+
16
+ * If you use `usage`, `help`, `overview` or any similar methods in the context
17
+ of a command (instance or class, except in rdoc-based documentation), please
18
+ use the equivalent module methods provided by Quickl.
19
+
20
+ * If you use methods provided by the Robustness module, notably valid_read_file!
21
+ and has_subcommand!, please use module methods provided by Quickl instead.
22
+
23
+ * If you override Command#_run, please now override Command#run instead.
24
+
25
+ * If you rescue SystemExit when executing Command#run, please now rescue
26
+ Quickl::Error (or any subclass) instead. No change is required if you rescue
27
+ when executing Command.run, or if you don't rescue on errors at all.
28
+
29
+ * Bug fixes
30
+
31
+ * A single dash option (e.g. -v) is now correctly recognized by a Delegator
32
+ command ("No such command -v" was previously raised)
33
+
34
+ * Enhancements
35
+
36
+ * Added Quickl.[command_name,super_command,sub_command,help,overview,usage,
37
+ documentation] as convenient query methods on commands and their hierarchy.
38
+ These helpers work both on command instances and on command classes and will
39
+ be part of the public API when stabilized (the same methods on the Command
40
+ class should be considered private from now on; if you use them, please
41
+ upgrade).
42
+
43
+ * Added Quickl.parse_commandline_args that converts a commandline string to
44
+ an ARGV array. This is mainly provided for testing purposes.
45
+
46
+ * Added Quickl.split_commandline_args to split ARGV on the "--" option
47
+ separator
48
+
49
+ * Command#parse_options now accepts a second option that allows specifying
50
+ the behavior in presence of '--' option separators. Default behavior is
51
+ backward compatible and conforms to getopt(1). :keep and :split can be used
52
+ to keep separators in result, or split the latter on separators. Thanks to
53
+ Brian Candler for pointing to getopt(1) for specification.
54
+
55
+ * Deprecations
56
+
57
+ The following methods are deprecated and will be removed in 0.4.0 (run with
58
+ ruby -w to see where refactoring is needed). Examples have been adapted and can
59
+ be copy-pasted safely.
60
+
61
+ * Command#method_missing auto-delegation from command instances to command
62
+ classes is deprecated. Please use explicit calls to command methods on the
63
+ class itself, or tools provided by the Quickl module itself.
64
+
65
+ * The robustness methods available in command instances, notably valid_read_file!
66
+ and has_command! are deprecated and replaced by convenient helpers available
67
+ as module methods of Quickl itself. The Robustness module will disappear in
68
+ 0.4.0.
69
+
70
+ * Possibly hurting changes to the internals
71
+
72
+ * An unused and undocumented `args` attribute (attr_reader) has been removed
73
+ from Single commands instances.
74
+
75
+ * No default behavior is implemented in Command#run anymore (call was
76
+ previously delegated to _run and surrounded in a begin/rescue/end block).
77
+ The method is now directly implemented in Single and Delegator subclasses.
78
+ This may break your code if it redefines _run.
79
+
80
+ * Error catching is done in Command.run instead of Command#run. The way to
81
+ specify how to react to errors did not change.
82
+
1
83
  # 0.2.2 / 2011.07.12
2
84
 
3
85
  * Enhancements
@@ -1,34 +1,34 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quickl (0.2.2)
4
+ quickl (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
8
8
  specs:
9
- bluecloth (2.0.11)
9
+ bluecloth (2.1.0)
10
10
  diff-lcs (1.1.2)
11
11
  rake (0.9.2)
12
- rspec (2.4.0)
13
- rspec-core (~> 2.4.0)
14
- rspec-expectations (~> 2.4.0)
15
- rspec-mocks (~> 2.4.0)
16
- rspec-core (2.4.0)
17
- rspec-expectations (2.4.0)
12
+ rspec (2.6.0)
13
+ rspec-core (~> 2.6.0)
14
+ rspec-expectations (~> 2.6.0)
15
+ rspec-mocks (~> 2.6.0)
16
+ rspec-core (2.6.4)
17
+ rspec-expectations (2.6.0)
18
18
  diff-lcs (~> 1.1.2)
19
- rspec-mocks (2.4.0)
19
+ rspec-mocks (2.6.0)
20
20
  wlang (0.10.2)
21
- yard (0.6.8)
21
+ yard (0.7.2)
22
22
 
23
23
  PLATFORMS
24
24
  java
25
25
  ruby
26
26
 
27
27
  DEPENDENCIES
28
- bluecloth (~> 2.0.9)
28
+ bluecloth (~> 2.1.0)
29
29
  bundler (~> 1.0)
30
30
  quickl!
31
31
  rake (~> 0.9.2)
32
- rspec (~> 2.4.0)
33
- wlang (~> 0.10.1)
34
- yard (~> 0.6.4)
32
+ rspec (~> 2.6.0)
33
+ wlang (~> 0.10.2)
34
+ yard (~> 0.7.2)
data/bin/quickl CHANGED
@@ -21,7 +21,7 @@ class Quickl::Main < Quickl::Command(__FILE__, __LINE__)
21
21
  OPTION_HELPERS = {
22
22
  :version => %q{
23
23
  opt.on_tail('--version', 'Show version and exit'){
24
- raise Quickl::Exit, "#{program_name} #{VERSION}"
24
+ raise Quickl::Exit, "#{Quickl.program_name} #{VERSION}"
25
25
  }
26
26
  },
27
27
  :help => %q{
@@ -81,7 +81,7 @@ class Quickl::Main < Quickl::Command(__FILE__, __LINE__)
81
81
 
82
82
  # Show version and exit
83
83
  opt.on_tail("--version", "Show version") do
84
- raise Quickl::Exit, "#{program_name} #{Quickl::VERSION} #{Quickl::COPYRIGHT}"
84
+ raise Quickl::Exit, "#{Quickl.program_name} #{Quickl::VERSION} #{Quickl::COPYRIGHT}"
85
85
  end
86
86
 
87
87
  end
@@ -8,7 +8,17 @@ require 'quickl/command'
8
8
  module Quickl
9
9
 
10
10
  # Quickl's COPYRIGHT info
11
- COPYRIGHT = "(c) 2010, Bernard Lambeau"
11
+ COPYRIGHT = "(c) 2010-2011, Bernard Lambeau"
12
+
13
+ #
14
+ # Prints a deprecation message on STDERR if $VERBOSE is set to true
15
+ #
16
+ def self.deprecated(who, instead, caller)
17
+ if $VERBOSE
18
+ STDERR << "WARN (Quickl): #{who} is deprecated, use #{instead} "\
19
+ "(#{caller.first})\n"
20
+ end
21
+ end
12
22
 
13
23
  #
14
24
  # Yields the block with the current command builder.
@@ -43,4 +53,154 @@ module Quickl
43
53
  end
44
54
  end
45
55
 
46
- end # module Quickl
56
+ #
57
+ # Convenient method for <code>File.basename($0)</code>
58
+ #
59
+ def self.program_name
60
+ File.basename($0)
61
+ end
62
+
63
+ #
64
+ # When `cmd` is a class, returns it. When a command instance, returns
65
+ # `cmd.class`. Otherwise, raises an ArgumentError.
66
+ #
67
+ def self.command_class(cmd)
68
+ case cmd
69
+ when Class
70
+ cmd
71
+ when Command
72
+ cmd.class
73
+ else
74
+ raise ArgumentError, "Not a recognized command #{cmd}"
75
+ end
76
+ end
77
+
78
+ #
79
+ # Returns the super command of `cmd`, or nil.
80
+ #
81
+ def self.super_command(cmd)
82
+ command_class(cmd).super_command
83
+ end
84
+
85
+ #
86
+ # Returns the subcommand of `cmd` which is called `name`, or nil.
87
+ #
88
+ def self.sub_command(cmd, name)
89
+ command_class(cmd).subcommand_by_name(name)
90
+ end
91
+
92
+ #
93
+ # Returns the subcommand of `cmd` which is called `name`. Raises a
94
+ # NoSuchCommand if not found.
95
+ #
96
+ def self.sub_command!(cmd, name)
97
+ unless subcmd = sub_command(cmd, name)
98
+ raise NoSuchCommand, "No such command #{name}"
99
+ end
100
+ subcmd
101
+ end
102
+
103
+ #
104
+ # Convenient method for <code>command_class(cmd).command_name</code>
105
+ #
106
+ def self.command_name(cmd)
107
+ command_class(cmd).command_name
108
+ end
109
+
110
+ #
111
+ # Convenient method for <code>command_class(cmd).usage</code>
112
+ #
113
+ def self.usage(cmd)
114
+ command_class(cmd).usage
115
+ end
116
+
117
+ #
118
+ # Convenient method for <code>command_class(cmd).overview</code>
119
+ #
120
+ def self.overview(cmd)
121
+ command_class(cmd).overview
122
+ end
123
+
124
+ #
125
+ # Convenient method for <code>command_class(cmd).documentation</code>
126
+ #
127
+ def self.documentation(cmd)
128
+ command_class(cmd).documentation
129
+ end
130
+
131
+ #
132
+ # Alias for documentation
133
+ #
134
+ def self.help(cmd)
135
+ command_class(cmd).help
136
+ end
137
+
138
+ #
139
+ # Checks that `file` is a readable file or raises an error. Returns `file` on
140
+ # success.
141
+ #
142
+ # @param [String] file path to a file that should exists and be readable
143
+ # @param [Class] error_class the error class to use on error
144
+ # (Quickl::IOAccessError by default)
145
+ # @param [String] msg a dedicated error message (a default one is provided)
146
+ # @return [String] file on success
147
+ #
148
+ def self.valid_read_file!(file, error_class = nil, msg = nil)
149
+ if File.file?(file) and File.readable?(file)
150
+ file
151
+ else
152
+ error_class ||= Quickl::IOAccessError
153
+ msg ||= "Not a file or not readable: #{file}"
154
+ raise error_class, msg, caller
155
+ end
156
+ end
157
+
158
+ #
159
+ # Parse a string with commandline arguments and returns an array.
160
+ #
161
+ # Example:
162
+ #
163
+ # parse_commandline_args("--text --size=10") # => ['--text', '--size=10']
164
+ #
165
+ def self.parse_commandline_args(args)
166
+ args = args.split(/\s+/)
167
+ result = []
168
+ until args.empty?
169
+ quote = ['"', "'"].find{|q| q == args.first[0,1]}
170
+ if quote
171
+ if args.first[-1,1] == quote
172
+ result << args.shift[1...-1]
173
+ else
174
+ block = [ args.shift[1..-1] ]
175
+ while args.first[-1,1] != quote
176
+ block << args.shift
177
+ end
178
+ block << args.shift[0...-1]
179
+ result << block.join(" ")
180
+ end
181
+ else
182
+ result << args.shift
183
+ end
184
+ end
185
+ result
186
+ end
187
+
188
+ #
189
+ # Splits an array `args` on '--' option separator.
190
+ #
191
+ def self.split_commandline_args(args)
192
+ args = args.dup
193
+ result, current = [], []
194
+ until args.empty?
195
+ if (x = args.shift) == "--"
196
+ result << current
197
+ current = []
198
+ else
199
+ current << x
200
+ end
201
+ end
202
+ result << current
203
+ result
204
+ end
205
+
206
+ end # module Quickl
@@ -39,7 +39,7 @@ module Quickl
39
39
 
40
40
  # Returns name of the program under execution
41
41
  def program_name
42
- File.basename($0)
42
+ Quickl.program_name
43
43
  end
44
44
 
45
45
  # Returns command name
@@ -85,7 +85,10 @@ module Quickl
85
85
 
86
86
  # Runs the command
87
87
  def run(argv = [], requester = nil)
88
- self.new.run(argv, requester)
88
+ cmd = self.new
89
+ cmd.run(argv, requester)
90
+ rescue Quickl::Error => ex
91
+ handle_error(ex, cmd)
89
92
  end
90
93
 
91
94
  ############################################### Error handling
@@ -98,7 +101,7 @@ module Quickl
98
101
 
99
102
  # Should I bypass reaction to a given error?
100
103
  def no_react_to?(ex)
101
- @no_react_to && @no_react_to.find{|c|
104
+ defined?(@no_react_to) && Array(@no_react_to).find{|c|
102
105
  ex.is_a?(c)
103
106
  }
104
107
  end
@@ -108,6 +111,20 @@ module Quickl
108
111
  !no_react_to?(ex)
109
112
  end
110
113
 
114
+ # Handles a command error
115
+ def handle_error(ex, cmd = self)
116
+ if react_to?(ex)
117
+ begin
118
+ ex.command = cmd
119
+ ex.react!
120
+ rescue Quickl::Error => ex2
121
+ handle_error(ex2, cmd)
122
+ end
123
+ else
124
+ raise ex
125
+ end
126
+ end
127
+
111
128
  end # module ClassMethods
112
129
 
113
130
  # Methods installed on all command instances
@@ -120,40 +137,13 @@ module Quickl
120
137
  # (gives access to options, help, usage, ...)
121
138
  def method_missing(name, *args, &block)
122
139
  if self.class.respond_to?(name)
140
+ Quickl.deprecated(name, "self.class.#{name}", caller)
123
141
  self.class.send(name, *args, &block)
124
142
  else
125
143
  super
126
144
  end
127
145
  end
128
-
129
- # Handles a command error
130
- def handle_error(ex)
131
- if react_to?(ex)
132
- begin
133
- ex.command = self
134
- ex.react!
135
- rescue Quickl::Error => ex2
136
- handle_error(ex2)
137
- end
138
- else
139
- raise ex
140
- end
141
- end
142
-
143
- #
144
- # Runs the command from a requester file with command-line
145
- # arguments.
146
- #
147
- # This method is intended to be overriden and does nothing
148
- # by default.
149
- #
150
- def run(argv, requester = nil)
151
- @requester = requester
152
- _run(argv)
153
- rescue Quickl::Error => ex
154
- handle_error(ex)
155
- end
156
-
146
+
157
147
  end # module InstanceMethods
158
148
 
159
149
  # Tracks child classes
@@ -56,16 +56,16 @@ module Quickl
56
56
  end
57
57
 
58
58
  # install hierarchy
59
- parent = @parent || RubyTools::parent_module(command)
59
+ parent = (defined?(@parent) && @parent) || RubyTools::parent_module(command)
60
60
  if parent && parent.ancestors.include?(Command)
61
61
  command.super_command = parent
62
62
  parent.subcommands << command
63
63
  end
64
64
 
65
65
  # execute callbacks
66
- @callbacks.each do |blk|
66
+ Array(@callbacks).each do |blk|
67
67
  blk.call(command)
68
- end if @callbacks
68
+ end if defined?(@callbacks)
69
69
 
70
70
  command
71
71
  end
@@ -1,26 +1,36 @@
1
1
  module Quickl
2
+
2
3
  module Command::Delegator
3
4
  module InstanceMethods
4
5
 
5
6
  # Run the command by delegation
6
- def _run(argv = [])
7
- # My own options
8
- my_argv = []
9
- while argv.first =~ /^--/
10
- my_argv << argv.shift
11
- end
7
+ def run(argv = [], requester = nil)
8
+ @requester = requester
9
+ my_argv, rest = split_argv(argv)
12
10
  parse_options(my_argv)
13
- execute(argv)
11
+ execute(rest)
14
12
  end
15
13
 
16
14
  def execute(argv)
17
15
  if cmd = argv.shift
18
- cmd = has_command!(cmd).run(argv, self)
16
+ Quickl.sub_command!(self, cmd).run(argv, self)
19
17
  else
20
18
  raise Quickl::Help.new(cmd.nil? ? 0 : -1)
21
19
  end
22
20
  end
23
21
 
22
+ private
23
+
24
+ def split_argv(argv)
25
+ my_argv = []
26
+ while (!argv.empty?) &&
27
+ (argv.first[0,1] == '-') &&
28
+ (argv.first != '--')
29
+ my_argv << argv.shift
30
+ end
31
+ [my_argv, argv]
32
+ end
33
+
24
34
  end
25
35
  module ClassMethods
26
36
 
@@ -45,9 +55,9 @@ module Quickl
45
55
  #
46
56
  def self.Delegator(*args)
47
57
  command_builder do |b|
48
- b.document *args
49
- b.class_module Command::Delegator::ClassMethods
50
- b.instance_module Command::Delegator::InstanceMethods
58
+ b.document(*args)
59
+ b.class_module(Command::Delegator::ClassMethods)
60
+ b.instance_module(Command::Delegator::InstanceMethods)
51
61
  yield(b) if block_given?
52
62
  end
53
63
  Command
@@ -58,4 +68,4 @@ module Quickl
58
68
  self.Delegator(*args, &block)
59
69
  end
60
70
 
61
- end # module Quickl
71
+ end # module Quickl