gli 0.1.6

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.
@@ -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
+