quickl 0.1.1

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