fhlow 1.91.0

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 (53) hide show
  1. data/bin/fhlow +186 -0
  2. data/lib/module_cmdparse/cmdparse.rb +480 -0
  3. data/lib/module_cmdparse/cmdparse/wrappers/optparse.rb +65 -0
  4. data/lib/module_config/config.rb +67 -0
  5. data/lib/module_config/configexception.rb +18 -0
  6. data/lib/module_config/configitems/complex.rb +71 -0
  7. data/lib/module_config/configitems/complexpackage.rb +67 -0
  8. data/lib/module_config/configitems/complexunit.rb +76 -0
  9. data/lib/module_config/configitems/simple.rb +56 -0
  10. data/lib/module_config/configitems/simplearchitecture.rb +52 -0
  11. data/lib/module_config/configitems/simplepackage.rb +47 -0
  12. data/lib/module_config/configitems/simpleunit.rb +53 -0
  13. data/lib/module_config/configs.test +24 -0
  14. data/lib/module_config/item.rb +34 -0
  15. data/lib/module_config/itemfactory.rb +55 -0
  16. data/lib/module_config/section.rb +69 -0
  17. data/lib/module_config/test.flw +20 -0
  18. data/lib/module_config/test.rb +85 -0
  19. data/lib/module_config/tmp +3 -0
  20. data/lib/module_config/unittests/config_1.flw +14 -0
  21. data/lib/module_config/unittests/config_1_fixed.flw +14 -0
  22. data/lib/module_config/unittests/config_2.flw +15 -0
  23. data/lib/module_config/unittests/config_3a.flw +16 -0
  24. data/lib/module_config/unittests/config_3b.flw +15 -0
  25. data/lib/module_config/unittests/config_test.rb +579 -0
  26. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configexception_rb.html +647 -0
  27. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-complex_rb.html +700 -0
  28. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-complexpackage_rb.html +694 -0
  29. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-complexunit_rb.html +704 -0
  30. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-simple_rb.html +685 -0
  31. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-simplepackage_rb.html +676 -0
  32. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-simpleunit_rb.html +682 -0
  33. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-item_rb.html +663 -0
  34. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-itemfactory_rb.html +687 -0
  35. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-myconfig_rb.html +687 -0
  36. data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-section_rb.html +692 -0
  37. data/lib/module_config/unittests/coverage/index.html +689 -0
  38. data/lib/module_fhlow/fhlowexception.rb +55 -0
  39. data/lib/module_fhlow/leaf.rb +197 -0
  40. data/lib/module_fhlow/leaf.rb~ +202 -0
  41. data/lib/module_fhlow/leaffactory.rb +97 -0
  42. data/lib/module_fhlow/leafs/Package.rb +55 -0
  43. data/lib/module_fhlow/leafs/Unit.rb +152 -0
  44. data/lib/module_fhlow/log.rb +100 -0
  45. data/lib/module_fhlow/node.rb +206 -0
  46. data/lib/module_fhlow/pen.rb +101 -0
  47. data/lib/module_fhlow/plugin.rb +54 -0
  48. data/lib/module_fhlow/pluginpool.rb +81 -0
  49. data/lib/module_fhlow/rootnode.rb +98 -0
  50. data/lib/module_fhlow/test.rb +15 -0
  51. data/lib/module_term/ansicolor.rb +102 -0
  52. data/tests/testsuite.rb +20 -0
  53. metadata +106 -0
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ ###################### environment setup ##############################################
4
+ # Tries to detect the root directory of the actual fhlow root directory by parsing
5
+ # the string provided by 'Dir.pwd'.
6
+ =begin
7
+ def detectFhlowRootDir(_dir = Dir.pwd)
8
+
9
+ if _dir == ""
10
+ print ",-----------------------------------------------------------------+\n"
11
+ print "| ERROR: your actual working dir is not inside a fhlow structure! |\n"
12
+ print "`-----------------------------------------------------------------+\n"
13
+
14
+ if ARGV.include?("-a") or ARGV.include?("--askexit")
15
+ print "Press <enter> to exit..."
16
+ $stdin.getc
17
+ puts " bye!"
18
+ end
19
+
20
+ exit
21
+
22
+ elsif Dir.entries(_dir).include?("flw")
23
+ return _dir+"/"
24
+ else
25
+ return detectFhlowRootDir(_dir.gsub(/(.*)\/\w*[\/]*/,'\1'))
26
+ end
27
+ end
28
+
29
+ # add the fhlow lib dir to $: so ruby can find all the files
30
+ $:.unshift(detectFhlowRootDir+"flw/core/lib")
31
+
32
+ Dir.glob(detectFhlowRootDir+"flw/core/lib/module_*") { |entry| $:.unshift(entry) if File.directory?(entry) }
33
+
34
+ =end
35
+
36
+ # add the fhlow lib dir to $: so ruby can find all the files
37
+ Dir.glob(File.join(File.dirname(__FILE__), "..", "lib", "module_*")) { |entry| $:.unshift(entry) if File.directory?(entry) }
38
+
39
+
40
+ #######################################################################################
41
+
42
+
43
+ require 'rootnode'
44
+ require 'pen'
45
+ require 'log'
46
+ require 'pluginpool'
47
+ require 'cmdparse'
48
+ require 'fhlowexception'
49
+ require 'config'
50
+
51
+
52
+ module Fhlow
53
+
54
+ # Initialization of some options and switches
55
+ opt_askexit = false
56
+
57
+ begin
58
+ ####################################################################################################
59
+
60
+ # create a new pen
61
+ pen = Pen.new($stdout, $stdout.isatty)
62
+
63
+ # create the var dir if it does not exists
64
+ Dir.mkdir(RootNode.detectFhlowRootDir+"flw/var") unless File.exist?(RootNode.detectFhlowRootDir+"flw/var")
65
+
66
+ # create the logger
67
+ log = Log.new(RootNode.detectFhlowRootDir+"flw/var/log.flw", 1, pen)
68
+ log.info(self, "created logfile: "+RootNode.detectFhlowRootDir+"flw/var/log.flw")
69
+
70
+ ####################################################################################################
71
+
72
+
73
+ # create a CmdParser
74
+ cmd = CmdParse::CommandParser.new(false, true)
75
+ cmd.program_name = "fhlow"
76
+ cmd.program_version = [0, 2, 0]
77
+
78
+ # define the global options
79
+ cmd.options = CmdParse::OptionParserWrapper.new do |opt|
80
+ opt.separator "<global-options>:"
81
+ opt.on("-l", "--loglevel VAL", "Set the loglevel to VAL.") { |val| log.loglevel = val.to_i }
82
+ opt.on("-c", "--nocolor", "Disable colorful output.") { pen.noColor }
83
+
84
+ opt.on("-a", "--askexit", "Asks to hit a key before exiting.") { opt_askexit = true }
85
+ # TODO: alternative actual child
86
+ end
87
+
88
+ # add two default cmds
89
+ cmd.add_command(CmdParse::HelpCommand.new(pen))
90
+ cmd.add_command(CmdParse::VersionCommand.new(pen))
91
+
92
+
93
+ ####################################################################################################
94
+
95
+
96
+ # parse the commandline
97
+ # the block below will be invoked after the options have been handled
98
+ # and before the execution of the desired cmd
99
+ cmd.parse do |level, cmdname|
100
+ # level -> level in the cmd hierarchy
101
+ # cmdname -> name of the actual cmd in die hierarchy
102
+
103
+ # do all the init work in level 0
104
+ if level == 0
105
+ # we know all the commandline options now!
106
+ # their blocks have been executed already!
107
+
108
+
109
+ # print the header
110
+ pen.header
111
+
112
+
113
+ # tell the cmd-er to use our pen
114
+ cmd.pen = pen
115
+
116
+
117
+ # fetch the default configurations
118
+ defaultconf = Config::Config.new(RootNode.detectFhlowRootDir+"flw/defaults.flw")
119
+
120
+
121
+ # create the root node which initializes the whole tree structure
122
+ rn = RootNode.new(defaultconf, log, pen)
123
+ log.info(self, "successfully initialized the root object")
124
+
125
+ log.info(self, "sucessfully detected the actual child -> "+rn.getActualLeaf(Dir.pwd).name)
126
+
127
+
128
+ # create the plugin pool
129
+ pp = Pluginpool.new(RootNode.detectFhlowRootDir+"flw/plugins/", rn.getActualLeaf(Dir.pwd), pen, log)
130
+ log.info(self, "successfully included the plugins")
131
+
132
+ # register the plugins as cmds
133
+ pp.each do |plugin|
134
+ cmd.add_command(plugin.cmd)
135
+ log.debug(self, "adding command: "+plugin.cmd.name)
136
+ end
137
+
138
+
139
+ end
140
+
141
+ end # the desired cmd will be executed here
142
+
143
+
144
+ ####################################################################################################
145
+
146
+
147
+
148
+ rescue FhlowException => e
149
+ pen.seperator
150
+ pen.puts "#{pen.lightred}Error:#{pen.clear} #{e.caller}\n #{e.message}"
151
+ log.error(e.caller, e.message)
152
+ pen.print e.backtrace.join("\n") if $DEBUG
153
+
154
+ rescue CmdParse::ParseError, OptionParser::ParseError => e
155
+ pen.puts "#{pen.lightred}Error while parsing command line:#{pen.clear}"
156
+ pen.puts " "+e.message
157
+ log.error(self, e.message)
158
+ pen.seperator
159
+ pen.puts
160
+ if e.actualCmd == "mainCommand"
161
+ cmd.main_command.commands['help'].execute([]) if cmd.main_command.commands['help']
162
+ else
163
+ cmd.main_command.commands['help'].execute(cmd.main_command.commands[e.actualCmd].super_commands.reverse.collect {|c| c.name}) if cmd.main_command.commands['help']
164
+ end
165
+
166
+ rescue Config::ConfigException => e
167
+ pen.puts "#{pen.lightred}Error:#{pen.clear} \n#{e.message}"
168
+ log.error(e.caller, e.message)
169
+ pen.print e.backtrace.join("\n") if $DEBUG
170
+
171
+ ensure
172
+
173
+ # close the logfile
174
+ log.close if log != nil
175
+
176
+ # print the good bye message
177
+ pen.footer if pen != nil
178
+ end
179
+
180
+ if opt_askexit
181
+ print "Press <enter> to exit..."
182
+ $stdin.getc
183
+ puts " bye!"
184
+ end
185
+
186
+ end
@@ -0,0 +1,480 @@
1
+ #
2
+ #--
3
+ #
4
+ # $Id: cmdparse.rb 416 2006-06-17 19:32:55Z thomas $
5
+ #
6
+ # cmdparse: advanced command line parser supporting commands
7
+ # Copyright (C) 2004 Thomas Leitner
8
+ #
9
+ # This program is free software; you can redistribute it and/or modify it under the terms of the GNU
10
+ # General Public License as published by the Free Software Foundation; either version 2 of the
11
+ # License, or (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
14
+ # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License along with this program; if not,
18
+ # write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #
20
+ #++
21
+ #
22
+
23
+
24
+ # Namespace module for cmdparse.
25
+ module CmdParse
26
+
27
+ # The version of this cmdparse implemention
28
+ VERSION = [2, 0, 2]
29
+
30
+
31
+ # Base class for all cmdparse errors.
32
+ class ParseError < RuntimeError
33
+
34
+ attr_accessor :actualCmd
35
+
36
+ # Sets the reason for a subclass.
37
+ def self.reason( reason, has_arguments = true )
38
+ (@@reason ||= {})[self] = [reason, has_arguments]
39
+ end
40
+
41
+ # Returns the reason plus the message.
42
+ def message
43
+ data = @@reason[self.class] || ['Unknown error', true]
44
+ @cmdname = super
45
+ data[0] + (data[1] ? ": " + super : '')
46
+ end
47
+
48
+ end
49
+
50
+
51
+ # This error is thrown when an invalid command is encountered.
52
+ class InvalidCommandError < ParseError
53
+
54
+ reason 'Invalid command'
55
+ end
56
+
57
+ # This error is thrown when an invalid argument is encountered.
58
+ class InvalidArgumentError < ParseError
59
+ reason 'Invalid argument'
60
+ end
61
+
62
+ # This error is thrown when an invalid option is encountered.
63
+ class InvalidOptionError < ParseError
64
+ reason 'Invalid option'
65
+ end
66
+
67
+ # This error is thrown when no command was given and no default command was specified.
68
+ class NoCommandGivenError < ParseError
69
+ reason 'No command given', false
70
+ end
71
+
72
+ # This error is thrown when a command is added to another command which does not support commands.
73
+ class TakesNoCommandError < ParseError
74
+ reason 'This command takes no other commands', false
75
+ end
76
+
77
+
78
+ # Base class for all parser wrappers.
79
+ class ParserWrapper
80
+
81
+ # Returns the parser instance for the object and, if a block is a given, yields the instance.
82
+ def instance
83
+ yield @instance if block_given?
84
+ @instance
85
+ end
86
+
87
+ # Parses the arguments in order, i.e. stops at the first non-option argument, and returns all
88
+ # remaining arguments.
89
+ def order( args )
90
+ raise InvalidOptionError.new( args[0] ) if args[0] =~ /^-/
91
+ args
92
+ end
93
+
94
+ # Permutes the arguments so that all options anywhere on the command line are parsed and the
95
+ # remaining non-options are returned.
96
+ def permute( args )
97
+ raise InvalidOptionError.new( args[0] ) if args.any? {|a| a =~ /^-/}
98
+ args
99
+ end
100
+
101
+ # Returns a summary string of the options.
102
+ def summarize
103
+ ""
104
+ end
105
+ end
106
+
107
+
108
+ # Require default option parser wrapper
109
+ require 'cmdparse/wrappers/optparse'
110
+
111
+ # Command Hash - will return partial key matches as well if there is a single
112
+ # non-ambigous matching key
113
+ class CommandHash < Hash
114
+
115
+ def []( cmd_name )
116
+ super or begin
117
+ possible = keys.select {|key| key =~ /^#{cmd_name}.*/ }
118
+ fetch( possible[0] ) if possible.size == 1
119
+ end
120
+ end
121
+ end
122
+
123
+
124
+ # Base class for the commands. This class implements all needed methods so that it can be used by
125
+ # the +CommandParser+ class.
126
+ class Command
127
+
128
+ # The name of the command
129
+ attr_reader :name
130
+
131
+ # A short description of the command.
132
+ attr_accessor :short_desc
133
+
134
+ # A detailed description of the command
135
+ attr_accessor :description
136
+
137
+ # The wrapper for parsing the command line options.
138
+ attr_accessor :options
139
+
140
+ # Returns the name of the default command.
141
+ attr_reader :default_command
142
+
143
+ # Sets or returns the super command of this command. The super command is either a +Command+
144
+ # instance for normal commands or a +CommandParser+ instance for the root command.
145
+ attr_accessor :super_command
146
+
147
+ # Returns the list of commands for this command.
148
+ attr_reader :commands
149
+
150
+ # used for printing
151
+ attr_accessor :pen
152
+
153
+ # Initializes the command called +name+. The parameter +has_commands+ specifies if this command
154
+ # takes other commands as argument. The optional argument +partial_commands+ specifies, if
155
+ # partial command matching should be used.
156
+ def initialize( name, has_commands, partial_commands = false )
157
+ @name = name
158
+ @options = ParserWrapper.new
159
+ @has_commands = has_commands
160
+ @commands = Hash.new
161
+ @default_command = nil
162
+ @pen = nil
163
+ use_partial_commands( partial_commands )
164
+ end
165
+
166
+ def use_partial_commands( use_partial )
167
+ temp = ( use_partial ? CommandHash.new : Hash.new )
168
+ temp.update( @commands )
169
+ @commands = temp
170
+ end
171
+
172
+ # Returns +true+ if this command supports sub commands.
173
+ def has_commands?
174
+ @has_commands
175
+ end
176
+
177
+ # Adds a command to the command list if this command takes other commands as argument. If the
178
+ # optional parameter +default+ is true, then this command is used when no command is specified
179
+ # on the command line.
180
+ def add_command( command, default = false )
181
+ raise TakesNoCommandError.new( @name ) if !has_commands?
182
+ @commands[command.name] = command
183
+ @default_command = command.name if default
184
+ command.super_command = self
185
+ command.init
186
+ end
187
+
188
+ # For sorting commands by name.
189
+ def <=>( other )
190
+ @name <=> other.name
191
+ end
192
+
193
+ # Returns the +CommandParser+ instance for this command or +nil+ if this command was not
194
+ # assigned to a +CommandParser+ instance.
195
+ def commandparser
196
+ cmd = super_command
197
+ cmd = cmd.super_command while !cmd.nil? && !cmd.kind_of?( CommandParser )
198
+ cmd
199
+ end
200
+ # Returns a list of super commands, ie.:
201
+ # [command, super_command, super_super_command, ...]
202
+ def super_commands
203
+ cmds = []
204
+ cmd = self
205
+ while !cmd.nil? && !cmd.super_command.kind_of?( CommandParser )
206
+ cmds << cmd
207
+ cmd = cmd.super_command
208
+ end
209
+ cmds
210
+ end
211
+
212
+ # This method is called when the command is added to a +Command+ instance.
213
+ def init; end
214
+
215
+ # Set the given +block+ as execution block. See also: +execute+.
216
+ def set_execution_block( &block )
217
+ @exec_block = block
218
+ end
219
+
220
+ # Invokes the block set by +set_execution_block+. This method is called by the +CommandParser+
221
+ # instance if this command was specified on the command line.
222
+ def execute( args )
223
+ @exec_block.call( args )
224
+ end
225
+
226
+ # Defines the usage line for the command.
227
+ def usage
228
+ tmp = "#{@pen.bold}Usage#{@pen.clear}:#{@pen.lightcyan} #{commandparser.program_name}#{@pen.clear}"
229
+ tmp << "#{@pen.lightyellow} <global-options> #{@pen.clear}" if !commandparser.options.instance_of?( ParserWrapper )
230
+ tmp << super_commands.reverse.collect do |c|
231
+ t = @pen.lightcyan+c.name+@pen.clear
232
+ t << "#{@pen.lightyellow} <local-options>#{@pen.clear}" if !c.options.instance_of?( ParserWrapper )
233
+ t
234
+ end.join(' ')
235
+ tmp << (has_commands? ? "#{@pen.lightcyan} COMMAND #{@pen.lightyellow}<options>#{@pen.lightgreen} [ARGS]#{@pen.clear}" : "#{@pen.lightgreen} [ARGS]#{@pen.clear}")
236
+ end
237
+
238
+ # Default method for showing the help for the command.
239
+ def show_help
240
+ @pen.puts "#{@pen.lightgreen}#{@name}#{@pen.clear} - #{short_desc}"
241
+ @pen.puts
242
+ description.split("\n").each { |line| @pen.puts line } if description
243
+ @pen.puts
244
+ @pen.puts usage
245
+ @pen.puts
246
+ if has_commands?
247
+ list_commands
248
+ @pen.puts
249
+ end
250
+ unless (summary = options.summarize).empty?
251
+ summary.each do |line|
252
+ @pen.puts line.gsub(/(-\w,)/, @pen.lightyellow+'\1'+@pen.clear).gsub(/(--[\w]*)/,@pen.lightyellow+'\1'+@pen.clear).gsub(/(<.*>)/,@pen.lightyellow+'\1'+@pen.clear)
253
+ end
254
+ @pen.puts
255
+ end
256
+ end
257
+
258
+ #######
259
+ private
260
+ #######
261
+
262
+ def list_commands( level = 1, command = self )
263
+ @pen.puts "Available #{@pen.lightcyan}COMMAND#{@pen.clear}s:" if level == 1
264
+ command.commands.sort.each do |name, cmd|
265
+ @pen.print " "*level + @pen.lightcyan+name.ljust(25-4*(level-1)), @pen.clear, level>1 ? "#{@pen.yellow}->#{@pen.clear} " : "" ,cmd.short_desc.to_s
266
+ @pen.print " #{@pen.bold}(=default)#{@pen.clear}" if name == command.default_command
267
+ #@pen.puts
268
+ @pen.nl
269
+ list_commands( level + 1, cmd ) if cmd.has_commands?
270
+ @pen.puts if level == 1
271
+ end
272
+ end
273
+
274
+ end
275
+
276
+
277
+ # The default help command. It adds the options "-h" and "--help" to the global options of the
278
+ # associated +CommandParser+. When the command is specified on the command line, it can show the
279
+ # main help or individual command help.
280
+ class HelpCommand < Command
281
+
282
+ def initialize(_pen = $stdout)
283
+ super( 'help', false )
284
+ self.short_desc = 'Provide help for individual commands'
285
+ self.description = "This command prints the program help if no arguments are given. If one or\n" \
286
+ "more command names are given as arguments, these arguments are interpreted\n" \
287
+ "as a hierachy of commands and the help for the right most command is show."
288
+ @pen = _pen
289
+ end
290
+
291
+ def init
292
+ case commandparser.main_command.options
293
+ when OptionParserWrapper
294
+ commandparser.main_command.options.instance do |opt|
295
+ opt.on_tail( "-h", "--help", "Show help" ) do
296
+ execute( [] )
297
+ end
298
+ end
299
+ end
300
+ end
301
+
302
+ def usage
303
+ @pen.bold+"Usage: "+@pen.clear+@pen.lightcyan+"#{commandparser.program_name}"+@pen.clear+@pen.lightcyan+" help"+@pen.clear+" ["+@pen.lightcyan+"COMMAND SUBCOMMAND"+@pen.clear+" ...]"
304
+ end
305
+
306
+ def execute(args)
307
+ if args.length > 0
308
+ cmd = commandparser.main_command
309
+ arg = args.shift
310
+ while !arg.nil? && cmd.commands[arg]
311
+ cmd = cmd.commands[arg]
312
+ arg = args.shift
313
+ end
314
+ if arg.nil?
315
+ cmd.show_help
316
+ else
317
+ raise InvalidArgumentError, args.unshift(arg).join(' ')
318
+ end
319
+ else
320
+ show_program_help
321
+ end
322
+ end
323
+
324
+ #######
325
+ private
326
+ #######
327
+
328
+ def show_program_help
329
+ @pen.puts commandparser.banner + "\n" if commandparser.banner
330
+ @pen.puts "#{@pen.bold}Usage#{@pen.clear}: #{@pen.lightcyan}#{commandparser.program_name}#{@pen.lightyellow} <global-options> " \
331
+ "#{@pen.lightcyan}COMMAND#{@pen.lightyellow} <local-options>#{@pen.clear} [#{@pen.lightcyan}COMMAND#{@pen.lightyellow} <options> ...#{@pen.clear}]#{@pen.lightgreen} [args] #{@pen.clear}"
332
+ @pen.puts " try \"#{@pen.lightcyan}#{commandparser.program_name} help COMMAND #{@pen.clear}[#{@pen.lightcyan}COMMAND#{@pen.clear}]\" for detailed help on commands."
333
+ @pen.puts ""
334
+ list_commands( 1, commandparser.main_command )
335
+ @pen.puts ""
336
+ commandparser.main_command.options.summarize.each do |line|
337
+ @pen.puts line.gsub(/(-\w,)/, @pen.lightyellow+'\1'+@pen.clear).gsub(/(--[\w]*)/,@pen.lightyellow+'\1'+@pen.clear).gsub(/(<.*>)/,@pen.lightyellow+'\1'+@pen.clear)
338
+ end
339
+
340
+ end
341
+ end
342
+
343
+
344
+ # The default version command. It adds the options "-v" and "--version" to the global options of
345
+ # the associated +CommandParser+. When specified on the command line, it shows the version of the
346
+ # program.
347
+ class VersionCommand < Command
348
+
349
+ def initialize(_pen = $stdout)
350
+ super( 'version', false )
351
+ self.short_desc = "Show the version of the program"
352
+ @pen = _pen
353
+ end
354
+
355
+ def init
356
+ case commandparser.main_command.options
357
+ when OptionParserWrapper
358
+ commandparser.main_command.options.instance do |opt|
359
+ opt.on_tail( "--version", "-v", "Show the version of the program" ) do
360
+ execute( [] )
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+ def usage
367
+ "#{@pen.bold}Usage#{@pen.bold}: #{@pen.lightcyan}#{commandparser.program_name} version#{@pen.clear}"
368
+ end
369
+
370
+ def execute( args )
371
+ version = commandparser.program_version
372
+ version = version.join( '.' ) if version.instance_of?( Array )
373
+ @pen.puts commandparser.banner + "\n" if commandparser.banner
374
+ @pen.puts version
375
+ end
376
+
377
+ end
378
+
379
+
380
+ # The main class for creating a command based CLI program.
381
+ class CommandParser
382
+
383
+ # A standard banner for help & version screens
384
+ attr_accessor :banner
385
+
386
+ # The top level command representing the program itself.
387
+ attr_reader :main_command
388
+
389
+ # The name of the program.
390
+ attr_accessor :program_name
391
+
392
+ # The version of the program.
393
+ attr_accessor :program_version
394
+
395
+ # Are Exceptions be handled gracefully? I.e. by printing error message and the help screen?
396
+ attr_reader :handle_exceptions
397
+
398
+ attr_writer :pen
399
+
400
+ # Create a new CommandParser object. The optional argument +handleExceptions+ specifies if the
401
+ # object should handle exceptions gracefully. Set +partial_commands+ to +true+, if you want
402
+ # partial command matching for the top level commands.
403
+ def initialize( handleExceptions = false, partial_commands = false )
404
+ @main_command = Command.new( 'mainCommand', true )
405
+ @main_command.super_command = self
406
+ @main_command.use_partial_commands( partial_commands )
407
+ @program_name = $0
408
+ @program_version = "0.0.0"
409
+ @pen = $stdout
410
+ @handle_exceptions = handleExceptions
411
+ end
412
+
413
+ # Returns the wrapper for parsing the global options.
414
+ def options
415
+ @main_command.options
416
+ end
417
+
418
+ # Sets the wrapper for parsing the global options.
419
+ def options=( wrapper )
420
+ @main_command.options = wrapper
421
+ end
422
+
423
+ # Adds a top level command.
424
+ def add_command( *args )
425
+ @main_command.add_command( *args )
426
+ end
427
+
428
+ # Parses the command line arguments. If a block is specified, the current hierarchy level and
429
+ # the name of the current command is yielded after the options for the level have been parsed.
430
+ def parse( argv = ARGV ) # :yields: level, commandName
431
+ level = 0
432
+ command = @main_command
433
+
434
+ while !command.nil?
435
+ argv = if command.has_commands? || ENV.include?( 'POSIXLY_CORRECT' )
436
+ command.options.order( argv )
437
+ else
438
+ command.options.permute( argv )
439
+ end
440
+
441
+ yield( level, command.name ) if block_given?
442
+
443
+ if command.has_commands?
444
+ cmdName, argv = argv[0], argv[1..-1] || []
445
+
446
+ if cmdName.nil?
447
+ if command.default_command.nil?
448
+ error = NoCommandGivenError.new
449
+ error.actualCmd = command.name
450
+ raise error
451
+ else
452
+ cmdName = command.default_command
453
+ end
454
+ else
455
+ unless command.commands[cmdName]
456
+
457
+ error = InvalidCommandError.new(cmdName)
458
+ error.actualCmd = command.name
459
+ raise error
460
+ end
461
+ end
462
+
463
+ command = command.commands[cmdName]
464
+ level += 1
465
+ else
466
+ command.execute(argv)
467
+ command = nil
468
+ end
469
+ end
470
+
471
+ rescue ParseError, OptionParser::ParseError => e
472
+ raise if !@handle_exceptions
473
+ @pen.puts "#{@pen.lightred}Error while parsing command line:#{@pen.lightred}"
474
+ @pen.puts " "+e.message
475
+ @pen.seperator
476
+ @pen.puts
477
+ @main_command.commands['help'].execute( command.super_commands.reverse.collect {|c| c.name} ) if @main_command.commands['help']
478
+ end
479
+ end
480
+ end