pablo 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +4 -0
- data/LICENSE.txt +22 -0
- data/Manifest.txt +26 -0
- data/README.txt +61 -0
- data/lib/pablo.rb +298 -0
- data/lib/pablo/always.rb +44 -0
- data/lib/pablo/arguments.rb +76 -0
- data/lib/pablo/color.rb +57 -0
- data/lib/pablo/command.rb +71 -0
- data/lib/pablo/errors.rb +68 -0
- data/lib/pablo/expansion.rb +58 -0
- data/lib/pablo/help.rb +191 -0
- data/lib/pablo/helpers.rb +46 -0
- data/lib/pablo/option.rb +89 -0
- data/lib/pablo/parser.rb +192 -0
- data/lib/pablo/token.rb +66 -0
- data/lib/pablo/version.rb +29 -0
- data/test/fixtures/license.txt +22 -0
- data/test/fixtures/load_yaml.yml +37 -0
- data/test/fixtures/output.yml +13 -0
- data/test/help_fixtures.rb +47 -0
- data/test/test_arguments.rb +56 -0
- data/test/test_expansion.rb +62 -0
- data/test/test_output.rb +202 -0
- data/test/test_pablo.rb +115 -0
- data/test/test_parsers.rb +470 -0
- metadata +117 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
###### DON'T PANIC License 1.1 ###########
|
2
|
+
#
|
3
|
+
# Don't panic, this piece of software is
|
4
|
+
# free, i.e. you can do with it whatever
|
5
|
+
# you like, including, but not limited to:
|
6
|
+
#
|
7
|
+
# * using it
|
8
|
+
# * copying it
|
9
|
+
# * (re)distributing it
|
10
|
+
# * burning/burying/shredding it
|
11
|
+
# * eating it
|
12
|
+
# * using it to obtain world domination
|
13
|
+
# * and ignoring it
|
14
|
+
#
|
15
|
+
# Under the sole condition that you
|
16
|
+
#
|
17
|
+
# * CONSIDER buying the author a strong
|
18
|
+
# brownian motion producer, say a nice
|
19
|
+
# hot cup of tea, should you ever meet
|
20
|
+
# him in person.
|
21
|
+
#
|
22
|
+
##########################################
|
23
|
+
|
24
|
+
class Pablo
|
25
|
+
#
|
26
|
+
# Safely encapsules the arguments and provides means to
|
27
|
+
# manipulate them
|
28
|
+
class Arguments
|
29
|
+
|
30
|
+
# undef all instance methods so we can use this as
|
31
|
+
# a proxy to the underlying Array
|
32
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
33
|
+
|
34
|
+
def method_missing sym, *args, &block
|
35
|
+
@slices[-1][:arr].send(sym, *args, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize arr
|
39
|
+
@slices = [{:idx => 0, :arr => arr}]
|
40
|
+
end
|
41
|
+
|
42
|
+
def consume idx
|
43
|
+
@slices[-1][:arr].delete_at idx
|
44
|
+
end
|
45
|
+
|
46
|
+
def consumed?
|
47
|
+
@slices[-1][:arr].empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
def consume_all
|
51
|
+
@slices[-1][:arr].clear
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Constrain arguments on the subset +idx..-1+.
|
56
|
+
def slice idx
|
57
|
+
@slices << {
|
58
|
+
:idx => idx,
|
59
|
+
:arr => @slices[-1][:arr][idx..-1]
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Remove arguments constrain and reconcile arguments
|
65
|
+
# with slice.
|
66
|
+
def unslice
|
67
|
+
raise "cannot unslice fully unsliced Arguments" unless @slices.length > 1
|
68
|
+
idx, arr = @slices[-1][:idx], @slices[-1][:arr]
|
69
|
+
@slices[-2][:arr][idx..-1] = arr
|
70
|
+
@slices.delete_at(-1)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
data/lib/pablo/color.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
###### DON'T PANIC License 1.1 ###########
|
2
|
+
#
|
3
|
+
# Don't panic, this piece of software is
|
4
|
+
# free, i.e. you can do with it whatever
|
5
|
+
# you like, including, but not limited to:
|
6
|
+
#
|
7
|
+
# * using it
|
8
|
+
# * copying it
|
9
|
+
# * (re)distributing it
|
10
|
+
# * burning/burying/shredding it
|
11
|
+
# * eating it
|
12
|
+
# * using it to obtain world domination
|
13
|
+
# * and ignoring it
|
14
|
+
#
|
15
|
+
# Under the sole condition that you
|
16
|
+
#
|
17
|
+
# * CONSIDER buying the author a strong
|
18
|
+
# brownian motion producer, say a nice
|
19
|
+
# hot cup of tea, should you ever meet
|
20
|
+
# him in person.
|
21
|
+
#
|
22
|
+
##########################################
|
23
|
+
|
24
|
+
class Pablo
|
25
|
+
|
26
|
+
#
|
27
|
+
# Runs the given +block+, whenever something needs to be given color.
|
28
|
+
# The block will be given the following arguments:
|
29
|
+
# * The string to colorize
|
30
|
+
# * A symbol indicating the type of the String. The color should be
|
31
|
+
# guessed from that. Can be any of:
|
32
|
+
# * :h1 - First class heading
|
33
|
+
# * :h2 - Second class heading
|
34
|
+
# * :usage - Usage information
|
35
|
+
# * :em - Highlighted word
|
36
|
+
def colorizing &block
|
37
|
+
raise "colorize expects a block" unless block_given?
|
38
|
+
@colorize = block
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Colorize the given +string+ of the given +type+.
|
43
|
+
# See Pablo#colorize for valid values of +type+.
|
44
|
+
# If no colorization function is given, the +string+ is returned
|
45
|
+
# uncolored.
|
46
|
+
def colorize string, type
|
47
|
+
if type == :text
|
48
|
+
string.gsub(/\$([^$]+)\$/ )do |match|
|
49
|
+
colorize(match[1..-2], :em)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
@colorize.nil? ? string : (@colorize.call(string, type))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
###### DON'T PANIC License 1.1 ###########
|
2
|
+
#
|
3
|
+
# Don't panic, this piece of software is
|
4
|
+
# free, i.e. you can do with it whatever
|
5
|
+
# you like, including, but not limited to:
|
6
|
+
#
|
7
|
+
# * using it
|
8
|
+
# * copying it
|
9
|
+
# * (re)distributing it
|
10
|
+
# * burning/burying/shredding it
|
11
|
+
# * eating it
|
12
|
+
# * using it to obtain world domination
|
13
|
+
# * and ignoring it
|
14
|
+
#
|
15
|
+
# Under the sole condition that you
|
16
|
+
#
|
17
|
+
# * CONSIDER buying the author a strong
|
18
|
+
# brownian motion producer, say a nice
|
19
|
+
# hot cup of tea, should you ever meet
|
20
|
+
# him in person.
|
21
|
+
#
|
22
|
+
##########################################
|
23
|
+
|
24
|
+
class Pablo
|
25
|
+
|
26
|
+
class Command < Pablo::Parser
|
27
|
+
|
28
|
+
def initialize *args, &block
|
29
|
+
super(*args, &block)
|
30
|
+
@opts[:consume_all] = @pablo.consume_all? if @opts[:consume_all].nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse args
|
34
|
+
name = nil
|
35
|
+
idx = args.zip((0...args.length).to_a).find_index { |(a,i)|
|
36
|
+
verifies?(a,i) and (name = matches?(a))
|
37
|
+
}
|
38
|
+
|
39
|
+
unless idx.nil?
|
40
|
+
args.consume idx
|
41
|
+
args.slice idx
|
42
|
+
|
43
|
+
@pablo.commands[name] = true
|
44
|
+
|
45
|
+
unless returning(consume?(name, args)) { run?(args) }
|
46
|
+
args.consume_all if @toplevel and @opts[:consume_all]
|
47
|
+
end
|
48
|
+
|
49
|
+
args.unslice
|
50
|
+
true
|
51
|
+
else
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Format a command's names to be displayed to the user.
|
58
|
+
def names_to_user
|
59
|
+
@names.collect(&:to_s).join('|')
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Format a command's names to be used in a Regexp.
|
64
|
+
def names_to_rex
|
65
|
+
Regexp.new(names_to_user)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
data/lib/pablo/errors.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
###### DON'T PANIC License 1.1 ###########
|
2
|
+
#
|
3
|
+
# Don't panic, this piece of software is
|
4
|
+
# free, i.e. you can do with it whatever
|
5
|
+
# you like, including, but not limited to:
|
6
|
+
#
|
7
|
+
# * using it
|
8
|
+
# * copying it
|
9
|
+
# * (re)distributing it
|
10
|
+
# * burning/burying/shredding it
|
11
|
+
# * eating it
|
12
|
+
# * using it to obtain world domination
|
13
|
+
# * and ignoring it
|
14
|
+
#
|
15
|
+
# Under the sole condition that you
|
16
|
+
#
|
17
|
+
# * CONSIDER buying the author a strong
|
18
|
+
# brownian motion producer, say a nice
|
19
|
+
# hot cup of tea, should you ever meet
|
20
|
+
# him in person.
|
21
|
+
#
|
22
|
+
##########################################
|
23
|
+
|
24
|
+
class Pablo
|
25
|
+
|
26
|
+
class MissingArgumentError < RuntimeError
|
27
|
+
def initialize name, arg = nil
|
28
|
+
@name, @arg = name, arg
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
@arg ?
|
33
|
+
"Missing argument '#{@arg}' for '#{@name}'." :
|
34
|
+
"Missing argument for '#{@name}'."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class WrongArgumentError < RuntimeError
|
39
|
+
def initialize name, arg = nil, type = nil
|
40
|
+
@name, @arg, @type = name, arg, type
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
(@arg ?
|
45
|
+
"Wrong argument '#{@arg}' for '#{@name}'." :
|
46
|
+
"Wrong argument for '#{@name}'.") +
|
47
|
+
(@type ?
|
48
|
+
" Expected #{@type}." :
|
49
|
+
'')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Displays a message about a missing argument
|
55
|
+
# and exits parsing.
|
56
|
+
def missing_argument *args
|
57
|
+
raise MissingArgumentError.new @cur.last_match, *args
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Displays a message about a wrong argument (format)
|
62
|
+
# and exits parsing.
|
63
|
+
def wrong_argument *args
|
64
|
+
raise WrongArgumentError.new @cur.last_match, *args
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
###### DON'T PANIC License 1.1 ###########
|
2
|
+
#
|
3
|
+
# Don't panic, this piece of software is
|
4
|
+
# free, i.e. you can do with it whatever
|
5
|
+
# you like, including, but not limited to:
|
6
|
+
#
|
7
|
+
# * using it
|
8
|
+
# * copying it
|
9
|
+
# * (re)distributing it
|
10
|
+
# * burning/burying/shredding it
|
11
|
+
# * eating it
|
12
|
+
# * using it to obtain world domination
|
13
|
+
# * and ignoring it
|
14
|
+
#
|
15
|
+
# Under the sole condition that you
|
16
|
+
#
|
17
|
+
# * CONSIDER buying the author a strong
|
18
|
+
# brownian motion producer, say a nice
|
19
|
+
# hot cup of tea, should you ever meet
|
20
|
+
# him in person.
|
21
|
+
#
|
22
|
+
##########################################
|
23
|
+
|
24
|
+
class Pablo
|
25
|
+
|
26
|
+
#
|
27
|
+
# Whether or not the given +type+ is subject to expansion.
|
28
|
+
def expands? type
|
29
|
+
@opts[:expand] == true or @opts[:expand] == type
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Expands the given +arg+ument if possible.
|
34
|
+
def expand! arg
|
35
|
+
arr = @expand.find_all { |e| e.start_with?(arg) }
|
36
|
+
|
37
|
+
case arr.length
|
38
|
+
when 1 then arr[0]
|
39
|
+
when 0 then arg
|
40
|
+
else
|
41
|
+
@ambiguity.call(arg, arr) unless @ambiguity.nil?
|
42
|
+
arg
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# The block given to this method will be called each time an
|
48
|
+
# argument could not be expanded because there were multiple
|
49
|
+
# parsers that matched it.
|
50
|
+
# The block will be passed the argument and an Array containing
|
51
|
+
# the matching parser names as parameters.
|
52
|
+
def ambiguity &block
|
53
|
+
raise "Pablo#ambiguity needs a block to do something sensible" unless block_given?
|
54
|
+
@ambiguity = block
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
data/lib/pablo/help.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
###### DON'T PANIC License 1.1 ###########
|
2
|
+
#
|
3
|
+
# Don't panic, this piece of software is
|
4
|
+
# free, i.e. you can do with it whatever
|
5
|
+
# you like, including, but not limited to:
|
6
|
+
#
|
7
|
+
# * using it
|
8
|
+
# * copying it
|
9
|
+
# * (re)distributing it
|
10
|
+
# * burning/burying/shredding it
|
11
|
+
# * eating it
|
12
|
+
# * using it to obtain world domination
|
13
|
+
# * and ignoring it
|
14
|
+
#
|
15
|
+
# Under the sole condition that you
|
16
|
+
#
|
17
|
+
# * CONSIDER buying the author a strong
|
18
|
+
# brownian motion producer, say a nice
|
19
|
+
# hot cup of tea, should you ever meet
|
20
|
+
# him in person.
|
21
|
+
#
|
22
|
+
##########################################
|
23
|
+
|
24
|
+
class Pablo
|
25
|
+
|
26
|
+
#
|
27
|
+
# The class that handles standard help displaying.
|
28
|
+
class HelpCommand < Pablo::Command
|
29
|
+
private
|
30
|
+
|
31
|
+
def run? args
|
32
|
+
super(args)
|
33
|
+
args.empty? ? @pablo.help() : @pablo.help(args[0])
|
34
|
+
throw :abort
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# The class that handles standard version displaying.
|
40
|
+
class VersionCommand < Pablo::Command
|
41
|
+
private
|
42
|
+
|
43
|
+
def run? args
|
44
|
+
super(args)
|
45
|
+
@pablo.version()
|
46
|
+
throw :abort
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# The class that handles standard License displaying.
|
52
|
+
class LicenseCommand < Pablo::Command
|
53
|
+
private
|
54
|
+
|
55
|
+
def run? args
|
56
|
+
super(args)
|
57
|
+
@pablo.license()
|
58
|
+
throw :abort
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Creates the standard license command.
|
64
|
+
def license_command options = {}, &block
|
65
|
+
desc('Shows the license of this program.') unless @pending[:desc]
|
66
|
+
longdesc("Displays information about the license of this program.") unless @pending[:longdesc]
|
67
|
+
exec(Pablo::LicenseCommand, :license, options, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Creates the standard version command.
|
72
|
+
def version_command options = {}, &block
|
73
|
+
desc('Shows the version of this program.') unless @pending[:desc]
|
74
|
+
longdesc("Displays information about the version of this program.") unless @pending[:longdesc]
|
75
|
+
exec(Pablo::VersionCommand, :version, options, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Creates the standard help command.
|
80
|
+
def help_command options = {}, &block
|
81
|
+
desc('Displays this message.') unless @pending[:desc]
|
82
|
+
longdesc("Shows a simple help message for this program.\n" +
|
83
|
+
"If a command is given as well, a more detailed message about that\n" +
|
84
|
+
"particular command is shown (such as this one).") unless @pending[:longdesc]
|
85
|
+
usage('[<command>]') unless @pending[:usage]
|
86
|
+
exec(Pablo::HelpCommand, :help, options, &block)
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Creates standard ambiguity output.
|
91
|
+
def ambiguity_command
|
92
|
+
ambiguity do |arg,arr|
|
93
|
+
$stdout.puts "Ambiguous argument '#{arg}'. Could be any of:"
|
94
|
+
|
95
|
+
arr.each do |name|
|
96
|
+
$stdout.puts " * #{name}"
|
97
|
+
end
|
98
|
+
|
99
|
+
throw :abort
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Print the help screen.
|
105
|
+
# * command == nil: program help
|
106
|
+
# * else: specific command's help
|
107
|
+
def help command = nil
|
108
|
+
if command.nil?
|
109
|
+
put_header
|
110
|
+
|
111
|
+
$stdout.puts "\n #{colorize(@opts[:program], :em)} #{colorize(@opts[:usage], :usage)}\n" unless @opts[:usage].nil?
|
112
|
+
$stdout.puts "\n#{indent colorize(@opts[:longdesc], :text), 3}" unless @opts[:longdesc].nil?
|
113
|
+
|
114
|
+
cmdlen = @registered.collect(&:names_to_user).max_by(&:length).length
|
115
|
+
|
116
|
+
[:commands, :options].each do |type|
|
117
|
+
parsers = @registered.find_all { |p| p.klass_to_sym == type }
|
118
|
+
|
119
|
+
unless parsers.empty?
|
120
|
+
$stdout.puts
|
121
|
+
$stdout.puts ' ' + colorize("#{type.to_s.capitalize}:", :h2)
|
122
|
+
|
123
|
+
parsers.each { |p|
|
124
|
+
p.desc ?
|
125
|
+
$stdout.puts(" #{colorize(p.names_to_user.ljust(cmdlen), :em)} : #{colorize(p.desc, :text)}") :
|
126
|
+
$stdout.puts(' ' + colorize(p.names_to_user.ljust(cmdlen), :em))
|
127
|
+
}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
tokens = @registered.find_all { |p| p.is_a? Pablo::Token and not p.desc.nil? }
|
132
|
+
unless tokens.empty?
|
133
|
+
$stdout.puts
|
134
|
+
$stdout.puts ' ' + colorize("Tokens:", :h2)
|
135
|
+
|
136
|
+
tokens.each { |t|
|
137
|
+
$stdout.puts " #{colorize(t.names_to_user.ljust(cmdlen), :em)} : #{t.desc}"
|
138
|
+
}
|
139
|
+
end
|
140
|
+
else
|
141
|
+
command = expand!(command)
|
142
|
+
found = @registered.find do |item|
|
143
|
+
if not item.names_to_rex.nil? and item.names_to_rex =~ command
|
144
|
+
put_header
|
145
|
+
|
146
|
+
$stdout.print "\n " + colorize(item.names_to_user, :em)
|
147
|
+
$stdout.print ' ' + colorize(item.usage, :usage) unless item.usage.nil?
|
148
|
+
$stdout.puts "\n\n"
|
149
|
+
$stdout.puts indent(colorize(item.longdesc, :text), 1) unless item.longdesc.nil?
|
150
|
+
true
|
151
|
+
else false
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
if found.nil?
|
156
|
+
$stdout.puts "Sorry, but I don't know '#{colorize(command, :em)}'."
|
157
|
+
$stdout.puts "Try running '#{colorize('help', :em)}' without parameters to get a list of all the commands, options etc."
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Print version screen and stop processing.
|
164
|
+
def version
|
165
|
+
$stdout.puts colorize("#{@opts[:program]} #{@opts[:version]}", :h1)
|
166
|
+
$stdout.puts @opts[:capabilities].join(' ') unless @opts[:capabilities].nil?
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# Print license and stop processing. Nothing else.
|
171
|
+
def license
|
172
|
+
if File.exist? @opts[:license].to_s
|
173
|
+
open(@opts[:license]) { |f| $stdout.puts f.read }
|
174
|
+
else
|
175
|
+
$stdout.puts colorize(@opts[:license], :text)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# Prints the standard header for most Pablo output.
|
181
|
+
def put_header
|
182
|
+
unless @opts[:program].nil?
|
183
|
+
str = "#{@opts[:program]}"
|
184
|
+
str << " #{@opts[:version]}" unless @opts[:version].nil?
|
185
|
+
str << " - #{@opts[:desc]}" unless @opts[:desc].nil?
|
186
|
+
$stdout.puts ' ' + colorize(str, :h1)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|