coglius 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Cogfile +15 -0
  2. data/LICENSE +201 -0
  3. data/lib/coglius.rb +29 -0
  4. data/lib/coglius/app.rb +250 -0
  5. data/lib/coglius/app_support.rb +284 -0
  6. data/lib/coglius/command.rb +149 -0
  7. data/lib/coglius/command_line_option.rb +34 -0
  8. data/lib/coglius/command_line_token.rb +62 -0
  9. data/lib/coglius/command_support.rb +214 -0
  10. data/lib/coglius/commands/compound_command.rb +42 -0
  11. data/lib/coglius/commands/doc.rb +215 -0
  12. data/lib/coglius/commands/help.rb +73 -0
  13. data/lib/coglius/commands/help_modules/arg_name_formatter.rb +20 -0
  14. data/lib/coglius/commands/help_modules/command_finder.rb +60 -0
  15. data/lib/coglius/commands/help_modules/command_help_format.rb +138 -0
  16. data/lib/coglius/commands/help_modules/global_help_format.rb +70 -0
  17. data/lib/coglius/commands/help_modules/help_completion_format.rb +31 -0
  18. data/lib/coglius/commands/help_modules/list_formatter.rb +23 -0
  19. data/lib/coglius/commands/help_modules/one_line_wrapper.rb +18 -0
  20. data/lib/coglius/commands/help_modules/options_formatter.rb +49 -0
  21. data/lib/coglius/commands/help_modules/text_wrapper.rb +53 -0
  22. data/lib/coglius/commands/help_modules/tty_only_wrapper.rb +23 -0
  23. data/lib/coglius/commands/help_modules/verbatim_wrapper.rb +16 -0
  24. data/lib/coglius/commands/initconfig.rb +69 -0
  25. data/lib/coglius/commands/rdoc_document_listener.rb +116 -0
  26. data/lib/coglius/commands/scaffold.rb +401 -0
  27. data/lib/coglius/copy_options_to_aliases.rb +33 -0
  28. data/lib/coglius/dsl.rb +221 -0
  29. data/lib/coglius/exceptions.rb +54 -0
  30. data/lib/coglius/flag.rb +68 -0
  31. data/lib/coglius/gli_option_parser.rb +124 -0
  32. data/lib/coglius/option_parser_factory.rb +45 -0
  33. data/lib/coglius/options.rb +23 -0
  34. data/lib/coglius/switch.rb +35 -0
  35. data/lib/coglius/terminal.rb +94 -0
  36. data/lib/coglius/version.rb +5 -0
  37. data/templates/coglius/generator.rb.erb +26 -0
  38. metadata +208 -0
