coglius 0.0.1

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