quickl 0.2.2 → 0.3.0

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