gli 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ This is a command line parser for a git-like command line client.
2
+
3
+ = Use
4
+
5
+ The simplest way to get started is to create a scaffold project
6
+
7
+ gli init my_proj command_name other_command_name
8
+
9
+ This will create a (very) basic scaffold project in <tt>./my_proj</tt>, with a bare-bones
10
+ main file in <tt>./my_proj/bin/my_proj</tt>. This file demonstrates most of what you need
11
+ to describe your command line interface
12
+
13
+ == More Detail
14
+
15
+ This sets you up to use the DSL that GLI defines:
16
+
17
+ #!/usr/bin/ruby
18
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
19
+
20
+ require 'gli'
21
+
22
+ include GLI
23
+
24
+ This describes a command line switch "-n" that is global to all commands and specified before
25
+ the command name on the command line.
26
+
27
+ desc 'Dry run; don\'t change the disk'
28
+ switch :n
29
+
30
+
31
+ This describes a command line flag that is global and has a default value of '<tt>.</tt>'. It also
32
+ specifies a short description of its argument. This is used to print command line help. Note that we
33
+ have specified two different aliases for this flag. <tt>-r</tt> (because it is listed first) is the default
34
+ one and <tt>--root</tt> (note two-dash syntax) is also supported. This means that <tt>-r some_dir</tt> and <tt>--root=some_dir</tt> mean
35
+ the same thing to the application.
36
+
37
+ desc 'Root dir in which to create project'
38
+ default_value '.'
39
+ arg_name 'root_dir'
40
+ flag [:r,:root]
41
+
42
+ Here we specify a command. Inside the block we can use the same sorts of things as we did above to define flags
43
+ and switches specific to the command. These must come after the command name. Also note that we use <tt>arg_name</tt>
44
+ here to describe the arguments this command accepts.
45
+
46
+ desc 'Create a new GLI-based project'
47
+ arg_name 'project_name [command[ command]*]'
48
+ command [:init,:scaffold] do |c|
49
+
50
+ c.desc 'Create an ext dir'
51
+ c.switch [:e,:ext]
52
+
53
+ c.desc 'Overwrite/ignore existing files and directories'
54
+ c.switch [:force]
55
+
56
+ Here we specify the actual actions to take when the command is executed. We define a block that
57
+ will be given the global options (as a Hash), the command-specific options (as a hash) and the command
58
+ line arguments
59
+
60
+ c.action do |global_options,options,args|
61
+ if args.length < 1
62
+ raise(MissingArgumentException,'You must specify the name of your project')
63
+ end
64
+ Scaffold.create_scaffold(g[:r],!o[:notest],o[:e],args[0],args[1..-1],o[:force],g[:n])
65
+ end
66
+ end
67
+
68
+ You can also specify some global code to run before, after and on errors:
69
+
70
+ pre do |global_options,command,options,args|
71
+ puts "After parsing, but before #{command.name} is run"
72
+ return true
73
+ # return false if we want to skip command execution for some reason
74
+ end
75
+
76
+ post do |global_options,command,options,args|
77
+ puts "After successful execution of #{command.name}"
78
+ end
79
+
80
+ on_error do |global_options,command,options,args|
81
+ puts "We go an error"
82
+ return true # does the standard error handling code
83
+ # return false # this would skip standard error handling code
84
+ end
85
+
86
+ Now, we run the program using the arguments the user provided on the command line
87
+
88
+ run(ARGV)
89
+
90
+ What this gives you:
91
+
92
+ * A reasonably useful help system. <tt>your_program help</tt> will list all the global options and commands (along with command aliases) and <tt>your_program help command_name</tt> will list help for that given command.
93
+ * Error handling when flags do not receive arguments or unknown flags or switches are given
94
+ * Error handling when an unknown command is specified
95
+ * Default values for flags if they are not specified by the user (switches all default to false)
96
+
97
+ What this doesn't give you:
98
+
99
+ * A way to indicate required flags
100
+ * A way to indicate a require argument or required number of arguments
101
+ * A way to do default switches to 'true' and therefore accept things like <tt>--no-force</tt>
102
+
103
+ = Interface Generated
104
+
105
+ *executable* <i>global options and flags</i> *command* <i>command specific options and flags</i> `arguments`
106
+
107
+ [switch] a command line control string that takes no argument. The <tt>-l</tt> in <tt>ls -l</tt>
108
+ [flag] a switch that takes an argument. The <tt>-d' '</tt> in <tt>cut -d' ' file</tt>
109
+ [command] the command to execute. The <tt>rebase</tt> in <tt>git rebase</tt>
110
+ [arguments] Anything that's not a switch, flag, or command. The <tt>main.c</tt> in <tt>git add main.c</tt>
111
+
112
+ == Switches
113
+
114
+ Switches can be specified one at a time in either a long or short format:
115
+
116
+ git add -i
117
+ git add --interactive
118
+
119
+ Switches can also be combined in their short form:
120
+
121
+ ls -l -a
122
+ ls -la
123
+
124
+ == Flags
125
+
126
+ Flags can be specified in long or short form, and with or without an equals:
127
+
128
+ git merge -s resolve
129
+ git merge --strategy=resolve
130
+
131
+ == Stop Switch
132
+
133
+ A <tt>--</tt> at any time stops processing and sends the rest of the argument to the command as arguments, even if
134
+ they start with a "--"
135
+
data/bin/gli ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/ruby
2
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'gli'
5
+ require 'support/scaffold'
6
+
7
+ include GLI
8
+ desc 'Be verbose'
9
+ switch :v
10
+
11
+ desc 'Print version'
12
+ switch :version
13
+
14
+ desc 'Dry run; don\'t change the disk'
15
+ switch :n
16
+
17
+ desc 'Root dir of project'
18
+ default_value '.'
19
+ flag [:r,:root]
20
+
21
+ desc 'Create a new GLI-based project'
22
+ arg_name 'project_name [command[ command]*]'
23
+ command [:init,:scaffold] do |c|
24
+
25
+ c.desc 'Create an ext dir'
26
+ c.switch [:e,:ext]
27
+
28
+ c.desc 'Do not create a test dir'
29
+ c.switch [:notest]
30
+
31
+ c.desc 'Overwrite/ignore existing files and directories'
32
+ c.switch [:force]
33
+
34
+ c.action do |g,o,args|
35
+ if args.length < 1
36
+ raise(MissingArgumentException,'You must specify the name of your project')
37
+ end
38
+ Scaffold.create_scaffold(g[:r],!o[:notest],o[:e],args[0],args[1..-1],o[:force],g[:n])
39
+ end
40
+ end
41
+
42
+ pre do |global,command,options,args|
43
+ if (!command || command.name == :help) && global[:version]
44
+ puts "#{$0} v#{GLI::VERSION}"
45
+ false
46
+ else
47
+ puts "Executing #{command.name}" if global[:v]
48
+ true
49
+ end
50
+ end
51
+
52
+ post do |global,command,options,args|
53
+ puts "Executed #{command.name}" if global[:v]
54
+ end
55
+
56
+ #on_error do |global,command,options,args|
57
+ # puts "Got an error" if global[:v]
58
+ # true
59
+ #end
60
+
61
+ run(ARGV)
@@ -0,0 +1,265 @@
1
+ require 'gli/command_line_token.rb'
2
+ require 'gli/command.rb'
3
+ require 'gli/switch.rb'
4
+ require 'gli/flag.rb'
5
+ require 'support/help.rb'
6
+
7
+ # A means to define and parse a command line interface that works as
8
+ # Git's does, in that you specify global options, a command name, command
9
+ # specific options, and then command arguments.
10
+ module GLI
11
+ extend self
12
+
13
+ VERSION = '0.1.5'
14
+
15
+ @@program_name = $0.split(/\//)[-1]
16
+ @@post_block = nil
17
+ @@pre_block = nil
18
+ @@error_block = nil
19
+
20
+ # Reset the GLI module internal data structures; mostly for testing
21
+ def reset
22
+ switches.clear
23
+ flags.clear
24
+ commands.clear
25
+ clear_nexts
26
+ end
27
+
28
+ # describe the next switch, flag, or command
29
+ def desc(description); @@next_desc = description; end
30
+ # describe the argument name of the next flag
31
+ def arg_name(name); @@next_arg_name = name; end
32
+ # set the default value of the next flag
33
+ def default_value(val); @@next_default_value = val; end
34
+
35
+ # Create a flag, which is a switch that takes an argument
36
+ def flag(names)
37
+ flag = Flag.new(names,@@next_desc,@@next_arg_name,@@next_default_value)
38
+ flags[flag.name] = flag
39
+ clear_nexts
40
+ end
41
+
42
+ # Create a switch
43
+ def switch(names)
44
+ switch = Switch.new(names,@@next_desc)
45
+ switches[switch.name] = switch
46
+ clear_nexts
47
+ end
48
+
49
+ # Define a command.
50
+ def command(names)
51
+ command = Command.new(names,@@next_desc,@@next_arg_name)
52
+ commands[command.name] = command
53
+ yield command
54
+ clear_nexts
55
+ end
56
+
57
+ # Define a block to run after command line arguments are parsed
58
+ # but before any command is run. If this block raises an exception
59
+ # the command specified will not be executed.
60
+ # The block will receive the global-options,command,options, and arguments
61
+ # If this block evaluates to true, the program will proceed; otherwise
62
+ # the program will end immediately
63
+ def pre(&a_proc)
64
+ @@pre_block = a_proc
65
+ end
66
+
67
+ # Define a block to run after command hase been executed, only
68
+ # if there was not an error.
69
+ # The block will receive the global-options,command,options, and arguments
70
+ def post(&a_proc)
71
+ @@post_block = a_proc
72
+ end
73
+
74
+ # Define a block to run if an error occurs.
75
+ # The block will receive the exception that was caught.
76
+ # It should return false to avoid the built-in error handling
77
+ def on_error(&a_proc)
78
+ @@error_block = a_proc
79
+ end
80
+
81
+ # Runs whatever command is needed based on the arguments.
82
+ def run(args)
83
+ commands[:help] = DefaultHelpCommand.new if !commands[:help]
84
+ begin
85
+ global_options,command,options,arguments = parse_options(args)
86
+ proceed = true
87
+ proceed = @@pre_block.call(global_options,command,options,arguments) if @@pre_block
88
+ if proceed
89
+ command = commands[:help] if !command
90
+ command.execute(global_options,options,arguments)
91
+ @@post_block.call(global_options,command,options,arguments) if @@post_block
92
+ end
93
+ rescue UnknownCommandException, UnknownArgumentException, MissingArgumentException => ex
94
+ regular_error_handling = true
95
+ regular_error_handling = @@error_block.call(ex) if @@error_block
96
+
97
+ if regular_error_handling
98
+ puts "error: #{ex}"
99
+ puts
100
+ help = commands[:help]
101
+ help.execute({},{},[])
102
+ end
103
+ end
104
+ end
105
+
106
+ def program_name(override=nil)
107
+ if override
108
+ @@program_name = override
109
+ end
110
+ @@program_name
111
+ end
112
+
113
+ # Returns an array of four values:
114
+ # * global options (as a Hash)
115
+ # * Command
116
+ # * command options (as a Hash)
117
+ # * arguments (as an Array)
118
+ def parse_options(args)
119
+ global_options,command,options,arguments = parse_options_helper(args.clone,Hash.new,nil,Hash.new,Array.new)
120
+ flags.each { |name,flag| global_options[name] = flag.default_value if !global_options[name] }
121
+ command.flags.each { |name,flag| options[name] = flag.default_value if !options[name] }
122
+ return [global_options,command,options,arguments]
123
+ end
124
+
125
+ # Finds the index of the first non-flag
126
+ # argument or -1 if there wasn't one.
127
+ def find_non_flag_index(args)
128
+ args.each_index do |i|
129
+ return i if args[i] =~ /^[^\-]/;
130
+ return i-1 if args[i] =~ /^\-\-$/;
131
+ end
132
+ -1;
133
+ end
134
+
135
+ alias :d :desc
136
+ alias :f :flag
137
+ alias :s :switch
138
+ alias :c :command
139
+
140
+ def clear_nexts
141
+ @@next_desc = nil
142
+ @@next_arg_name = nil
143
+ @@next_default_value = nil
144
+ end
145
+
146
+ clear_nexts
147
+
148
+ def flags; @@flags ||= {}; end
149
+ def switches; @@switches ||= {}; end
150
+ def commands; @@commands ||= {}; end
151
+
152
+ # Recursive helper for parsing command line options
153
+ # [args] the arguments that have yet to be processed
154
+ # [global_options] the global options hash
155
+ # [command] the Command that has been identified (or nil if not identified yet)
156
+ # [command_options] options for Command
157
+ # [arguments] the arguments for Command
158
+ #
159
+ # This works by finding the first non-switch/flag argument, and taking that sublist and trying to pick out
160
+ # flags and switches. After this is done, one of the following is true:
161
+ # * the sublist is empty - in this case, go again, as there might be more flags to parse
162
+ # * the sublist has a flag left in it - unknown flag; we bail
163
+ # * the sublist has a non-flag left in it - this is the command (or the start of the arguments list)
164
+ #
165
+ # This sort does the same thing in two phases; in the first phase, the command hasn't been identified, so
166
+ # we are looking for global switches and flags, ending when we get the command.
167
+ #
168
+ # Once the command has been found, we start looking for command-specific flags and switches.
169
+ # When those have been found, we know the rest of the argument list is arguments for the command
170
+ def parse_options_helper(args,global_options,command,command_options,arguments)
171
+ non_flag_i = find_non_flag_index(args)
172
+ all_flags = false
173
+ if non_flag_i == 0
174
+ # no flags
175
+ if !command
176
+ command_name = args.shift
177
+ command = find_command(command_name)
178
+ raise(UnknownCommandException,"Unknown command '#{command_name}'") if !command
179
+ return parse_options_helper(args,global_options,command,command_options,arguments)
180
+ else
181
+ return global_options,command,command_options,arguments | args
182
+ end
183
+ elsif non_flag_i == -1
184
+ all_flags = true
185
+ end
186
+
187
+ try_me = args[0..non_flag_i]
188
+ rest = args[(non_flag_i+1)..args.length]
189
+ if all_flags
190
+ try_me = args
191
+ rest = []
192
+ end
193
+
194
+ # Suck up whatever options we can
195
+ switch_hash = switches
196
+ flag_hash = flags
197
+ options = global_options
198
+ if command
199
+ switch_hash = command.switches
200
+ flag_hash = command.flags
201
+ options = command_options
202
+ end
203
+
204
+ switch_hash.each do |name,switch|
205
+ value = switch.get_value!(try_me)
206
+ options[name] = value if !options[name]
207
+ end
208
+
209
+ flag_hash.each do |name,flag|
210
+ value = flag.get_value!(try_me)
211
+ options[name] = value if !options[name]
212
+ end
213
+
214
+ if try_me.empty?
215
+ return [global_options,command,command_options,arguments] if rest.empty?
216
+ # If we have no more options we've parsed them all
217
+ # and rest may have more
218
+ return parse_options_helper(rest,global_options,command,command_options,arguments)
219
+ else
220
+ if command
221
+ check = rest
222
+ check = rest | try_me if all_flags
223
+ check.each() do |arg|
224
+ if arg =~ /^\-\-$/
225
+ try_me.delete arg
226
+ break
227
+ end
228
+ raise(UnknownArgumentException,"Unknown argument #{arg}") if arg =~ /^\-/
229
+ end
230
+ return [global_options,command,command_options,try_me | rest]
231
+ else
232
+ # Now we have our command name
233
+ command_name = try_me.shift
234
+ raise(UnknownArgumentException,"Unknown argument #{command_name}") if command_name =~ /^\-/
235
+
236
+ command = find_command(command_name)
237
+ raise(UnknownCommandException,"Unknown command '#{command_name}'") if !command
238
+
239
+ return parse_options_helper(rest,global_options,command,command_options,arguments)
240
+ end
241
+ end
242
+
243
+ end
244
+
245
+ def find_command(name)
246
+ sym = name.to_sym
247
+ return commands[name.to_sym] if commands[sym]
248
+ commands.keys.each do |command_name|
249
+ command = commands[command_name]
250
+ return command if (command.aliases && command.aliases.include?(sym))
251
+ end
252
+ nil
253
+ end
254
+
255
+ # Raise this if you get an argument you were not expecting
256
+ class UnknownArgumentException < Exception
257
+ end
258
+
259
+ class UnknownCommandException < Exception
260
+ end
261
+
262
+ # Raise this if your command doesn't get the number of arguments you were expecting
263
+ class MissingArgumentException < Exception
264
+ end
265
+ end
@@ -0,0 +1,71 @@
1
+ require 'gli/command_line_token.rb'
2
+
3
+ module GLI
4
+ # A command to be run, in context of global flags and switches
5
+ class Command < CommandLineToken
6
+
7
+ # Create a new command
8
+ #
9
+ # [names] the name or names of this command (symbol or Array of symbols)
10
+ # [description] description of this command
11
+ # [arguments_name] description of the arguments, or nil if this command doesn't take arguments
12
+ #
13
+ def initialize(names,description,arguments_name=nil)
14
+ super(names,description)
15
+ @arguments_description = arguments_name || ''
16
+ clear_nexts
17
+ end
18
+
19
+ def names
20
+ all_forms
21
+ end
22
+
23
+ def usage
24
+ usage = name.to_s
25
+ usage += ' [options]' if !flags.empty? || !switches.empty?
26
+ usage += ' ' + @arguments_description if @arguments_description
27
+ usage
28
+ end
29
+
30
+ def flags; @flags ||= {}; end
31
+ def switches; @switches ||= {}; end
32
+
33
+ # describe the next switch or flag
34
+ def desc(description); @next_desc = description; end
35
+ # describe the argument name of the next flag
36
+ def arg_name(name); @next_arg_name = name; end
37
+ # set the default value of the next flag
38
+ def default_value(val); @next_default_value = val; end
39
+
40
+ def flag(names)
41
+ flag = Flag.new(names,@next_desc,@next_arg_name,@next_default_value)
42
+ flags[flag.name] = flag
43
+ clear_nexts
44
+ end
45
+
46
+ # Create a switch
47
+ def switch(names)
48
+ switch = Switch.new(names,@next_desc)
49
+ switches[switch.name] = switch
50
+ clear_nexts
51
+ end
52
+
53
+ def action(&block)
54
+ @action = block
55
+ end
56
+
57
+ def self.name_as_string(name)
58
+ name.to_s
59
+ end
60
+
61
+ def clear_nexts
62
+ @next_desc = nil
63
+ @next_arg_name = nil
64
+ @next_default_value = nil
65
+ end
66
+
67
+ def execute(global_options,options,arguments)
68
+ @action.call(global_options,options,arguments)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,47 @@
1
+ module GLI
2
+ # Logical element of a command line, mostly so that subclasses can have similar
3
+ # initialization and interface
4
+ class CommandLineToken
5
+ attr_reader :name
6
+ attr_reader :aliases
7
+ attr_reader :description
8
+
9
+ def initialize(names,description)
10
+ @description = description
11
+ @name,@aliases,@names = parse_names(names)
12
+ end
13
+
14
+ def usage
15
+ all_forms
16
+ end
17
+
18
+ private
19
+ # Returns a string of all possible forms
20
+ # of this flag. Mostly intended for printing
21
+ # to the user.
22
+ def all_forms(joiner=', ')
23
+ forms = all_forms_a
24
+ forms.join(joiner)
25
+ end
26
+
27
+
28
+ # Handles dealing with the "names" param, parsing
29
+ # it into the primary name and aliases list
30
+ def parse_names(names)
31
+ names_hash = Hash.new
32
+ names = names.is_a?(Array) ? names : [names]
33
+ names.each { |n| names_hash[self.class.name_as_string(n)] = true }
34
+ name = names.shift
35
+ aliases = names.length > 0 ? names : nil
36
+ [name,aliases,names_hash]
37
+ end
38
+
39
+ def all_forms_a
40
+ forms = [self.class.name_as_string(name)]
41
+ if aliases
42
+ forms |= aliases.collect { |a| self.class.name_as_string(a) }.sort { |x,y| y.length <=> x.length }
43
+ end
44
+ forms
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,66 @@
1
+ require 'gli/command_line_token.rb'
2
+
3
+ module GLI
4
+ # Defines a flag, which is to say a switch that takes an argument
5
+ class Flag < Switch
6
+
7
+ attr_reader :default_value
8
+
9
+ def initialize(names,description,argument_name=nil,default=nil)
10
+ super(names,description)
11
+ @argument_name = argument_name || "arg"
12
+ @default_value = default
13
+ end
14
+
15
+ def get_value!(args)
16
+ args.each_index() do |index|
17
+ arg = args[index]
18
+ present,matched,value = find_me(arg)
19
+ if present
20
+ args.delete_at index
21
+ if !value || value == ''
22
+ if args[index]
23
+ value = args[index]
24
+ args.delete_at index
25
+ return value
26
+ else
27
+ raise(MissingArgumentException,"#{matched} requires an argument")
28
+ end
29
+ else
30
+ return value
31
+ end
32
+ end
33
+ end
34
+ return @default_value
35
+ end
36
+
37
+ def find_me(arg)
38
+ if @names[arg]
39
+ return [true,arg,nil] if arg.length == 2
40
+ # This means we matched the long-form, but there's no argument
41
+ raise(MissingArgumentException,"#{arg} requires an argument via #{arg}=argument")
42
+ end
43
+ @names.keys.each() do |name|
44
+ match_string = "^#{name}=(.*)$"
45
+ match_data = arg.match(match_string)
46
+ return [true,name,$1] if match_data;
47
+ end
48
+ [false,nil,nil]
49
+ end
50
+
51
+ # Returns a string of all possible forms
52
+ # of this flag. Mostly intended for printing
53
+ # to the user.
54
+ def all_forms(joiner=', ')
55
+ forms = all_forms_a
56
+ string = forms.join(joiner)
57
+ if forms[-1] =~ /^\-\-/
58
+ string += '='
59
+ else
60
+ string += ' '
61
+ end
62
+ string += @argument_name
63
+ return string
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,56 @@
1
+ require 'gli/command_line_token.rb'
2
+
3
+ module GLI
4
+ # Defines a command line switch
5
+ class Switch < CommandLineToken
6
+
7
+ def initialize(names,description)
8
+ super(names,description)
9
+ end
10
+
11
+ # Given the argument list, scans it looking for this switch
12
+ # returning true if it's in the argumennt list (and removing it from the argument list)
13
+ def get_value!(args)
14
+ idx = -1
15
+ args.each_index do |i|
16
+ result = find_me(args[i])
17
+ if result[0]
18
+ if result[1]
19
+ args[i] = result[1]
20
+ else
21
+ args.delete_at i
22
+ end
23
+ return result[0]
24
+ end
25
+ end
26
+ false
27
+ end
28
+
29
+ # Finds the switch in the given arg, returning the arg to keep.
30
+ # Returns an array of size 2:
31
+ # [0] true or false if the arg was found
32
+ # [1] the remaining arg to keep in the command line or nil to remove it
33
+ def find_me(arg)
34
+ if @names[arg]
35
+ return [true,nil]
36
+ end
37
+ @names.keys.each() do |name|
38
+ if name =~ /^-(\w)$/
39
+ match_string = "^\\-(\\w*)#{$1}(\\w*)$"
40
+ match_data = arg.match(match_string)
41
+ if match_data
42
+ # Note that if [1] and [2] were both empty
43
+ # we'd have returned above
44
+ return [true, "-" + match_data[1] + match_data[2]]
45
+ end
46
+ end
47
+ end
48
+ [false]
49
+ end
50
+
51
+ def self.name_as_string(name)
52
+ string = name.to_s
53
+ string.length == 1 ? "-#{string}" : "--#{string}"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,112 @@
1
+ require 'gli'
2
+ require 'gli/command'
3
+
4
+ module GLI
5
+ class DefaultHelpCommand < Command
6
+ def initialize
7
+ super(:help,'Shows list of commands or help for one command','[command]')
8
+ end
9
+
10
+ def execute(global_options,options,arguments)
11
+ if arguments.empty?
12
+ list_global_flags
13
+ list_commands
14
+ else
15
+ list_one_command_help(arguments[0])
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def list_global_flags
22
+ usage = "usage: #{GLI.program_name} command"
23
+ all_options = GLI.switches.merge(GLI.flags)
24
+ if !all_options.empty?
25
+ usage += ' [options]'
26
+ end
27
+ puts usage
28
+ puts
29
+ puts 'Options:' if !all_options.empty?
30
+ output_command_tokens_for_help(all_options)
31
+ puts if !all_options.empty?
32
+ end
33
+
34
+ def list_commands
35
+ puts 'Commands:'
36
+ output_command_tokens_for_help(GLI.commands,:names)
37
+ end
38
+
39
+ def list_one_command_help(command_name)
40
+ command = GLI.find_command(command_name)
41
+ if command
42
+ puts command.usage
43
+ description = wrap(command.description,4)
44
+ puts " #{description}"
45
+ all_options = command.switches.merge(command.flags)
46
+ if !all_options.empty?
47
+ puts
48
+ puts "Options:"
49
+ output_command_tokens_for_help(all_options)
50
+ end
51
+ else
52
+ puts "No such command #{command_name}"
53
+ end
54
+ end
55
+
56
+ def output_command_tokens_for_help(tokens,usage_name=:usage)
57
+ max = 0
58
+ tokens.values.each do |token|
59
+ len = token.send(usage_name).length
60
+ if len > max
61
+ max = len
62
+ end
63
+ end
64
+ names = tokens.keys.sort { |x,y| x.to_s <=> y.to_s }
65
+ names.each do |name|
66
+ token = tokens[name]
67
+ description = token.description || ''
68
+ if token.kind_of? Flag
69
+ description += " (default: #{token.default_value})" if token.default_value
70
+ end
71
+ description = wrap(description,max+7)
72
+ printf " %-#{max}s - %s\n",token.send(usage_name),description
73
+ end
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # Wraps the line at the given column length, using the given line padding.
80
+ # Assumes that the first line doesn't need the padding, as its filled
81
+ # up with other stuff
82
+ def wrap(line,pad_length=0,line_length=80)
83
+ line_padding = sprintf("%#{pad_length}s",'')
84
+ words = line.split(/\s+/)
85
+ return line if !words || words.empty?
86
+ wrapped = ''
87
+ while wrapped.length + line_padding.length < line_length
88
+ wrapped += ' ' if wrapped.length > 0
89
+ word = words.shift
90
+ if (wrapped.length + line_padding.length + word.length > line_length)
91
+ words.unshift word
92
+ break;
93
+ end
94
+ wrapped += word
95
+ return wrapped if words.empty?
96
+ end
97
+ wrapped += "\n"
98
+ this_line = line_padding
99
+ words.each do |word|
100
+ if this_line.length + word.length > line_length
101
+ wrapped += this_line
102
+ wrapped += "\n"
103
+ this_line = line_padding + word
104
+ else
105
+ this_line += ' ' if this_line.length > line_padding.length
106
+ this_line += word
107
+ end
108
+ end
109
+ wrapped.chomp!
110
+ wrapped + "\n" + this_line
111
+ end
112
+ end
@@ -0,0 +1,127 @@
1
+ require 'gli'
2
+ require 'fileutils'
3
+
4
+ module GLI
5
+ class Scaffold
6
+
7
+ def self.create_scaffold(root_dir,create_test_dir,create_ext_dir,project_name,commands,force=false,dry_run=false)
8
+ dirs = [File.join(root_dir,project_name,'lib')]
9
+ dirs << File.join(root_dir,project_name,'bin')
10
+ dirs << File.join(root_dir,project_name,'test') if create_test_dir
11
+ dirs << File.join(root_dir,project_name,'ext') if create_ext_dir
12
+
13
+ if mkdirs(dirs,force,dry_run)
14
+ mk_binfile(root_dir,create_ext_dir,force,dry_run,project_name,commands)
15
+ end
16
+ end
17
+
18
+ def self.mk_binfile(root_dir,create_ext_dir,force,dry_run,project_name,commands)
19
+ bin_file = File.join(root_dir,project_name,'bin',project_name)
20
+ if !File.exist?(bin_file) || force
21
+ if !dry_run
22
+ File.open(bin_file,'w') do |file|
23
+ file.chmod(0755)
24
+ file.puts '#!/usr/bin/ruby'
25
+ file.puts '$: << File.expand_path(File.dirname(__FILE__) + \'/../lib\')'
26
+ file.puts '$: << File.expand_path(File.dirname(__FILE__) + \'/../ext\')' if create_ext_dir
27
+ file.puts <<EOS
28
+ require 'rubygems'
29
+ require 'gli'
30
+
31
+ include GLI
32
+
33
+ desc 'Describe some switch here'
34
+ switch [:s,:switch]
35
+
36
+ desc 'Describe some flag here'
37
+ default_value 'the default'
38
+ arg_name 'The name of the argument'
39
+ flag [:f,:flagname]
40
+ EOS
41
+ first = true
42
+ commands.each do |command|
43
+ if first
44
+ file.puts <<EOS
45
+
46
+ desc 'Describe #{command} here'
47
+ arg_name 'Describe arguments to #{command} here'
48
+ command :#{command} do |c|
49
+ c.desc 'Describe a switch to #{command}'
50
+ c.switch :s
51
+
52
+ c.desc 'Describe a flag to #{command}'
53
+ c.default_value 'default'
54
+ c.flag :s
55
+ c.action do |global_options,options,args|
56
+ # Your command logic here
57
+ end
58
+ end
59
+ EOS
60
+ else
61
+ file.puts <<EOS
62
+ command :#{command} do |c|
63
+ c.action do |global_options,options,args|
64
+ end
65
+ end
66
+ EOS
67
+ end
68
+ first = false
69
+ end
70
+ file.puts <<EOS
71
+ pre do |global,command,options,args|
72
+ # Pre logic here
73
+ # Return true to proceed; false to abourt and not call the
74
+ # chosen command
75
+ true
76
+ end
77
+
78
+ post do |global,command,options,args|
79
+ # Post logic here
80
+ end
81
+
82
+ on_error do |exception|
83
+ # Error logic here
84
+ # return false to skip default error handling
85
+ true
86
+ end
87
+
88
+ GLI.run(ARGV)
89
+ EOS
90
+ puts "Created #{bin_file}"
91
+ end
92
+ end
93
+ else
94
+ puts bin_file + " exists; use --force to override"
95
+ return false
96
+ end
97
+ true
98
+ end
99
+
100
+ def self.mkdirs(dirs,force,dry_run)
101
+ exists = false
102
+ if !force
103
+ dirs.each do |dir|
104
+ if File.exist? dir
105
+ puts "#{dir} exists; use --force to override"
106
+ exists = true
107
+ end
108
+ end
109
+ end
110
+ if !exists
111
+ dirs.each do |dir|
112
+ puts "Creating dir #{dir}..."
113
+ if dry_run
114
+ $stderr.puts "dry-run; #{dir} not created"
115
+ else
116
+ FileUtils.mkdir_p dir
117
+ end
118
+ end
119
+ else
120
+ puts "Exiting..."
121
+ return false
122
+ end
123
+ true
124
+ end
125
+
126
+ end
127
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ platform: ruby
6
+ authors:
7
+ - David Copeland
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-19 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: davidcopeland@naildrivin5.com
18
+ executables:
19
+ - gli
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - lib/gli/command.rb
26
+ - lib/gli/command_line_token.rb
27
+ - lib/gli/flag.rb
28
+ - lib/gli/switch.rb
29
+ - lib/gli.rb
30
+ - lib/support/help.rb
31
+ - lib/support/scaffold.rb
32
+ - bin/gli
33
+ - README.rdoc
34
+ has_rdoc: true
35
+ homepage: http://davetron5000.github.com/gli
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --title
39
+ - Git Like Interface
40
+ - --main
41
+ - README.rdoc
42
+ - -ri
43
+ require_paths:
44
+ - lib
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project: gli
61
+ rubygems_version: 1.3.1
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: A Git Like Interface for building command line apps
65
+ test_files: []
66
+