quickl 0.1.1

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 (46) hide show
  1. data/CHANGELOG.md +9 -0
  2. data/README.md +118 -0
  3. data/Rakefile +64 -0
  4. data/bin/quickl +116 -0
  5. data/examples/delegate/README.md +86 -0
  6. data/examples/delegate/bin/delegate +9 -0
  7. data/examples/delegate/lib/delegate.rb +41 -0
  8. data/examples/delegate/lib/hello_world.rb +39 -0
  9. data/examples/delegate/lib/help.rb +24 -0
  10. data/examples/delegate/test/delegate_test.rb +68 -0
  11. data/examples/hello/README.md +74 -0
  12. data/examples/hello/hello +57 -0
  13. data/examples/hello/hello_test.rb +65 -0
  14. data/examples/helper.rb +6 -0
  15. data/lib/quickl.rb +47 -0
  16. data/lib/quickl/command.rb +154 -0
  17. data/lib/quickl/command/builder.rb +58 -0
  18. data/lib/quickl/command/delegate.rb +53 -0
  19. data/lib/quickl/command/options.rb +55 -0
  20. data/lib/quickl/command/robustness.rb +18 -0
  21. data/lib/quickl/command/single.rb +27 -0
  22. data/lib/quickl/errors.rb +196 -0
  23. data/lib/quickl/ext/object.rb +29 -0
  24. data/lib/quickl/naming.rb +53 -0
  25. data/lib/quickl/ruby_tools.rb +60 -0
  26. data/quickl.gemspec +38 -0
  27. data/templates/single.erb +40 -0
  28. data/test/command/command_name.spec +16 -0
  29. data/test/command/documentation.spec +23 -0
  30. data/test/command/overview.spec +14 -0
  31. data/test/command/run.spec +22 -0
  32. data/test/command/subcommands.spec +20 -0
  33. data/test/command/usage.spec +16 -0
  34. data/test/mini_client.rb +59 -0
  35. data/test/naming/command2module.spec +17 -0
  36. data/test/naming/module2command.spec +21 -0
  37. data/test/ruby_tools/class_unqualified_name.spec +28 -0
  38. data/test/ruby_tools/extract_file_rdoc.spec +28 -0
  39. data/test/ruby_tools/fixtures.rb +27 -0
  40. data/test/ruby_tools/fixtures/RubyTools.rdoc +12 -0
  41. data/test/ruby_tools/fixtures/Utils.rdoc +3 -0
  42. data/test/ruby_tools/optional_args_block_call.spec +37 -0
  43. data/test/ruby_tools/parent_module.spec +23 -0
  44. data/test/spec_helper.rb +13 -0
  45. data/test/wrapping.rb +39 -0
  46. metadata +129 -0
