davetron5000-gli 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,133 @@
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 is describes a command line flag that is global and has a default value of <tt>.</tt>. It also
32
+ specified 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> 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
+ end
73
+
74
+ post do |global_options,command,options,args|
75
+ puts "After successful execution of #{command.name}"
76
+ end
77
+
78
+ on_error do |global_options,command,options,args|
79
+ puts "We go an error"
80
+ return true # does the standard error handling code
81
+ # return false # this would skip standard error handling code
82
+ end
83
+
84
+ Now, we run the program using the arguments the user provided on the command line
85
+
86
+ run(ARGV)
87
+
88
+ What this gives you:
89
+
90
+ * 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.
91
+ * Error handling when flags do not receive arguments or unknown flags or switches are given
92
+ * Error handling when an unknown command is specified
93
+ * Default values for flags if they are not specified by the user (switches all default to false)
94
+
95
+ What this doesn't give you:
96
+
97
+ * A way to indicate required flags
98
+ * A way to indicate a require argument or required number of arguments
99
+ * A way to do default switches to 'true' and therefore accept things like <tt>--no-force</tt>
100
+
101
+ = Interface Generated
102
+
103
+ *executable* <i>global options and flags</i> *command* <i>command specific options and flags</i> `arguments`
104
+
105
+ [switch] a command line control string that takes no argument. The <tt>-l</tt> in <tt>ls -l</tt>
106
+ [flag] a switch that takes an argument. The <tt>-d' '</tt> in <tt>cut -d' ' file</tt>
107
+ [command] the command to execute. The <tt>rebase</tt> in <tt>git rebase</tt>
108
+ [arguments] Anything that's not a switch, flag, or command. The <tt>main.c</tt> in <tt>git add main.c</tt>
109
+
110
+ == Switches
111
+
112
+ Switches can be specified one at a time in either a long or short format:
113
+
114
+ git add -i
115
+ git add --interactive
116
+
117
+ Switches can also be combined in their short form:
118
+
119
+ ls -l -a
120
+ ls -la
121
+
122
+ == Flags
123
+
124
+ Flags can be specified in long or short form, and with or without an equals:
125
+
126
+ git merge -s resolve
127
+ git merge --strategy=resolve
128
+
129
+ == Stop Switch
130
+
131
+ A <tt>--</tt> at any time stops processing and sends the rest of the argument to the command as arguments, even if
132
+ they start with a "--"
133
+
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 in which to create 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,266 @@
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.4'
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 global-options,command,options, and arguments
76
+ def on_error(&a_proc)
77
+ @@error_block = a_proc
78
+ end
79
+
80
+ # Runs whatever command is needed based on the arguments.
81
+ def run(args)
82
+ commands[:help] = DefaultHelpCommand.new if !commands[:help]
83
+ begin
84
+ global_options,command,options,arguments = parse_options(args)
85
+ proceed = true
86
+ proceed = @@pre_block.call(global_options,command,options,arguments) if @@pre_block
87
+ if proceed
88
+ command = commands[:help] if !command
89
+ command.execute(global_options,options,arguments)
90
+ @@post_block.call(global_options,command,options,arguments) if @@post_block
91
+ end
92
+ rescue UnknownCommandException, UnknownArgumentException, MissingArgumentException => ex
93
+ regular_error_handling = true
94
+ if @@error_block
95
+ regular_error_handling = @@error_block.call(global_options,command,options,arguments,ex)
96
+ end
97
+
98
+ if regular_error_handling
99
+ puts "error: #{ex}"
100
+ puts
101
+ help = commands[:help]
102
+ help.execute({},{},[])
103
+ end
104
+ end
105
+ end
106
+
107
+ def program_name(override=nil)
108
+ if override
109
+ @@program_name = override
110
+ end
111
+ @@program_name
112
+ end
113
+
114
+ # Returns an array of four values:
115
+ # * global options (as a Hash)
116
+ # * Command
117
+ # * command options (as a Hash)
118
+ # * arguments (as an Array)
119
+ def parse_options(args)
120
+ global_options,command,options,arguments = parse_options_helper(args.clone,Hash.new,nil,Hash.new,Array.new)
121
+ flags.each { |name,flag| global_options[name] = flag.default_value if !global_options[name] }
122
+ command.flags.each { |name,flag| options[name] = flag.default_value if !options[name] }
123
+ return [global_options,command,options,arguments]
124
+ end
125
+
126
+ # Finds the index of the first non-flag
127
+ # argument or -1 if there wasn't one.
128
+ def find_non_flag_index(args)
129
+ args.each_index do |i|
130
+ return i if args[i] =~ /^[^\-]/;
131
+ return i-1 if args[i] =~ /^\-\-$/;
132
+ end
133
+ -1;
134
+ end
135
+
136
+ alias :d :desc
137
+ alias :f :flag
138
+ alias :s :switch
139
+ alias :c :command
140
+
141
+ def clear_nexts
142
+ @@next_desc = nil
143
+ @@next_arg_name = nil
144
+ @@next_default_value = nil
145
+ end
146
+
147
+ clear_nexts
148
+
149
+ def flags; @@flags ||= {}; end
150
+ def switches; @@switches ||= {}; end
151
+ def commands; @@commands ||= {}; end
152
+
153
+ # Recursive helper for parsing command line options
154
+ # [args] the arguments that have yet to be processed
155
+ # [global_options] the global options hash
156
+ # [command] the Command that has been identified (or nil if not identified yet)
157
+ # [command_options] options for Command
158
+ # [arguments] the arguments for Command
159
+ #
160
+ # This works by finding the first non-switch/flag argument, and taking that sublist and trying to pick out
161
+ # flags and switches. After this is done, one of the following is true:
162
+ # * the sublist is empty - in this case, go again, as there might be more flags to parse
163
+ # * the sublist has a flag left in it - unknown flag; we bail
164
+ # * the sublist has a non-flag left in it - this is the command (or the start of the arguments list)
165
+ #
166
+ # This sort does the same thing in two phases; in the first phase, the command hasn't been identified, so
167
+ # we are looking for global switches and flags, ending when we get the command.
168
+ #
169
+ # Once the command has been found, we start looking for command-specific flags and switches.
170
+ # When those have been found, we know the rest of the argument list is arguments for the command
171
+ def parse_options_helper(args,global_options,command,command_options,arguments)
172
+ non_flag_i = find_non_flag_index(args)
173
+ all_flags = false
174
+ if non_flag_i == 0
175
+ # no flags
176
+ if !command
177
+ command_name = args.shift
178
+ command = find_command(command_name)
179
+ raise(UnknownCommandException,"Unknown command '#{command_name}'") if !command
180
+ return parse_options_helper(args,global_options,command,command_options,arguments)
181
+ else
182
+ return global_options,command,command_options,arguments | args
183
+ end
184
+ elsif non_flag_i == -1
185
+ all_flags = true
186
+ end
187
+
188
+ try_me = args[0..non_flag_i]
189
+ rest = args[(non_flag_i+1)..args.length]
190
+ if all_flags
191
+ try_me = args
192
+ rest = []
193
+ end
194
+
195
+ # Suck up whatever options we can
196
+ switch_hash = switches
197
+ flag_hash = flags
198
+ options = global_options
199
+ if command
200
+ switch_hash = command.switches
201
+ flag_hash = command.flags
202
+ options = command_options
203
+ end
204
+
205
+ switch_hash.each do |name,switch|
206
+ value = switch.get_value!(try_me)
207
+ options[name] = value if !options[name]
208
+ end
209
+
210
+ flag_hash.each do |name,flag|
211
+ value = flag.get_value!(try_me)
212
+ options[name] = value if !options[name]
213
+ end
214
+
215
+ if try_me.empty?
216
+ return [global_options,command,command_options,arguments] if rest.empty?
217
+ # If we have no more options we've parsed them all
218
+ # and rest may have more
219
+ return parse_options_helper(rest,global_options,command,command_options,arguments)
220
+ else
221
+ if command
222
+ check = rest
223
+ check = rest | try_me if all_flags
224
+ check.each() do |arg|
225
+ if arg =~ /^\-\-$/
226
+ try_me.delete arg
227
+ break
228
+ end
229
+ raise(UnknownArgumentException,"Unknown argument #{arg}") if arg =~ /^\-/
230
+ end
231
+ return [global_options,command,command_options,try_me | rest]
232
+ else
233
+ # Now we have our command name
234
+ command_name = try_me.shift
235
+ raise(UnknownArgumentException,"Unknown argument #{command_name}") if command_name =~ /^\-/
236
+
237
+ command = find_command(command_name)
238
+ raise(UnknownCommandException,"Unknown command '#{command_name}'") if !command
239
+
240
+ return parse_options_helper(rest,global_options,command,command_options,arguments)
241
+ end
242
+ end
243
+
244
+ end
245
+
246
+ def find_command(name)
247
+ sym = name.to_sym
248
+ return commands[name.to_sym] if commands[sym]
249
+ commands.keys.each do |command_name|
250
+ command = commands[command_name]
251
+ return command if (command.aliases && command.aliases.include?(sym))
252
+ end
253
+ nil
254
+ end
255
+
256
+ # Raise this if you get an argument you were not expecting
257
+ class UnknownArgumentException < Exception
258
+ end
259
+
260
+ class UnknownCommandException < Exception
261
+ end
262
+
263
+ # Raise this if your command doesn't get the number of arguments you were expecting
264
+ class MissingArgumentException < Exception
265
+ end
266
+ 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,95 @@
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.puts '#!/usr/bin/ruby'
24
+ file.puts '$: << File.expand_path(File.dirname(__FILE__) + \'/../lib\')'
25
+ file.puts '$: << File.expand_path(File.dirname(__FILE__) + \'/../ext\')' if create_ext_dir
26
+ file.puts <<EOS
27
+ require 'gli'
28
+
29
+ include GLI
30
+
31
+ desc 'Describe some switch here'
32
+ switch [:s,:switch]
33
+
34
+ desc 'Describe some flag here'
35
+ default_value 'the default'
36
+ arg_name 'The name of the argument'
37
+ flag [:f,:flagname]
38
+ EOS
39
+ commands.each do |command|
40
+ file.puts <<EOS
41
+
42
+ desc 'Describe #{command} here'
43
+ arg_name 'Describe arguments to #{command} here'
44
+ command :#{command} do |c|
45
+ c.desc 'Describe a switch to #{command}'
46
+ c.switch :s
47
+
48
+ c.desc 'Describe a flag to #{command}'
49
+ c.default_value 'default'
50
+ c.flag :s
51
+
52
+ c.action do |global_options,options,args|
53
+ # Your command logic here
54
+ end
55
+ end
56
+ EOS
57
+ end
58
+ puts "Create #{bin_file}"
59
+ end
60
+ end
61
+ else
62
+ puts bin_file + " exists; use --force to override"
63
+ return false
64
+ end
65
+ true
66
+ end
67
+
68
+ def self.mkdirs(dirs,force,dry_run)
69
+ exists = false
70
+ if !force
71
+ dirs.each do |dir|
72
+ if File.exist? dir
73
+ puts "#{dir} exists; use --force to override"
74
+ exists = true
75
+ end
76
+ end
77
+ end
78
+ if !exists
79
+ dirs.each do |dir|
80
+ puts "Creating dir #{dir}..."
81
+ if dry_run
82
+ $stderr.puts "dry-run; #{dir} not created"
83
+ else
84
+ FileUtils.mkdir_p dir
85
+ end
86
+ end
87
+ else
88
+ puts "Exiting..."
89
+ return false
90
+ end
91
+ true
92
+ end
93
+
94
+ end
95
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: davetron5000-gli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - David Copeland
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-03 00:00:00 -08: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:
61
+ rubygems_version: 1.2.0
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: A Git Like Interface for building command line apps
65
+ test_files: []
66
+