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.
- data/bin/fhlow +186 -0
- data/lib/module_cmdparse/cmdparse.rb +480 -0
- data/lib/module_cmdparse/cmdparse/wrappers/optparse.rb +65 -0
- data/lib/module_config/config.rb +67 -0
- data/lib/module_config/configexception.rb +18 -0
- data/lib/module_config/configitems/complex.rb +71 -0
- data/lib/module_config/configitems/complexpackage.rb +67 -0
- data/lib/module_config/configitems/complexunit.rb +76 -0
- data/lib/module_config/configitems/simple.rb +56 -0
- data/lib/module_config/configitems/simplearchitecture.rb +52 -0
- data/lib/module_config/configitems/simplepackage.rb +47 -0
- data/lib/module_config/configitems/simpleunit.rb +53 -0
- data/lib/module_config/configs.test +24 -0
- data/lib/module_config/item.rb +34 -0
- data/lib/module_config/itemfactory.rb +55 -0
- data/lib/module_config/section.rb +69 -0
- data/lib/module_config/test.flw +20 -0
- data/lib/module_config/test.rb +85 -0
- data/lib/module_config/tmp +3 -0
- data/lib/module_config/unittests/config_1.flw +14 -0
- data/lib/module_config/unittests/config_1_fixed.flw +14 -0
- data/lib/module_config/unittests/config_2.flw +15 -0
- data/lib/module_config/unittests/config_3a.flw +16 -0
- data/lib/module_config/unittests/config_3b.flw +15 -0
- data/lib/module_config/unittests/config_test.rb +579 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configexception_rb.html +647 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-complex_rb.html +700 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-complexpackage_rb.html +694 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-complexunit_rb.html +704 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-simple_rb.html +685 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-simplepackage_rb.html +676 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-simpleunit_rb.html +682 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-item_rb.html +663 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-itemfactory_rb.html +687 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-myconfig_rb.html +687 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-section_rb.html +692 -0
- data/lib/module_config/unittests/coverage/index.html +689 -0
- data/lib/module_fhlow/fhlowexception.rb +55 -0
- data/lib/module_fhlow/leaf.rb +197 -0
- data/lib/module_fhlow/leaf.rb~ +202 -0
- data/lib/module_fhlow/leaffactory.rb +97 -0
- data/lib/module_fhlow/leafs/Package.rb +55 -0
- data/lib/module_fhlow/leafs/Unit.rb +152 -0
- data/lib/module_fhlow/log.rb +100 -0
- data/lib/module_fhlow/node.rb +206 -0
- data/lib/module_fhlow/pen.rb +101 -0
- data/lib/module_fhlow/plugin.rb +54 -0
- data/lib/module_fhlow/pluginpool.rb +81 -0
- data/lib/module_fhlow/rootnode.rb +98 -0
- data/lib/module_fhlow/test.rb +15 -0
- data/lib/module_term/ansicolor.rb +102 -0
- data/tests/testsuite.rb +20 -0
- metadata +106 -0
data/bin/fhlow
ADDED
@@ -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
|