@@ -0,0 +1,33 @@
1
+ module Coglius
2
+
3
+ # Mixin that both Coglius and Command can use to copy command-line options to the aliased versions
4
+ # of flags and switches
5
+ #
6
+ # includers must provide the methods +flags+ and +switches+ that return an Array of Flag or Switch,
7
+ # respectively
8
+ module CopyOptionsToAliases # :nodoc:
9
+ # For each option in options, copies its value to keys for the aliases of the flags or
10
+ # switches in gli_like
11
+ #
12
+ # options - Hash of options parsed from command line; this is an I/O param
13
+ def copy_options_to_aliases(options) # :nodoc:
14
+ new_options = {}
15
+ options.each do |key,value|
16
+ if flags[key] && flags[key].aliases
17
+ copy_aliases(flags[key].aliases,new_options,value)
18
+ elsif switches[key] && switches[key].aliases
19
+ copy_aliases(switches[key].aliases,new_options,value)
20
+ end
21
+ end
22
+ options.merge!(new_options)
23
+ end
24
+
25
+ private
26
+
27
+ def copy_aliases(aliases,new_options,value)
28
+ aliases.each do |alias_name|
29
+ new_options[alias_name] = value
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,221 @@
1
+ module Coglius
2
+ # The primary DSL for Coglius. This represents the methods shared between your top-level app and
3
+ # the commands. See Coglius::Command for additional methods that apply only to command objects.
4
+ module DSL
5
+ # Describe the next switch, flag, or command. This should be a
6
+ # short, one-line description
7
+ #
8
+ # +description+:: A String of the short descripiton of the switch, flag, or command following
9
+ def desc(description); @next_desc = description; end
10
+ alias :d :desc
11
+
12
+ # Provide a longer, more detailed description. This
13
+ # will be reformatted and wrapped to fit in the terminal's columns
14
+ #
15
+ # +long_desc+:: A String that is s longer description of the switch, flag, or command following.
16
+ def long_desc(long_desc); @next_long_desc = long_desc; end
17
+
18
+ # Describe the argument name of the next flag. It's important to keep
19
+ # this VERY short and, ideally, without any spaces (see Example).
20
+ #
21
+ # +name+:: A String that *briefly* describes the argument given to the following command or flag.
22
+ # +options+:: Symbol or array of symbols to annotate this argument. This doesn't affect parsing, just
23
+ # the help output. Values recognized are:
24
+ # +:optional+:: indicates this argument is optional; will format it with square brackets
25
+ # +:multiple+:: indicates multiple values are accepted; will format appropriately
26
+ #
27
+ # Example:
28
+ # desc 'Set the filename'
29
+ # arg_name 'file_name'
30
+ # flag [:f,:filename]
31
+ #
32
+ # Produces:
33
+ # -f, --filename=file_name Set the filename
34
+ def arg_name(name,options=[])
35
+ @next_arg_name = name
36
+ @next_arg_options = options
37
+ end
38
+
39
+ # set the default value of the next flag or switch
40
+ #
41
+ # +val+:: The default value to be used for the following flag if the user doesn't specify one
42
+ # and, when using a config file, the config also doesn't specify one. For a switch, this is
43
+ # the value to be used if the switch isn't specified on the command-line. Note that if you
44
+ # set a switch to have a default of true, using the switch on the command-line has no effect.
45
+ # To disable a switch where the default is true, use the <tt>--no-</tt> form.
46
+ def default_value(val); @next_default_value = val; end
47
+
48
+ # Create a flag, which is a switch that takes an argument
49
+ #
50
+ # +names+:: a String or Symbol, or an Array of String or Symbol that represent all the different names
51
+ # and aliases for this flag. The last element can be a hash of options:
52
+ # +:desc+:: the description, instead of using #desc
53
+ # +:long_desc+:: the long_description, instead of using #long_desc
54
+ # +:default_value+:: the default value, instead of using #default_value
55
+ # +:arg_name+:: the arg name, instead of using #arg_name
56
+ # +:must_match+:: A regexp that the flag's value must match
57
+ # +:type+:: A Class (or object you passed to Coglius::App#accept) to trigger type coversion
58
+ #
59
+ # Example:
60
+ #
61
+ # desc 'Set the filename'
62
+ # flag [:f,:filename,'file-name']
63
+ #
64
+ # flag :ipaddress, :desc => "IP Address", :must_match => /\d+\.\d+\.\d+\.\d+/
65
+ #
66
+ # flag :names, :desc => "list of names", :type => Array
67
+ #
68
+ # Produces:
69
+ #
70
+ # -f, --filename, --file-name=arg Set the filename
71
+ def flag(*names)
72
+ options = extract_options(names)
73
+ names = [names].flatten
74
+
75
+ verify_unused(names)
76
+ flag = Flag.new(names,options)
77
+ flags[flag.name] = flag
78
+
79
+ clear_nexts
80
+ flags_declaration_order << flag
81
+ flag
82
+ end
83
+ alias :f :flag
84
+
85
+ # Create a switch, which is a command line flag that takes no arguments (thus, it _switches_ something on)
86
+ #
87
+ # +names+:: a String or Symbol, or an Array of String or Symbol that represent all the different names
88
+ # and aliases for this switch. The last element can be a hash of options:
89
+ # +:desc+:: the description, instead of using #desc
90
+ # +:long_desc+:: the long_description, instead of using #long_desc
91
+ # +:default_value+:: if the switch is omitted, use this as the default value. By default, switches default to off, or +false+
92
+ # +:negatable+:: if true, this switch will get a negatable form (e.g. <tt>--[no-]switch</tt>, false it will not. Default is true
93
+ def switch(*names)
94
+ options = extract_options(names)
95
+ names = [names].flatten
96
+
97
+ verify_unused(names)
98
+ switch = Switch.new(names,options)
99
+ switches[switch.name] = switch
100
+
101
+ clear_nexts
102
+ switches_declaration_order << switch
103
+ switch
104
+ end
105
+ alias :s :switch
106
+
107
+ def clear_nexts # :nodoc:
108
+ @next_desc = nil
109
+ @next_arg_name = nil
110
+ @next_arg_options = nil
111
+ @next_default_value = nil
112
+ @next_long_desc = nil
113
+ end
114
+
115
+ # Define a new command. This can be done in a few ways, but the most common method is
116
+ # to pass a symbol (or Array of symbols) representing the command name (or names) and a block.
117
+ # The block will be given an instance of the Command that was created.
118
+ # You then may call methods on this object to define aspects of that Command.
119
+ #
120
+ # Alternatively, you can call this with a one element Hash, where the key is the symbol representing the name
121
+ # of the command, and the value being an Array of symbols representing the commands to call in order, as a
122
+ # chained or compound command. Note that these commands must exist already, and that only those command-specific
123
+ # options defined in *this* command will be parsed and passed to the chained commands. This might not be what you expect
124
+ #
125
+ # +names+:: a String or Symbol, or an Array of String or Symbol that represent all the different names and aliases
126
+ # for this command *or* a Hash, as described above.
127
+ #
128
+ # ==Examples
129
+ #
130
+ # # Make a command named list
131
+ # command :list do |c|
132
+ # c.action do |global_options,options,args|
133
+ # # your command code
134
+ # end
135
+ # end
136
+ #
137
+ # # Make a command named list, callable by ls as well
138
+ # command [:list,:ls] do |c|
139
+ # c.action do |global_options,options,args|
140
+ # # your command code
141
+ # end
142
+ # end
143
+ #
144
+ # # Make a command named all, that calls list and list_contexts
145
+ # command :all => [ :list, :list_contexts ]
146
+ #
147
+ # # Make a command named all, aliased as :a:, that calls list and list_contexts
148
+ # command [:all,:a] => [ :list, :list_contexts ]
149
+ #
150
+ def command(*names)
151
+ command_options = {
152
+ :description => @next_desc,
153
+ :arguments_name => @next_arg_name,
154
+ :arguments_options => @next_arg_options,
155
+ :long_desc => @next_long_desc,
156
+ :skips_pre => @skips_pre,
157
+ :skips_post => @skips_post,
158
+ :skips_around => @skips_around,
159
+ }
160
+ if names.first.kind_of? Hash
161
+ command = Coglius::Commands::CompoundCommand.new(self,
162
+ names.first,
163
+ command_options)
164
+ command.parent = self
165
+ commands[command.name] = command
166
+ else
167
+ command = Command.new(command_options.merge(:names => [names].flatten))
168
+ command.parent = self
169
+ commands[command.name] = command
170
+ yield command
171
+ end
172
+ @commands_declaration_order ||= []
173
+ @commands_declaration_order << command
174
+ clear_nexts
175
+ end
176
+ alias :c :command
177
+
178
+ def flags_declaration_order # :nodoc:
179
+ @flags_declaration_order ||= []
180
+ end
181
+
182
+ def switches_declaration_order # :nodoc:
183
+ @switches_declaration_order ||= []
184
+ end
185
+
186
+
187
+ private
188
+ # Checks that the names passed in have not been used in another flag or option
189
+ def verify_unused(names) # :nodoc:
190
+ names.each do |name|
191
+ verify_unused_in_option(name,flags,"flag")
192
+ verify_unused_in_option(name,switches,"switch")
193
+ end
194
+ end
195
+
196
+ def verify_unused_in_option(name,option_like,type) # :nodoc:
197
+ return if name.to_s == 'help'
198
+ raise ArgumentError.new("#{name} has already been specified as a #{type} #{context_description}") if option_like[name]
199
+ option_like.each do |one_option_name,one_option|
200
+ if one_option.aliases
201
+ if one_option.aliases.include? name
202
+ raise ArgumentError.new("#{name} has already been specified as an alias of #{type} #{one_option_name} #{context_description}")
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ # Extract the options hash out of the argument to flag/switch and
209
+ # set the values if using classic style
210
+ def extract_options(names)
211
+ options = {}
212
+ options = names.pop if names.last.kind_of? Hash
213
+ options = { :desc => @next_desc,
214
+ :long_desc => @next_long_desc,
215
+ :default_value => @next_default_value,
216
+ :arg_name => @next_arg_name}.merge(options)
217
+ end
218
+
219
+
220
+ end
221
+ end
@@ -0,0 +1,54 @@
1
+ module Coglius
2
+ # Mixed into all exceptions that Coglius handles; you can use this to catch
3
+ # anything that came from Coglius intentionally. You can also mix this into non-Coglius
4
+ # exceptions to get Coglius's exit behavior.
5
+ module StandardException
6
+ def exit_code; 1; end
7
+ end
8
+ # Indicates that the command line invocation was bad
9
+ class BadCommandLine < StandardError
10
+ include StandardException
11
+ def exit_code; 64; end
12
+ end
13
+
14
+ # Indicates the bad command line was an unknown command
15
+ class UnknownCommand < BadCommandLine
16
+ end
17
+
18
+ # Indicates the bad command line was an unknown global argument
19
+ class UnknownGlobalArgument < BadCommandLine
20
+ end
21
+
22
+ # Indicates the bad command line was an unknown command argument
23
+ class UnknownCommandArgument < BadCommandLine
24
+ # The command for which the argument was unknown
25
+ attr_reader :command
26
+ # +message+:: the error message to show the user
27
+ # +command+:: the command we were using to parse command-specific options
28
+ def initialize(message,command)
29
+ super(message)
30
+ @command = command
31
+ end
32
+ end
33
+
34
+ # Raise this if you want to use an exit status that isn't the default
35
+ # provided by Coglius. Note that Coglius::App#exit_now! might be a bit more to your liking.
36
+ #
37
+ # Example:
38
+ #
39
+ # raise CustomExit.new("Not connected to DB",-5) unless connected?
40
+ # raise CustomExit.new("Bad SQL",-6) unless valid_sql?(args[0])
41
+ #
42
+ class CustomExit < StandardError
43
+ include StandardException
44
+ attr_reader :exit_code #:nodoc:
45
+ # Create a custom exit exception
46
+ #
47
+ # +message+:: String containing error message to show the user
48
+ # +exit_code+:: the exit code to use (as an Int), overridding Coglius's default
49
+ def initialize(message,exit_code)
50
+ super(message)
51
+ @exit_code = exit_code
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,68 @@
1
+ require 'coglius/command_line_option.rb'
2
+
3
+ module Coglius
4
+ # Defines a flag, which is to say a switch that takes an argument
5
+ class Flag < CommandLineOption # :nodoc:
6
+
7
+ # Regexp that is used to see if the flag's argument matches
8
+ attr_reader :must_match
9
+
10
+ # Type to which we want to cast the values
11
+ attr_reader :type
12
+
13
+ # Name of the argument that user configured
14
+ attr_reader :argument_name
15
+
16
+ # Creates a new option
17
+ #
18
+ # names:: Array of symbols or strings representing the names of this switch
19
+ # options:: hash of options:
20
+ # :desc:: the short description
21
+ # :long_desc:: the long description
22
+ # :default_value:: the default value of this option
23
+ # :arg_name:: the name of the flag's argument, default is "arg"
24
+ # :must_match:: a regexp that the flag's value must match
25
+ # :type:: a class to convert the value to
26
+ # :mask:: if true, the default value of this flag will not be output in the help.
27
+ # This is useful for password flags where you might not want to show it
28
+ # on the command-line.
29
+ def initialize(names,options)
30
+ super(names,options)
31
+ @argument_name = options[:arg_name] || "arg"
32
+ @default_value = options[:default_value]
33
+ @must_match = options[:must_match]
34
+ @type = options[:type]
35
+ @mask = options[:mask]
36
+ end
37
+
38
+ def safe_default_value
39
+ if @mask
40
+ "********"
41
+ else
42
+ default_value
43
+ end
44
+ end
45
+
46
+ def arguments_for_option_parser
47
+ args = all_forms_a.map { |name| "#{name} VAL" }
48
+ args << @must_match if @must_match
49
+ args << @type if @type
50
+ args
51
+ end
52
+
53
+ # Returns a string of all possible forms
54
+ # of this flag. Mostly intended for printing
55
+ # to the user.
56
+ def all_forms(joiner=', ')
57
+ forms = all_forms_a
58
+ string = forms.join(joiner)
59
+ if forms[-1] =~ /^\-\-/
60
+ string += '='
61
+ else
62
+ string += ' '
63
+ end
64
+ string += @argument_name
65
+ return string
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,124 @@
1
+ module Coglius
2
+ # Parses the command-line options using an actual +OptionParser+
3
+ class CogliusOptionParser
4
+ def initialize(commands,flags,switches,accepts,default_command = nil)
5
+ @commands = commands
6
+ @flags = flags
7
+ @switches = switches
8
+ @accepts = accepts
9
+ @default_command = default_command
10
+ end
11
+
12
+ # Given the command-line argument array, returns and array of size 4:
13
+ #
14
+ # 0:: global options
15
+ # 1:: command, as a Command
16
+ # 2:: command-specific options
17
+ # 3:: unparsed arguments
18
+ def parse_options(args) # :nodoc:
19
+ args_clone = args.clone
20
+ global_options = {}
21
+ command = nil
22
+ command_options = {}
23
+ remaining_args = nil
24
+
25
+ global_options,command_name,args = parse_global_options(OptionParserFactory.new(@flags,@switches,@accepts), args)
26
+ @flags.each do |name,flag|
27
+ global_options[name] = flag.default_value unless global_options[name]
28
+ end
29
+ @switches.each do |name,switch|
30
+ global_options[name] = switch.default_value if global_options[name].nil?
31
+ end
32
+
33
+ command_name ||= @default_command || :help
34
+ command = find_command(command_name)
35
+ if Array(command).empty?
36
+ raise UnknownCommand.new("Unknown command '#{command_name}'")
37
+ elsif command.kind_of? Array
38
+ raise UnknownCommand.new("Ambiguous command '#{command_name}'. It matches #{command.sort.join(',')}")
39
+ end
40
+
41
+ command_options,args = parse_command_options(OptionParserFactory.new(command.flags,command.switches,@accepts),
42
+ command,
43
+ args)
44
+
45
+ command.flags.each do |name,flag|
46
+ command_options[name] = flag.default_value unless command_options[name]
47
+ end
48
+ command.switches.each do |name,switch|
49
+ command_options[name] = switch.default_value if command_options[name].nil?
50
+ end
51
+
52
+ [global_options,command,command_options,args]
53
+ end
54
+
55
+ private
56
+
57
+ def parse_command_options(option_parser_factory,command,args)
58
+ option_parser,command_options = option_parser_factory.option_parser
59
+ help_args = %w(-h --help).reject { |_| command.has_option?(_) }
60
+
61
+ unless help_args.empty?
62
+ option_parser.on(*help_args) do
63
+ # We need to raise the help exception later in the process, after
64
+ # the command-line has been parsed, so we know what command
65
+ # to show help for. Unfortunately, we can't just call #action
66
+ # on the command to override what to do, so...metaprogramming.
67
+ class << command
68
+ def execute(*help_args)
69
+ exception = BadCommandLine.new(nil)
70
+ class << exception
71
+ def exit_code; 0; end
72
+ end
73
+ raise exception
74
+ end
75
+ end
76
+ end
77
+ end
78
+ option_parser.parse!(args)
79
+ [command_options,args]
80
+ rescue OptionParser::InvalidOption => ex
81
+ raise UnknownCommandArgument.new("Unknown option #{ex.args.join(' ')}",command)
82
+ rescue OptionParser::InvalidArgument => ex
83
+ raise UnknownCommandArgument.new("#{ex.reason}: #{ex.args.join(' ')}",command)
84
+ end
85
+
86
+ def parse_global_options(option_parser_factory,args,&error_handler)
87
+ if error_handler.nil?
88
+ error_handler = lambda { |message|
89
+ raise UnknownGlobalArgument.new(message)
90
+ }
91
+ end
92
+ option_parser,global_options = option_parser_factory.option_parser
93
+ command = nil
94
+ option_parser.order!(args) do |non_option|
95
+ command = non_option
96
+ break
97
+ end
98
+ [global_options,command,args]
99
+ rescue OptionParser::InvalidOption => ex
100
+ error_handler.call("Unknown option #{ex.args.join(' ')}")
101
+ rescue OptionParser::InvalidArgument => ex
102
+ error_handler.call("#{ex.reason}: #{ex.args.join(' ')}")
103
+ end
104
+
105
+ def find_command(name) # :nodoc:
106
+ names_to_commands = {}
107
+ @commands.each do |command_name,command|
108
+ names_to_commands[command_name.to_s] = command
109
+ Array(command.aliases).each do |command_alias|
110
+ names_to_commands[command_alias.to_s] = command
111
+ end
112
+ end
113
+ names_to_commands.fetch(name.to_s) do |command_to_match|
114
+ find_command_by_partial_name(names_to_commands, command_to_match)
115
+ end
116
+ end
117
+
118
+ def find_command_by_partial_name(names_to_commands, command_to_match)
119
+ partial_matches = names_to_commands.keys.select { |command_name| command_name =~ /^#{command_to_match}/ }
120
+ return names_to_commands[partial_matches[0]] if partial_matches.size == 1
121
+ partial_matches
122
+ end
123
+ end
124
+ end