@@ -0,0 +1,58 @@
1
+ module Quickl
2
+ class Command
3
+ class Builder
4
+
5
+ # Adds document tracking information
6
+ def document(file, line)
7
+ @file, @line = file, line
8
+ end
9
+
10
+ # Adds some command class modules
11
+ def class_modules(*mods)
12
+ @class_modules ||= [
13
+ Command::ClassMethods,
14
+ Command::Options::ClassMethods,
15
+ ]
16
+ @class_modules += mods
17
+ end
18
+ alias :class_module class_modules
19
+
20
+ # Adds some command instance modules
21
+ def instance_modules(*mods)
22
+ @instance_modules ||= [
23
+ Command::InstanceMethods,
24
+ Command::Robustness,
25
+ Command::Options::InstanceMethods
26
+ ]
27
+ @instance_modules += mods
28
+ end
29
+ alias :instance_module instance_modules
30
+
31
+ # Installs on a command subclass
32
+ def run(command)
33
+ # install class and instance methods
34
+ class_modules.each{|mod|
35
+ command.extend(mod)
36
+ }
37
+ instance_modules.each{|mod|
38
+ command.instance_eval{ include mod }
39
+ }
40
+
41
+ # install documentation
42
+ if @file and @line
43
+ command.doc_place = [@file, @line]
44
+ end
45
+
46
+ # install hierarchy
47
+ parent = RubyTools::parent_module(command)
48
+ if parent && parent.ancestors.include?(Command)
49
+ command.super_command = parent
50
+ parent.subcommands << command
51
+ end
52
+
53
+ command
54
+ end
55
+
56
+ end # class Builder
57
+ end # class Command
58
+ end # module Quickl
@@ -0,0 +1,53 @@
1
+ module Quickl
2
+ module Command::Delegate
3
+ module InstanceMethods
4
+
5
+ # 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
12
+ parse_options(my_argv)
13
+
14
+ # Run the subcommand now
15
+ if cmd = argv.shift
16
+ has_command!(cmd).run(argv)
17
+ else
18
+ raise Quickl::Help.new(cmd.nil? ? 0 : -1)
19
+ end
20
+ end
21
+
22
+ end
23
+ module ClassMethods
24
+
25
+ def summarized_subcommands
26
+ doc = subcommands.collect{|cmd|
27
+ [cmd.command_name, cmd.overview]
28
+ }
29
+ max = doc.inject(0){|memo,pair|
30
+ l = pair.first.length
31
+ memo > l ? memo : l
32
+ }
33
+ doc.collect{|pair|
34
+ " %-#{max}s %s" % pair
35
+ }.join("\n")
36
+ end
37
+
38
+ end
39
+ end # module Command::Delegate
40
+
41
+ #
42
+ # Create a delegate command
43
+ #
44
+ def self.Delegate(*args)
45
+ command_builder do |b|
46
+ b.document *args
47
+ b.class_module Command::Delegate::ClassMethods
48
+ b.instance_module Command::Delegate::InstanceMethods
49
+ end
50
+ Command
51
+ end
52
+
53
+ end # module Quickl
@@ -0,0 +1,55 @@
1
+ module Quickl
2
+ module Command::Options
3
+ module ClassMethods
4
+
5
+ # Installed options builders
6
+ attr_reader :option_builders
7
+
8
+ # Builds an OptionParser instance
9
+ def build_options(scope = self)
10
+ OptionParser.new do |opt|
11
+ opt.program_name = File.basename $0
12
+ if const_defined?(:VERSION)
13
+ opt.version = const_get(:VERSION)
14
+ end
15
+ opt.summary_indent = ' ' * 2
16
+ (option_builders || []).each{|b|
17
+ scope.instance_exec(opt, &b)
18
+ }
19
+ end
20
+ end
21
+
22
+ # Without builder nor block, returns built options.
23
+ # Otherwise, adds a new option builder
24
+ def options(builder = nil, &block)
25
+ if b = (builder || block)
26
+ @option_builders ||= []
27
+ @option_builders << b
28
+ else
29
+ build_options
30
+ end
31
+ end
32
+
33
+ # Returns summarized options
34
+ def summarized_options
35
+ options.summarize.join.rstrip
36
+ end
37
+
38
+ end # module ClassMethods
39
+ module InstanceMethods
40
+
41
+ # Returns OptionParser instance
42
+ def options
43
+ @options ||= self.class.build_options(self)
44
+ end
45
+
46
+ # Parses options
47
+ def parse_options(argv)
48
+ options.parse!(argv)
49
+ rescue OptionParser::ParseError => ex
50
+ raise Quickl::InvalidOption, ex.message, ex.backtrace
51
+ end
52
+
53
+ end # module InstanceMethods
54
+ end # module Command::Options
55
+ end # module Quickl
@@ -0,0 +1,18 @@
1
+ module Quickl
2
+ class Command
3
+ module Robustness
4
+ include Naming
5
+
6
+ # Checks that a command whose name is given exists
7
+ # or raises a NoSuchCommand.
8
+ def has_command!(name, referer = self.class)
9
+ name.split(':').inject(referer){|cur,look|
10
+ cur.const_get(command2module(look))
11
+ }
12
+ rescue NameError => ex
13
+ raise NoSuchCommand, "No such command #{name}", ex.backtrace
14
+ end
15
+
16
+ end # module Robustness
17
+ end # class Command
18
+ end # module Quickl
@@ -0,0 +1,27 @@
1
+ module Quickl
2
+
3
+ # Instance method for being a single command
4
+ module Command::Single
5
+
6
+ # Command arguments after options parsing
7
+ attr_reader :args
8
+
9
+ # Run the command by delegation
10
+ def _run(argv = [])
11
+ execute(parse_options(argv))
12
+ end
13
+
14
+ end # module Command::Delegate
15
+
16
+ #
17
+ # Create a delegate command
18
+ #
19
+ def self.Command(*args)
20
+ command_builder do |b|
21
+ b.document *args
22
+ b.instance_module Command::Single
23
+ end
24
+ Command
25
+ end
26
+
27
+ end # module Quickl
@@ -0,0 +1,196 @@
1
+ module Quickl
2
+
3
+ #
4
+ # Main class of all Quickl's errors.
5
+ #
6
+ # Quickl's errors normally occur only in the context of a command
7
+ # execution, command which can be obtained through the dedicated
8
+ # accessor.
9
+ #
10
+ # Each Quickl error comes bundled with a default reaction implemented
11
+ # by the _react!_ method. Unless you provide your own reaction via
12
+ # dedicated Command's DSL methods (react_to, no_react_to, ...), this
13
+ # reaction will be executed if the error is raised. Default reactions
14
+ # are implemented so that the command fail gracefully for the ordinary
15
+ # commandline user.
16
+ #
17
+ class Error < StandardError
18
+
19
+ # Quickl command under execution
20
+ attr_accessor :command
21
+
22
+ # Exit code
23
+ attr_reader :exit_code
24
+
25
+ # Creates an Exit instance
26
+ def initialize(*args)
27
+ super(args.find{|s| s.is_a?(String)} || "")
28
+ @exit_code = args.find{|s| s.is_a?(Integer)} || nil
29
+ end
30
+
31
+ # Returns true if exit code is not nil
32
+ def exit?
33
+ !exit_code.nil?
34
+ end
35
+
36
+ # Returns $stdout or $stderr according to exit code.
37
+ def error_io
38
+ exit_code == -1 ? $stderr : $stdout
39
+ end
40
+
41
+ # Print error's message on IO returned by error_io and
42
+ # make a call to Kernel.exit with required exit code.
43
+ def do_kernel_exit
44
+ error_io.puts self.message
45
+ Kernel.exit(exit_code) if exit?
46
+ end
47
+
48
+ end # class Error
49
+
50
+ #
51
+ # This error can be raised to exit command execution with a message
52
+ # and exit code.
53
+ #
54
+ # Default exit code:
55
+ # 0
56
+ #
57
+ # Default reaction:
58
+ # Prints the error message (on $stdout if exit code is 0, on $stderr
59
+ # otherwise) then invoke Kernel.exit with the exit code.
60
+ #
61
+ # Examples:
62
+ #
63
+ # # Non-failure exit, for --version, --help, --copyright and the
64
+ # # like
65
+ # raise Quickl::Exit, "a friendly message"
66
+ #
67
+ # # Failure exit, because something failed
68
+ # raise Quickl::Exit.new(-1), "Something was wrong!"
69
+ #
70
+ class Exit < Error
71
+
72
+ def initialize(*args)
73
+ super(*(args + [ 0 ]))
74
+ end
75
+
76
+ def react!
77
+ do_kernel_exit
78
+ end
79
+
80
+ end # class Exit
81
+
82
+ #
83
+ # This error can be raised to print command's help and exit.
84
+ #
85
+ # Default exit code:
86
+ # 0
87
+ #
88
+ # Default reaction:
89
+ # raise Exit.new(code), additional + "\n" + help, backtrace
90
+ #
91
+ # Examples:
92
+ #
93
+ # # Print command help on $stdout and exit with 0
94
+ # raise Quickl::Help
95
+ #
96
+ # # Print command help on $stderr and exit with -1
97
+ # raise Quickl::Help.new(-1)
98
+ #
99
+ # # Print additional message before help
100
+ # raise Quickl::Help, "Hello user, below if the help!"
101
+ #
102
+ class Help < Error
103
+
104
+ def initialize(*args)
105
+ super(*(args + [ 0 ]))
106
+ end
107
+
108
+ def react!
109
+ msg = if (self.message || "").empty?
110
+ command.help
111
+ else
112
+ self.message.to_s + "\n" + command.help
113
+ end
114
+ raise Exit.new(self.exit_code), msg, backtrace
115
+ end
116
+
117
+ end # class Help
118
+
119
+ #
120
+ # This error can be raised to indicate that some command option
121
+ # was misused. This error is automatically raised on occurence
122
+ # of an OptionParser::ParseError when options are parsed.
123
+ #
124
+ # Default exit code:
125
+ # -1
126
+ #
127
+ # Default reaction:
128
+ # raise Help.new(code), message, backtrace
129
+ #
130
+ # Examples:
131
+ #
132
+ # # Print message on $stderr and exit with -1
133
+ # raise Quickl::InvalidOption, "--no-clue option does not exists"
134
+ #
135
+ class InvalidOption < Error
136
+
137
+ def initialize(*args)
138
+ super(*(args + [ -1 ]))
139
+ end
140
+
141
+ def react!
142
+ raise Help.new(self.exit_code), self.message, backtrace
143
+ end
144
+
145
+ end # class InvalidOption
146
+
147
+ #
148
+ # This error can be raised to indicate that some command argument
149
+ # was misused.
150
+ #
151
+ # Default exit code:
152
+ # -1
153
+ #
154
+ # Default reaction:
155
+ # raise Help.new(code), message, backtrace
156
+ #
157
+ # Examples:
158
+ #
159
+ # # Print message on $stderr and exit with -1
160
+ # raise Quickl::InvalidArgument, "Missing command argument"
161
+ #
162
+ class InvalidArgument < Error
163
+
164
+ def initialize(*args)
165
+ super(*(args + [ -1 ]))
166
+ end
167
+
168
+ def react!
169
+ raise Help.new(self.exit_code), self.message, backtrace
170
+ end
171
+
172
+ end # class InvalidArgument
173
+
174
+ #
175
+ # This error can be raised to indicate that a command has not been
176
+ # found (typically by delegates).
177
+ #
178
+ # Default exit code:
179
+ # -1
180
+ #
181
+ # Default reaction:
182
+ # raise Help.new(code), message, backtrace
183
+ #
184
+ class NoSuchCommand < Error
185
+
186
+ def initialize(*args)
187
+ super(*(args + [ -1 ]))
188
+ end
189
+
190
+ def react!
191
+ raise Help.new(self.exit_code), self.message, backtrace
192
+ end
193
+
194
+ end # class NoSuchCommand
195
+
196
+ end # module Quickl
@@ -0,0 +1,29 @@
1
+ class Object
2
+ # From active_support 2.3.4
3
+ unless defined? instance_exec # 1.9
4
+ module InstanceExecMethods #:nodoc:
5
+ end
6
+ include InstanceExecMethods
7
+
8
+ # Evaluate the block with the given arguments within the context of
9
+ # this object, so self is set to the method receiver.
10
+ #
11
+ # From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec
12
+ def instance_exec(*args, &block)
13
+ begin
14
+ old_critical, Thread.critical = Thread.critical, true
15
+ n = 0
16
+ n += 1 while respond_to?(method_name = "__instance_exec#{n}")
17
+ InstanceExecMethods.module_eval { define_method(method_name, &block) }
18
+ ensure
19
+ Thread.critical = old_critical
20
+ end
21
+
22
+ begin
23
+ send(method_name, *args)
24
+ ensure
25
+ InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ module Quickl
2
+ module Naming
3
+
4
+ #
5
+ # Converts a command name to a module name. Implements
6
+ # the following conversions:
7
+ #
8
+ # hello -> Hello
9
+ # hello-world -> HelloWorld
10
+ #
11
+ # This method is part of Quickl's private interface even
12
+ # if its effect are considered public.
13
+ #
14
+ def command2module(name)
15
+ case name
16
+ when String
17
+ name.gsub(/^(.)|-(.)/){|x| x.upcase}.gsub(/-/,'')
18
+ when Symbol
19
+ command2module(name.to_s).to_sym
20
+ else
21
+ raise ArgumentError, "Invalid name argument #{name.class}"
22
+ end
23
+ end
24
+
25
+ #
26
+ # Converts a module name to a command name. Implements the
27
+ # following conversions:
28
+ #
29
+ # Hello -> hello
30
+ # HelloWorld -> hello-world
31
+ #
32
+ # This method is part of Quickl's private interface even
33
+ # if its effect are considered public.
34
+ #
35
+ def module2command(mod)
36
+ case mod
37
+ when Module
38
+ name = RubyTools::class_unqualified_name(mod)
39
+ name = name.gsub(/[A-Z]/){|x| "-#{x.downcase}"}[1..-1]
40
+ when String
41
+ name = mod.to_s
42
+ name = name.gsub(/[A-Z]/){|x| "-#{x.downcase}"}[1..-1]
43
+ when Symbol
44
+ name = mod.to_s
45
+ name = name.gsub(/[A-Z]/){|x| "-#{x.downcase}"}[1..-1]
46
+ name.to_sym
47
+ else
48
+ raise ArgumentError, "Invalid module argument #{mod.class}"
49
+ end
50
+ end
51
+
52
+ end # module Naming
53
+ end # module Quickl