pablo 1.0.3
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.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
|
+
|