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,284 @@
1
+ module Coglius
2
+ # Internals for make App work
3
+ module AppSupport
4
+ # Override the device of stderr; exposed only for testing
5
+ def error_device=(e) #:nodoc:
6
+ @stderr = e
7
+ end
8
+
9
+ def context_description
10
+ "in global context"
11
+ end
12
+
13
+ # Reset the Coglius module internal data structures; mostly useful for testing
14
+ def reset # :nodoc:
15
+ switches.clear
16
+ flags.clear
17
+ @commands = nil
18
+ @commands_declaration_order = []
19
+ @flags_declaration_order = []
20
+ @switches_declaration_order = []
21
+ @version = nil
22
+ @config_file = nil
23
+ @use_openstruct = false
24
+ @prog_desc = nil
25
+ @error_block = false
26
+ @pre_block = false
27
+ @post_block = false
28
+ @default_command = :help
29
+ @around_block = nil
30
+ clear_nexts
31
+ end
32
+
33
+ # Get an array of commands, ordered by when they were declared
34
+ def commands_declaration_order # :nodoc:
35
+ @commands_declaration_order
36
+ end
37
+
38
+ # Get the version string
39
+ def version_string #:nodoc:
40
+ @version
41
+ end
42
+
43
+ # Get the default command for the entire app
44
+ def get_default_command
45
+ @default_command
46
+ end
47
+
48
+ # Runs whatever command is needed based on the arguments.
49
+ #
50
+ # +args+:: the command line ARGV array
51
+ #
52
+ # Returns a number that would be a reasonable exit code
53
+ def run(args) #:nodoc:
54
+ args = args.dup if @preserve_argv
55
+ command = nil
56
+ begin
57
+ override_defaults_based_on_config(parse_config)
58
+
59
+ add_help_switch_if_needed(switches)
60
+
61
+ global_options,command,options,arguments = CogliusOptionParser.new(commands,flags,switches,accepts,@default_command).parse_options(args)
62
+
63
+ copy_options_to_aliased_versions(global_options,command,options)
64
+
65
+ global_options = convert_to_openstruct_if_needed(global_options)
66
+ options = convert_to_openstruct_if_needed(options)
67
+
68
+ if proceed?(global_options,command,options,arguments)
69
+ call_command(command,global_options,options,arguments)
70
+ end
71
+ 0
72
+ rescue Exception => ex
73
+ handle_exception(ex,command)
74
+ end
75
+ end
76
+
77
+
78
+ # Return the name of the config file; mostly useful for generating help docs
79
+ def config_file_name #:nodoc:
80
+ @config_file
81
+ end
82
+
83
+ def accepts #:nodoc:
84
+ @accepts ||= {}
85
+ end
86
+
87
+ # Copies all options in both global_options and options to keys for the aliases of those flags.
88
+ # For example, if a flag works with either -f or --flag, this will copy the value from [:f] to [:flag]
89
+ # to allow the user to access the options by any alias
90
+ def copy_options_to_aliased_versions(global_options,command,options) # :nodoc:
91
+ copy_options_to_aliases(global_options)
92
+ command.copy_options_to_aliases(options)
93
+ end
94
+
95
+ def parse_config # :nodoc:
96
+ config = {
97
+ 'commands' => {},
98
+ }
99
+ if @config_file && File.exist?(@config_file)
100
+ require 'yaml'
101
+ config.merge!(File.open(@config_file) { |file| YAML::load(file) })
102
+ end
103
+ config
104
+ end
105
+
106
+ def clear_nexts # :nodoc:
107
+ super
108
+ @skips_post = false
109
+ @skips_pre = false
110
+ @skips_around = false
111
+ end
112
+
113
+ def stderr
114
+ @stderr ||= STDERR
115
+ end
116
+
117
+ def self.included(klass)
118
+ @stderr = $stderr
119
+ end
120
+
121
+ def flags # :nodoc:
122
+ @flags ||= {}
123
+ end
124
+
125
+ def switches # :nodoc:
126
+ @switches ||= {}
127
+ end
128
+
129
+ def commands # :nodoc:
130
+ if !@commands
131
+ @commands = { :help => Coglius::Commands::Help.new(self), :_doc => Coglius::Commands::Doc.new(self) }
132
+ @commands_declaration_order ||= []
133
+ @commands_declaration_order << @commands[:help]
134
+ @commands_declaration_order << @commands[:_doc]
135
+ end
136
+ @commands
137
+ end
138
+
139
+ def pre_block
140
+ @pre_block ||= Proc.new do
141
+ true
142
+ end
143
+ end
144
+
145
+ def post_block
146
+ @post_block ||= Proc.new do
147
+ end
148
+ end
149
+
150
+ def around_blocks
151
+ @around_blocks || []
152
+ end
153
+
154
+ def help_sort_type
155
+ @help_sort_type || :alpha
156
+ end
157
+
158
+ def help_text_wrap_type
159
+ @help_text_wrap_type || :to_terminal
160
+ end
161
+
162
+ # Sets the default values for flags based on the configuration
163
+ def override_defaults_based_on_config(config)
164
+ override_default(flags,config)
165
+ override_default(switches,config)
166
+
167
+ override_command_defaults(commands,config)
168
+ end
169
+
170
+ def override_command_defaults(command_list,config)
171
+ command_list.each do |command_name,command|
172
+ next if command_name == :initconfig || command.nil?
173
+ command_config = (config['commands'] || {})[command_name] || {}
174
+
175
+ override_default(command.topmost_ancestor.flags,command_config)
176
+ override_default(command.topmost_ancestor.switches,command_config)
177
+
178
+ override_command_defaults(command.commands,command_config)
179
+ end
180
+ end
181
+
182
+ def override_default(tokens,config)
183
+ tokens.each do |name,token|
184
+ token.default_value=config[name] if config[name]
185
+ end
186
+ end
187
+
188
+ private
189
+
190
+ def handle_exception(ex,command)
191
+ if regular_error_handling?(ex)
192
+ output_error_message(ex)
193
+ if ex.kind_of?(OptionParser::ParseError) || ex.kind_of?(BadCommandLine)
194
+ commands[:help] and commands[:help].execute({},{},command.nil? ? [] : [command.name.to_s])
195
+ end
196
+ end
197
+
198
+ raise ex if ENV['Coglius_DEBUG'] == 'true'
199
+
200
+ ex.extend(Coglius::StandardException)
201
+ ex.exit_code
202
+ end
203
+
204
+ def output_error_message(ex)
205
+ stderr.puts error_message(ex) unless no_message_given?(ex)
206
+ if ex.kind_of?(OptionParser::ParseError) || ex.kind_of?(BadCommandLine)
207
+ stderr.puts unless no_message_given?(ex)
208
+ end
209
+ end
210
+
211
+ def no_message_given?(ex)
212
+ ex.message == ex.class.name
213
+ end
214
+
215
+ # Possibly returns a copy of the passed-in Hash as an instance of Coglius::Option.
216
+ # By default, it will *not*. However by putting <tt>use_openstruct true</tt>
217
+ # in your CLI definition, it will
218
+ def convert_to_openstruct_if_needed(options) # :nodoc:
219
+ @use_openstruct ? Options.new(options) : options
220
+ end
221
+
222
+ def add_help_switch_if_needed(switches)
223
+ help_switch_exists = switches.values.find { |switch|
224
+ (Array(switch.aliases) + [switch.name]).find { |an_alias|
225
+ an_alias.to_s == 'help'
226
+ }
227
+ }
228
+ unless help_switch_exists
229
+ desc 'Show this message'
230
+ switch :help, :negatable => false
231
+ end
232
+ end
233
+
234
+ # True if we should proceed with executing the command; this calls
235
+ # the pre block if it's defined
236
+ def proceed?(global_options,command,options,arguments) #:nodoc:
237
+ if command && command.skips_pre
238
+ true
239
+ else
240
+ pre_block.call(global_options,command,options,arguments)
241
+ end
242
+ end
243
+
244
+ # Returns true if we should proceed with Coglius's basic error handling.
245
+ # This calls the error block if the user provided one
246
+ def regular_error_handling?(ex) #:nodoc:
247
+ if @error_block
248
+ @error_block.call(ex)
249
+ else
250
+ true
251
+ end
252
+ end
253
+
254
+ # Returns a String of the error message to show the user
255
+ # +ex+:: The exception we caught that launched the error handling routines
256
+ def error_message(ex) #:nodoc:
257
+ "error: #{ex.message}"
258
+ end
259
+
260
+ def call_command(command,global_options,options,arguments)
261
+ arguments = arguments.map { |arg| arg.dup } # unfreeze
262
+ code = lambda { command.execute(global_options,options,arguments) }
263
+ nested_arounds = unless command.skips_around
264
+ around_blocks.inject do |outer_around, inner_around|
265
+ lambda { |go,c,o,a, code1|
266
+ inner = lambda { inner_around.call(go,c,o,a, code1) }
267
+ outer_around.call(go,c,o,a, inner)
268
+ }
269
+ end
270
+ end
271
+
272
+ if nested_arounds
273
+ nested_arounds.call(global_options,command, options, arguments, code)
274
+ else
275
+ code.call
276
+ end
277
+
278
+ unless command.skips_post
279
+ post_block.call(global_options,command,options,arguments)
280
+ end
281
+ end
282
+
283
+ end
284
+ end
@@ -0,0 +1,149 @@
1
+ require 'coglius/command_line_token.rb'
2
+ require 'coglius/copy_options_to_aliases.rb'
3
+ require 'coglius/dsl.rb'
4
+
5
+ module Coglius
6
+ # A command to be run, in context of global flags and switches. You are given an instance of this class
7
+ # to the block you use for Coglius::DSL#command. This class mixes in Coglius::DSL so all of those methods are available
8
+ # to describe the command, in addition to the methods documented here, most importantly
9
+ # #action.
10
+ #
11
+ # Example:
12
+ #
13
+ # command :list do |c| # <- c is an instance of Coglius::Command
14
+ # c.desc 'use long form'
15
+ # c.switch :l
16
+ #
17
+ # c.action do |global,options,args|
18
+ # # list things here
19
+ # end
20
+ #
21
+ # c.command :tasks do |t| # <- t is an instance of Coglius::Command
22
+ # # this is a "subcommand" of list
23
+ #
24
+ # t.action do |global,options,args|
25
+ # # do whatever list tasks should do
26
+ # end
27
+ # end
28
+ # end
29
+ #
30
+ class Command < CommandLineToken
31
+ include CopyOptionsToAliases
32
+ include DSL
33
+ include CommandSupport
34
+
35
+ # Create a new command.
36
+ #
37
+ # options:: Keys should be:
38
+ # +names+:: A String, Symbol, or Array of String or Symbol that represents the name(s) of this command (required).
39
+ # +description+:: short description of this command as a String
40
+ # +arguments_name+:: description of the arguments as a String, or nil if this command doesn't take arguments
41
+ # +long_desc+:: a longer description of the command, possibly with multiple lines. A double line-break is treated
42
+ # as a paragraph break. No other formatting is respected, though inner whitespace is maintained.
43
+ # +skips_pre+:: if true, this command advertises that it doesn't want the pre block called first
44
+ # +skips_post+:: if true, this command advertises that it doesn't want the post block called after it
45
+ # +skips_around+:: if true, this command advertises that it doesn't want the around block called
46
+ def initialize(options)
47
+ super(options[:names],options[:description],options[:long_desc])
48
+ @arguments_description = options[:arguments_name] || ''
49
+ @arguments_options = Array(options[:arguments_options]).flatten
50
+ @skips_pre = options[:skips_pre]
51
+ @skips_post = options[:skips_post]
52
+ @skips_around = options[:skips_around]
53
+ @commands_declaration_order = []
54
+ @flags_declaration_order = []
55
+ @switches_declaration_order = []
56
+ clear_nexts
57
+ end
58
+
59
+ # Set the default command if this command has subcommands and the user doesn't
60
+ # provide a subcommand when invoking THIS command. When nil, this will show an error and the help
61
+ # for this command; when set, the command with this name will be executed.
62
+ #
63
+ # +command_name+:: The primary name of the subcommand of this command that should be run by default as a String or Symbol.
64
+ def default_command(command_name)
65
+ @default_command = command_name
66
+ end
67
+
68
+ # Define the action to take when the user executes this command. Every command should either define this
69
+ # action block, or have subcommands (or both).
70
+ #
71
+ # +block+:: A block of code to execute. The block will be given 3 arguments:
72
+ # +global_options+:: A Hash of the _global_ options specified
73
+ # by the user, with defaults set and config file values used (if using a config file, see
74
+ # Coglius::App#config_file)
75
+ # +options+:: A Hash of the command-specific options specified by the
76
+ # user, with defaults set and config file values used (if using a config file, see
77
+ # Coglius::App#config_file).
78
+ # +arguments+:: An Array of Strings representing the unparsed command line arguments
79
+ # The block's result value is not used; raise an exception or use Coglius#exit_now! if you need an early exit based
80
+ # on an error condition
81
+ #
82
+ def action(&block)
83
+ @action = block
84
+ end
85
+
86
+ # Describes this commands action block when it *also* has subcommands.
87
+ # In this case, the Coglius::DSL#desc value is the general description of the commands
88
+ # that this command groups, and the value for *this* method documents what
89
+ # will happen if you omit a subcommand.
90
+ #
91
+ # Note that if you omit the action block and specify a subcommand, that subcommand's
92
+ # description will be used to describe what happens by default.
93
+ #
94
+ # desc:: the description of what this command's action block does.
95
+ #
96
+ # Example
97
+ #
98
+ # desc 'list things'
99
+ # command :list do |c|
100
+ #
101
+ # c.desc 'list tasks'
102
+ # c.command :tasks do |t|
103
+ # t.action do |global,options,args|
104
+ # end
105
+ # end
106
+ #
107
+ # c.desc 'list contexts'
108
+ # c.command :contexts do |t|
109
+ # t.action do |global,options,args|
110
+ # end
111
+ # end
112
+ #
113
+ # c.default_desc 'list both tasks and contexts'
114
+ # c.action do |global,options,args|
115
+ # # list everything
116
+ # end
117
+ # end
118
+ #
119
+ #
120
+ # > todo help list
121
+ # NAME
122
+ # list - List things
123
+ #
124
+ # SYNOPSIS
125
+ # todo [global options] list [command options]
126
+ # todo [global options] list [command options] tasks
127
+ # todo [global options] list [command options] contexts
128
+ #
129
+ # COMMANDS
130
+ # <default> - list both tasks and contexts
131
+ # tasks - list tasks
132
+ # contexts - list contexts
133
+ #
134
+ def default_desc(desc)
135
+ @default_desc = desc
136
+ end
137
+
138
+ # Returns true if this command has the given option defined
139
+ def has_option?(option) #:nodoc:
140
+ option = option.gsub(/^\-+/,'')
141
+ ((flags.values.map { |_| [_.name,_.aliases] }) +
142
+ (switches.values.map { |_| [_.name,_.aliases] })).flatten.map(&:to_s).include?(option)
143
+ end
144
+
145
+ def self.name_as_string(name,negatable=false) #:nodoc:
146
+ name.to_s
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,34 @@
1
+ require 'coglius/command_line_token.rb'
2
+
3
+ module Coglius
4
+ # An option, not a command or argument, on the command line
5
+ class CommandLineOption < CommandLineToken #:nodoc:
6
+
7
+ attr_accessor :default_value
8
+ # Command to which this option "belongs", nil if it's a global option
9
+ attr_accessor :associated_command
10
+
11
+ # Creates a new option
12
+ #
13
+ # names - Array of symbols or strings representing the names of this switch
14
+ # options - hash of options:
15
+ # :desc - the short description
16
+ # :long_desc - the long description
17
+ # :default_value - the default value of this option
18
+ def initialize(names,options = {})
19
+ super(names,options[:desc],options[:long_desc])
20
+ @default_value = options[:default_value]
21
+ end
22
+
23
+ def self.name_as_string(name,negatable=true)
24
+ string = name.to_s
25
+ if string.length == 1
26
+ "-#{string}"
27
+ elsif negatable
28
+ "--[no-]#{string}"
29
+ else
30
+ "--#{string}"
31
+ end
32
+ end
33
+ end
34
+ end