acclaim 0.3.2 → 0.4.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/.gitignore +1 -0
- data/acclaim.gemspec +12 -9
- data/acclaim.version +1 -0
- data/lib/acclaim.rb +11 -4
- data/lib/acclaim/command.rb +18 -169
- data/lib/acclaim/command/dsl.rb +181 -0
- data/lib/acclaim/command/dsl/root.rb +36 -0
- data/lib/acclaim/gem.rb +17 -0
- data/lib/acclaim/option.rb +17 -1
- data/lib/acclaim/option/parser.rb +114 -36
- data/spec/acclaim/option/parser_spec.rb +25 -0
- data/spec/acclaim/option_spec.rb +55 -6
- metadata +58 -7
- data/lib/acclaim/version.rb +0 -31
data/.gitignore
CHANGED
data/acclaim.gemspec
CHANGED
@@ -1,24 +1,27 @@
|
|
1
1
|
#!/usr/bin/env gem build
|
2
2
|
# encoding: utf-8
|
3
|
-
$:.unshift File.expand_path('../lib', __FILE__)
|
4
3
|
|
5
|
-
|
4
|
+
Gem::Specification.new 'acclaim' do |gem|
|
6
5
|
|
7
|
-
|
6
|
+
current_directory = File.dirname __FILE__
|
7
|
+
version_file = File.expand_path "#{gem.name}.version", current_directory
|
8
8
|
|
9
|
-
gem.version
|
10
|
-
|
11
|
-
gem.
|
12
|
-
gem.homepage
|
9
|
+
gem.version = File.read(version_file).chomp
|
10
|
+
|
11
|
+
gem.summary = 'Command-line option parser and command interface.'
|
12
|
+
gem.homepage = 'https://github.com/matheusmoreira/acclaim'
|
13
13
|
|
14
14
|
gem.author = 'Matheus Afonso Martins Moreira'
|
15
|
-
gem.email
|
15
|
+
gem.email = 'matheus.a.m.moreira@gmail.com'
|
16
16
|
|
17
17
|
gem.files = `git ls-files`.split "\n"
|
18
18
|
|
19
|
+
gem.add_runtime_dependency 'jewel'
|
19
20
|
gem.add_runtime_dependency 'ribbon'
|
20
21
|
|
21
|
-
gem.add_development_dependency '
|
22
|
+
gem.add_development_dependency 'redcarpet'
|
22
23
|
gem.add_development_dependency 'rookie'
|
24
|
+
gem.add_development_dependency 'rspec'
|
25
|
+
gem.add_development_dependency 'yard'
|
23
26
|
|
24
27
|
end
|
data/acclaim.version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.0
|
data/lib/acclaim.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
-
#
|
1
|
+
# Command line option parsing and command interface for Ruby.
|
2
|
+
#
|
3
|
+
# @author Matheus Afonso Martins Moreira
|
4
|
+
# @since 0.0.1
|
2
5
|
module Acclaim
|
3
6
|
end
|
4
7
|
|
5
|
-
%w(
|
6
|
-
|
7
|
-
|
8
|
+
%w(
|
9
|
+
|
10
|
+
acclaim/command
|
11
|
+
acclaim/gem
|
12
|
+
acclaim/option
|
13
|
+
|
14
|
+
).each { |file| require file }
|
data/lib/acclaim/command.rb
CHANGED
@@ -1,8 +1,15 @@
|
|
1
|
-
%w(
|
2
|
-
|
3
|
-
|
1
|
+
%w(
|
2
|
+
|
3
|
+
acclaim/command/dsl
|
4
|
+
acclaim/command/parser
|
5
|
+
acclaim/option
|
6
|
+
acclaim/option/parser
|
7
|
+
acclaim/option/parser/regexp
|
4
8
|
|
5
|
-
|
9
|
+
ribbon
|
10
|
+
ribbon/core_extensions/object
|
11
|
+
|
12
|
+
).each { |file| require file }
|
6
13
|
|
7
14
|
module Acclaim
|
8
15
|
|
@@ -42,177 +49,19 @@ module Acclaim
|
|
42
49
|
# Verbose? yes
|
43
50
|
# Doing testing with acclaim and safeguard!
|
44
51
|
class Command
|
52
|
+
end
|
45
53
|
|
46
|
-
|
47
|
-
module ClassMethods
|
48
|
-
|
49
|
-
# String which calls this command.
|
50
|
-
def line(*args)
|
51
|
-
@line = args.first unless args.empty?
|
52
|
-
@line ||= (name.gsub(/^.*::/, '').downcase rescue nil)
|
53
|
-
end
|
54
|
-
|
55
|
-
# Commands which may be given to this command.
|
56
|
-
def subcommands
|
57
|
-
@subcommands ||= []
|
58
|
-
end
|
59
|
-
|
60
|
-
# The options this command can take.
|
61
|
-
def options
|
62
|
-
@options ||= []
|
63
|
-
end
|
64
|
-
|
65
|
-
# Adds an option to this command.
|
66
|
-
def option(*args, &block)
|
67
|
-
options << Option.new(*args, &block)
|
68
|
-
end
|
69
|
-
|
70
|
-
# Same as #option.
|
71
|
-
alias opt option
|
72
|
-
|
73
|
-
# The block which is executed when this command is called. It is given 2
|
74
|
-
# parameters; the first is an Ribbon instance which can be queried for
|
75
|
-
# settings information; the second is the remaining command line.
|
76
|
-
def action(&block)
|
77
|
-
@action = block
|
78
|
-
end
|
79
|
-
|
80
|
-
# Same as #action.
|
81
|
-
alias when_called action
|
82
|
-
|
83
|
-
# Adds help subcommand and options to this command.
|
84
|
-
def help(*args)
|
85
|
-
Help.create(self, *args)
|
86
|
-
end
|
87
|
-
|
88
|
-
# Adds help subcommand and options to this command.
|
89
|
-
def version(*args)
|
90
|
-
Version.create(self, *args)
|
91
|
-
end
|
92
|
-
|
93
|
-
# Parses the argument array using this command's set of options.
|
94
|
-
def parse_options!(args)
|
95
|
-
Option::Parser.new(args, options).parse!
|
96
|
-
end
|
97
|
-
|
98
|
-
# Looks for this command's subcommands in the argument array.
|
99
|
-
def parse_subcommands!(args)
|
100
|
-
Command::Parser.new(args, subcommands).parse!
|
101
|
-
end
|
102
|
-
|
103
|
-
# Invokes this command with a fresh set of option values.
|
104
|
-
def run(*args)
|
105
|
-
invoke args
|
106
|
-
rescue Option::Parser::Error => error
|
107
|
-
$stderr.puts error.message
|
108
|
-
end
|
109
|
-
|
110
|
-
# Parses the argument array. The argument array will be searched for
|
111
|
-
# subcommands; if one is found, it will be invoked, if not, this command
|
112
|
-
# will be executed. A subcommand may be anywhere in the array as long as
|
113
|
-
# it is before an argument separator, which is tipically a double dash
|
114
|
-
# (<tt>--</tt>) and may be omitted.
|
115
|
-
#
|
116
|
-
# All argument separators will be deleted from the argument array before a
|
117
|
-
# command is executed.
|
118
|
-
def invoke(args = [], opts = {})
|
119
|
-
opts = Ribbon.wrap opts
|
120
|
-
opts.merge! parse_options!(args)
|
121
|
-
handle_special_options! opts, args
|
122
|
-
if subcommand = parse_subcommands!(args)
|
123
|
-
subcommand.invoke args, opts
|
124
|
-
else
|
125
|
-
delete_argument_separators_in! args
|
126
|
-
execute opts, args
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
# Calls this command's action block with the given option values and
|
131
|
-
# arguments.
|
132
|
-
def execute(opts, args)
|
133
|
-
@action.call opts, args if @action
|
134
|
-
end
|
135
|
-
|
136
|
-
# Same as #execute.
|
137
|
-
alias call execute
|
138
|
-
|
139
|
-
# True if this is a top-level command.
|
140
|
-
def root?
|
141
|
-
superclass == Acclaim::Command
|
142
|
-
end
|
143
|
-
|
144
|
-
# Returns all command ancestors of this command.
|
145
|
-
def command_ancestors
|
146
|
-
ancestors - Acclaim::Command.ancestors
|
147
|
-
end
|
148
|
-
|
149
|
-
# Returns the root of the command hierarchy.
|
150
|
-
def root
|
151
|
-
command_ancestors.last
|
152
|
-
end
|
153
|
-
|
154
|
-
# Returns the sequence of commands from #root that leads to this command.
|
155
|
-
def command_path
|
156
|
-
command_ancestors.reverse
|
157
|
-
end
|
158
|
-
|
159
|
-
# Computes the full command line of this command, which takes parent
|
160
|
-
# commands into account.
|
161
|
-
#
|
162
|
-
# class Command < Acclaim::Command
|
163
|
-
# class Do < Command
|
164
|
-
# class Something < Do
|
165
|
-
# end
|
166
|
-
# end
|
167
|
-
# end
|
168
|
-
#
|
169
|
-
# Command::Do::Something.full_line
|
170
|
-
# => "do something"
|
171
|
-
#
|
172
|
-
# Command::Do::Something.full_line include_root: true
|
173
|
-
# => "command do something"
|
174
|
-
def full_line(*args)
|
175
|
-
options = args.extract_ribbon!
|
176
|
-
command_path.tap do |path|
|
177
|
-
path.shift unless options.include_root?
|
178
|
-
end.map(&:line).join ' '
|
179
|
-
end
|
180
|
-
|
181
|
-
private
|
182
|
-
|
183
|
-
# Handles special options such as <tt>--help</tt> or <tt>--version</tt>.
|
184
|
-
def handle_special_options!(opts, args)
|
185
|
-
const_get(:Help).execute opts, args if opts.acclaim_help?
|
186
|
-
const_get(:Version).execute opts, args if opts.acclaim_version?
|
187
|
-
# TODO:
|
188
|
-
# possibly rescue a NameError and warn user
|
189
|
-
# fix bug:
|
190
|
-
# calling this method causes a subcommand to be executed even if it
|
191
|
-
# wasn't given on the command line. This may result up to **three**
|
192
|
-
# commands (help, version and the actual command) running in one
|
193
|
-
# invocation.
|
194
|
-
end
|
195
|
-
|
196
|
-
# Deletes all argument separators in the given argument array.
|
197
|
-
def delete_argument_separators_in!(args)
|
198
|
-
args.delete_if do |arg|
|
199
|
-
arg =~ Option::Parser::Regexp::ARGUMENT_SEPARATOR
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
end
|
54
|
+
class << Command
|
204
55
|
|
205
56
|
# Add the class methods to the subclass and add it to this command's list of
|
206
57
|
# subcommands.
|
207
|
-
|
208
|
-
|
209
|
-
|
58
|
+
#
|
59
|
+
# @param [Class] command the class that inherited from this command
|
60
|
+
def inherited(command)
|
61
|
+
command.extend Command::DSL
|
62
|
+
subcommands << command if respond_to? :subcommands
|
210
63
|
end
|
211
64
|
|
212
65
|
end
|
213
66
|
|
214
67
|
end
|
215
|
-
|
216
|
-
%w(help parser version).each do |command|
|
217
|
-
require command.prepend 'acclaim/command/'
|
218
|
-
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'acclaim/command/dsl/root'
|
2
|
+
|
3
|
+
module Acclaim
|
4
|
+
class Command
|
5
|
+
|
6
|
+
# Module containing the methods that make up the domain-specific language
|
7
|
+
# used to create commands.
|
8
|
+
#
|
9
|
+
# @author Matheus Afonso Martins Moreira
|
10
|
+
# @since 0.4.0
|
11
|
+
module DSL
|
12
|
+
|
13
|
+
# The string used to invoke this command from the command line.
|
14
|
+
#
|
15
|
+
# @overload line
|
16
|
+
# If not set, will try to figure out the name from the command's class.
|
17
|
+
#
|
18
|
+
# @return [String] the name used to invoke this command
|
19
|
+
#
|
20
|
+
# @overload line(string)
|
21
|
+
# Uses the given string to invoke this command.
|
22
|
+
#
|
23
|
+
# @param [String, nil] string the new command name
|
24
|
+
# @return [String] the name used to invoke this command
|
25
|
+
def line(*arguments)
|
26
|
+
@line = arguments.first unless arguments.empty?
|
27
|
+
@line ||= (name.gsub(/^.*::/, '').downcase if respond_to? :name)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Commands which may be given to this command.
|
31
|
+
#
|
32
|
+
# @return [Array] this command's subcommands
|
33
|
+
def subcommands
|
34
|
+
@subcommands ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
# The options this command can take.
|
38
|
+
#
|
39
|
+
# @return [Array] the options this command takes
|
40
|
+
def options
|
41
|
+
@options ||= []
|
42
|
+
end
|
43
|
+
|
44
|
+
# Adds an option to this command.
|
45
|
+
#
|
46
|
+
# @see Acclaim::Option#initialize
|
47
|
+
def option(*arguments, &block)
|
48
|
+
options << Option.new(*arguments, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
alias opt option
|
52
|
+
|
53
|
+
# The block which is executed when this command is called.
|
54
|
+
#
|
55
|
+
# @yieldparam [Ribbon] options a Ribbon instance which associates options
|
56
|
+
# with their corresponding values
|
57
|
+
# @yieldparam [Array] arguments the arguments that remained in the command
|
58
|
+
# line
|
59
|
+
# @return [Proc, nil] the given block
|
60
|
+
def action(&block)
|
61
|
+
@action = block if block.respond_to? :call
|
62
|
+
@action
|
63
|
+
end
|
64
|
+
|
65
|
+
alias when_called action
|
66
|
+
|
67
|
+
# Parses the argument array using this command's set of options.
|
68
|
+
def parse_options_in!(arguments)
|
69
|
+
Option::Parser.new(arguments, options).parse!
|
70
|
+
end
|
71
|
+
|
72
|
+
# Looks for this command's subcommands in the argument array.
|
73
|
+
def parse_subcommands_in!(arguments)
|
74
|
+
Command::Parser.new(arguments, subcommands).parse!
|
75
|
+
end
|
76
|
+
|
77
|
+
# Invokes this command with a fresh set of option values.
|
78
|
+
def run(*arguments)
|
79
|
+
invoke arguments
|
80
|
+
rescue Option::Parser::Error => error
|
81
|
+
$stderr.puts error.message
|
82
|
+
end
|
83
|
+
|
84
|
+
# Parses the argument array. The argument array will be searched for
|
85
|
+
# subcommands; if one is found, it will be invoked, if not, this command
|
86
|
+
# will be executed. A subcommand may be anywhere in the array as long as
|
87
|
+
# it is before an argument separator, which is tipically a double dash
|
88
|
+
# (<tt>--</tt>) and may be omitted.
|
89
|
+
#
|
90
|
+
# All argument separators will be deleted from the argument array before a
|
91
|
+
# command is executed.
|
92
|
+
def invoke(arguments = [], options = [])
|
93
|
+
options = options + self.options
|
94
|
+
subcommand = parse_subcommands_in! arguments
|
95
|
+
if subcommand.nil?
|
96
|
+
parsed_options = Option::Parser.new(arguments, options).parse!
|
97
|
+
delete_argument_separators_in! arguments
|
98
|
+
execute parsed_options, arguments
|
99
|
+
else
|
100
|
+
subcommand.invoke arguments, options
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Calls this command's action block with the given option values and
|
105
|
+
# arguments.
|
106
|
+
def execute(options, arguments)
|
107
|
+
@action.call options, arguments if @action.respond_to? :call
|
108
|
+
end
|
109
|
+
|
110
|
+
alias call execute
|
111
|
+
|
112
|
+
# True if this is a top-level command.
|
113
|
+
def root?
|
114
|
+
superclass == Acclaim::Command
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns all command ancestors of this command.
|
118
|
+
def command_ancestors
|
119
|
+
ancestors - Acclaim::Command.ancestors
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns the root of the command hierarchy.
|
123
|
+
def root
|
124
|
+
command_ancestors.last
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns the sequence of commands from #root that leads to this command.
|
128
|
+
def command_path
|
129
|
+
command_ancestors.reverse
|
130
|
+
end
|
131
|
+
|
132
|
+
# Computes the full command line of this command, which takes parent
|
133
|
+
# commands into account.
|
134
|
+
#
|
135
|
+
# class Command < Acclaim::Command
|
136
|
+
# class Do < Command
|
137
|
+
# class Something < Do
|
138
|
+
# end
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# Command::Do::Something.full_line
|
143
|
+
# => "do something"
|
144
|
+
#
|
145
|
+
# Command::Do::Something.full_line include_root: true
|
146
|
+
# => "command do something"
|
147
|
+
def full_line(*arguments)
|
148
|
+
options = arguments.extract_ribbon!
|
149
|
+
command_path.tap do |path|
|
150
|
+
path.shift unless options.include_root?
|
151
|
+
end.map(&:line).join ' '
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# Handles special options such as <tt>--help</tt> or <tt>--version</tt>.
|
157
|
+
def handle_special_options!(options, arguments)
|
158
|
+
const_get(:Help).execute options, arguments if options.acclaim_help?
|
159
|
+
const_get(:Version).execute options, arguments if options.acclaim_version?
|
160
|
+
# TODO:
|
161
|
+
# possibly rescue a NameError and warn user
|
162
|
+
# fix bug:
|
163
|
+
# calling this method causes a subcommand to be executed even if it
|
164
|
+
# wasn't given on the command line. This may result up to **three**
|
165
|
+
# commands (help, version and the actual command) running in one
|
166
|
+
# invocation.
|
167
|
+
end
|
168
|
+
|
169
|
+
# Deletes all argument separators in the given argument array.
|
170
|
+
def delete_argument_separators_in!(arguments)
|
171
|
+
arguments.delete_if do |arg|
|
172
|
+
arg =~ Option::Parser::Regexp::ARGUMENT_SEPARATOR
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
include Root
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
%w(
|
2
|
+
|
3
|
+
acclaim/command/help
|
4
|
+
acclaim/command/version
|
5
|
+
|
6
|
+
).each { |file| require file }
|
7
|
+
|
8
|
+
module Acclaim
|
9
|
+
class Command
|
10
|
+
module DSL
|
11
|
+
|
12
|
+
# Methods that only work with root commands.
|
13
|
+
#
|
14
|
+
# @author Matheus Afonso Martins Moreira
|
15
|
+
# @since 0.4.0
|
16
|
+
module Root
|
17
|
+
|
18
|
+
# Adds help subcommand and options to this command.
|
19
|
+
#
|
20
|
+
# @see Acclaim::Command::Help.create
|
21
|
+
def help(*arguments, &block)
|
22
|
+
Help.create root, *arguments, &block
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds version subcommand and options to this command.
|
26
|
+
#
|
27
|
+
# @see Acclaim::Command::Help.create
|
28
|
+
def version(*arguments, &block)
|
29
|
+
Version.create root, *arguments, &block
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/acclaim/gem.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'jewel'
|
2
|
+
|
3
|
+
module Acclaim
|
4
|
+
|
5
|
+
# Acclaim gem information and metadata.
|
6
|
+
#
|
7
|
+
# @author Matheus Afonso Martins Moreira
|
8
|
+
# @since 0.4.0
|
9
|
+
class Gem < Jewel::Gem
|
10
|
+
|
11
|
+
root '../..'
|
12
|
+
|
13
|
+
specification ::Gem::Specification.load root.join('acclaim.gemspec').to_s
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/lib/acclaim/option.rb
CHANGED
@@ -139,7 +139,7 @@ module Acclaim
|
|
139
139
|
|
140
140
|
# Returns +true+ if this option takes no arguments.
|
141
141
|
def flag?
|
142
|
-
arity
|
142
|
+
arity.none?
|
143
143
|
end
|
144
144
|
|
145
145
|
# Same as <tt>flag?</tt>
|
@@ -151,6 +151,22 @@ module Acclaim
|
|
151
151
|
# Same as <tt>flag?</tt>
|
152
152
|
alias switch? flag?
|
153
153
|
|
154
|
+
# Generate human-readable string containing this option's data.
|
155
|
+
#
|
156
|
+
# @return [String] string describing this option
|
157
|
+
def inspect
|
158
|
+
'#<%s %s (%s) %s = %s %s %s %s>' % [
|
159
|
+
self.class.name,
|
160
|
+
key,
|
161
|
+
names.join('|'),
|
162
|
+
type.inspect,
|
163
|
+
default.inspect,
|
164
|
+
arity.inspect,
|
165
|
+
on_multiple,
|
166
|
+
if required? then :required else :optional end
|
167
|
+
]
|
168
|
+
end
|
169
|
+
|
154
170
|
end
|
155
171
|
|
156
172
|
class << Option
|
@@ -1,11 +1,19 @@
|
|
1
|
-
%w(
|
1
|
+
%w(
|
2
2
|
|
3
|
-
|
3
|
+
acclaim/option/parser/error
|
4
|
+
acclaim/option/parser/regexp
|
5
|
+
|
6
|
+
ribbon
|
7
|
+
|
8
|
+
).each { |file| require file }
|
4
9
|
|
5
10
|
module Acclaim
|
6
11
|
class Option
|
7
12
|
|
8
13
|
# Parses arrays of strings and returns an Options instance containing data.
|
14
|
+
#
|
15
|
+
# @author Matheus Afonso Martins Moreira
|
16
|
+
# @since 0.0.1
|
9
17
|
class Parser
|
10
18
|
|
11
19
|
include Parser::Regexp
|
@@ -24,11 +32,15 @@ module Acclaim
|
|
24
32
|
self.options = options || []
|
25
33
|
end
|
26
34
|
|
27
|
-
# Parses the
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
35
|
+
# Parses the given argument array, looking for the given {Option options}
|
36
|
+
# and their arguments if any.
|
37
|
+
#
|
38
|
+
# If no options were given, the argument array will be preprocessed only.
|
39
|
+
#
|
40
|
+
# @note Parsed options and their parameters will be removed from the
|
41
|
+
# argument array.
|
31
42
|
#
|
43
|
+
# @example
|
32
44
|
# include Acclaim
|
33
45
|
#
|
34
46
|
# args = %w(-F log.txt --verbose arg1 arg2)
|
@@ -37,22 +49,49 @@ module Acclaim
|
|
37
49
|
# options << Option.new(:verbose)
|
38
50
|
#
|
39
51
|
# Option::Parser.new(args, options).parse!
|
40
|
-
#
|
52
|
+
# # => {file: "log.txt", verbose: true}
|
41
53
|
#
|
42
54
|
# args
|
43
|
-
#
|
55
|
+
# # => ["arg1", "arg2"]
|
44
56
|
def parse!
|
45
57
|
preprocess_argv!
|
46
|
-
parse_values
|
58
|
+
parse_values!.tap do
|
59
|
+
delete_options_from_argv!
|
60
|
+
end
|
47
61
|
end
|
48
62
|
|
49
63
|
private
|
50
64
|
|
65
|
+
# List of arguments that are to be removed from argv, identified by their
|
66
|
+
# index.
|
67
|
+
#
|
68
|
+
# @return [Array] the indexes of options marked for deletion
|
69
|
+
# @see #delete_options_from_argv!
|
70
|
+
# @since 0.4.0
|
71
|
+
def deleted_options
|
72
|
+
@deleted_options ||= []
|
73
|
+
end
|
74
|
+
|
75
|
+
# Deletes all marked options from argv.
|
76
|
+
#
|
77
|
+
# @note The list of removed indexes will be discarded.
|
78
|
+
#
|
79
|
+
# @see #deleted_options
|
80
|
+
# @since 0.4.0
|
81
|
+
def delete_options_from_argv!
|
82
|
+
argv.delete_if.with_index do |argument, index|
|
83
|
+
deleted_options.include? index
|
84
|
+
end
|
85
|
+
ensure
|
86
|
+
deleted_options.clear
|
87
|
+
end
|
88
|
+
|
51
89
|
# Preprocesses the argument array.
|
52
90
|
def preprocess_argv!
|
53
91
|
split_multiple_short_options!
|
54
92
|
normalize_parameters!
|
55
93
|
argv.compact!
|
94
|
+
check_for_errors!
|
56
95
|
end
|
57
96
|
|
58
97
|
# Splits multiple short options.
|
@@ -73,6 +112,8 @@ module Acclaim
|
|
73
112
|
# %w(--switch=PARAM1,PARAM2) => %w(--switch PARAM1 PARAM2)
|
74
113
|
# %w(--switch=PARAM1,) => %w(--switch PARAM1)
|
75
114
|
# %w(--switch=,PARAM2) => [ '--switch', '', 'PARAM2' ]
|
115
|
+
#
|
116
|
+
# @since 0.0.3
|
76
117
|
def normalize_parameters!
|
77
118
|
argv.find_all { |arg| arg =~ SWITCH_PARAM_EQUALS }.each do |switch|
|
78
119
|
switch_index = argv.index switch
|
@@ -83,48 +124,79 @@ module Acclaim
|
|
83
124
|
end
|
84
125
|
end
|
85
126
|
|
127
|
+
# Checks to see if the arguments have any errors
|
128
|
+
#
|
129
|
+
# @since 0.4.0
|
130
|
+
def check_for_errors!
|
131
|
+
ensure_required_options_are_present!
|
132
|
+
raise_on_multiple_options!
|
133
|
+
end
|
134
|
+
|
135
|
+
# Ensures all options are present in the argument array; raises a parser
|
136
|
+
# error otherwise.
|
137
|
+
#
|
138
|
+
# @since 0.4.0
|
139
|
+
def ensure_required_options_are_present!
|
140
|
+
options.find_all(&:required?).each do |option|
|
141
|
+
Error.raise_missing_required option if argv.find_all do |argument|
|
142
|
+
option =~ argument
|
143
|
+
end.empty?
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Raises a parser error if multiple switches were found for an option that
|
148
|
+
# explicitly disallowed it.
|
149
|
+
#
|
150
|
+
# @since 0.4.0
|
151
|
+
def raise_on_multiple_options!
|
152
|
+
options.find_all do |option|
|
153
|
+
option.on_multiple == :raise
|
154
|
+
end.each do |option|
|
155
|
+
Error.raise_multiple option if argv.find_all do |argument|
|
156
|
+
option =~ argument
|
157
|
+
end.count > 1
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
86
161
|
# Parses the options and their arguments, associating that information
|
87
162
|
# with a Ribbon instance.
|
163
|
+
#
|
164
|
+
# @since 0.0.3
|
88
165
|
def parse_values!
|
89
166
|
values = Ribbon.wrap
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
switches.each do |switch|
|
167
|
+
argv.each_with_index do |argument, index|
|
168
|
+
options.find_all do |option|
|
169
|
+
option =~ argument
|
170
|
+
end.each do |option|
|
171
|
+
key = option.key
|
172
|
+
values[key] = option.default unless values.has_key? key
|
97
173
|
if option.flag?
|
98
174
|
found_boolean option, values.ribbon
|
99
|
-
|
175
|
+
deleted_options << index
|
100
176
|
else
|
101
|
-
|
102
|
-
found_params_for option,
|
177
|
+
parameters = extract_parameters_of! option, index
|
178
|
+
found_params_for option, parameters, values.ribbon
|
103
179
|
end
|
104
180
|
end
|
105
181
|
end
|
106
182
|
values
|
107
183
|
end
|
108
184
|
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
185
|
+
# Starting from the given index, extracts parameters from the following
|
186
|
+
# arguments. Stops if it finds nil, another switch, an argument separator,
|
187
|
+
# or if enough values have been found according to the option's arity.
|
188
|
+
#
|
189
|
+
# @note All arguments scanned, in addition to the option at the given
|
190
|
+
# index, will be marked for deletion.
|
113
191
|
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
def extract_parameters_of!(option,
|
192
|
+
# @return [Array] the parameters for the given option found in argv
|
193
|
+
# @raise [Parser::Error] if not enough arguments are found
|
194
|
+
# @since 0.0.4
|
195
|
+
def extract_parameters_of!(option, index)
|
118
196
|
arity = option.arity
|
119
|
-
|
120
|
-
len = if arity.bound?
|
121
|
-
switch_index + arity.total
|
122
|
-
else
|
123
|
-
argv.length - 1
|
124
|
-
end
|
125
|
-
params = argv[switch_index + 1, len]
|
197
|
+
length = if arity.bound? then index + arity.total else argv.length - 1 end
|
126
198
|
values = []
|
127
|
-
|
199
|
+
argv[index + 1, length].each do |param|
|
128
200
|
case param
|
129
201
|
when nil, SWITCH, ARGUMENT_SEPARATOR then break
|
130
202
|
else
|
@@ -134,7 +206,9 @@ module Acclaim
|
|
134
206
|
end
|
135
207
|
count = values.count
|
136
208
|
Error.raise_wrong_arg_number count, *arity if count < arity.required
|
137
|
-
|
209
|
+
limit = index + count
|
210
|
+
range = index..limit
|
211
|
+
deleted_options.push *range
|
138
212
|
values
|
139
213
|
end
|
140
214
|
|
@@ -148,6 +222,8 @@ module Acclaim
|
|
148
222
|
# so. In this case, the value of the option will always be an array.
|
149
223
|
#
|
150
224
|
# The parameters will be converted according to the option's type.
|
225
|
+
#
|
226
|
+
# @since 0.0.6
|
151
227
|
def found_params_for(option, params = [], ribbon = Ribbon.new)
|
152
228
|
params = option.convert_parameters *params
|
153
229
|
if handler = option.handler then handler.call ribbon, params
|
@@ -162,6 +238,8 @@ module Acclaim
|
|
162
238
|
# If the option has an custom handler associated, it will be called with
|
163
239
|
# only the option values as the first argument. Otherwise, the value will
|
164
240
|
# be set to <tt>true</tt>.
|
241
|
+
#
|
242
|
+
# @since 0.0.6
|
165
243
|
def found_boolean(option, ribbon = Ribbon.new)
|
166
244
|
if handler = option.handler then handler.call ribbon
|
167
245
|
else ribbon[option.key] = true end
|
@@ -201,6 +201,31 @@ describe Acclaim::Option::Parser do
|
|
201
201
|
end
|
202
202
|
end
|
203
203
|
|
204
|
+
context 'which contains one option with unbound aritiy and one boolean option' do
|
205
|
+
let(:options) { [ Acclaim::Option.new(:boolean), Acclaim::Option.new(:parameters, arity: [1, -1]) ] }
|
206
|
+
|
207
|
+
context 'and the arguments are such that the boolean option is between the parameters' do
|
208
|
+
let!(:args) { %w(--parameters a b c d --boolean e f g) }
|
209
|
+
|
210
|
+
it 'should parse from left to right, stopping at the boolean option' do
|
211
|
+
subject.parse!
|
212
|
+
args.should == %w(e f g)
|
213
|
+
end
|
214
|
+
|
215
|
+
context 'the parsed ribbon' do
|
216
|
+
let(:ribbon) { subject.parse! }
|
217
|
+
|
218
|
+
it 'should cointain the correct value for boolean' do
|
219
|
+
ribbon.parameters.should == %w(a b c d)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'should cointain the correct value for parameters' do
|
223
|
+
ribbon.boolean.should be_true
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
204
229
|
context 'containing an option which' do
|
205
230
|
let!(:options) { [ Acclaim::Option.new(:files, '-f', arity: [1,0], on_multiple: on_multiple, &block) ] }
|
206
231
|
let!(:args) { %w(-f f1 -f f2 -f f3) }
|
data/spec/acclaim/option_spec.rb
CHANGED
@@ -15,17 +15,45 @@ describe Acclaim::Option do
|
|
15
15
|
subject.key.should == key
|
16
16
|
end
|
17
17
|
|
18
|
-
context 'when given multiple
|
18
|
+
context 'when given multiple switches' do
|
19
19
|
let(:switches) { %w(-s --switch) }
|
20
|
+
|
21
|
+
context 'directly' do
|
22
|
+
let(:args) { [*switches] }
|
23
|
+
|
24
|
+
it 'should find the switches' do
|
25
|
+
subject.names.should == switches
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'as an array' do
|
30
|
+
let(:args) { [switches] }
|
31
|
+
|
32
|
+
it 'should find the switches' do
|
33
|
+
subject.names.should == switches
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when given a description string' do
|
20
39
|
let(:description) { 'Description' }
|
21
|
-
let(:args) { [*switches, description] }
|
22
40
|
|
23
|
-
|
24
|
-
|
41
|
+
context 'by itself' do
|
42
|
+
let(:args) { [description] }
|
43
|
+
|
44
|
+
it 'should find the description' do
|
45
|
+
subject.description.should == description
|
46
|
+
end
|
25
47
|
end
|
26
48
|
|
27
|
-
|
28
|
-
|
49
|
+
context 'with another description specified in the options hash' do
|
50
|
+
let(:description_from_options_hash) { 'Description from options hash' }
|
51
|
+
let(:options_hash) { { description: description_from_options_hash } }
|
52
|
+
let(:args) { [description, options_hash] }
|
53
|
+
|
54
|
+
it 'should use the description from the options hash' do
|
55
|
+
subject.description.should == description_from_options_hash
|
56
|
+
end
|
29
57
|
end
|
30
58
|
end
|
31
59
|
|
@@ -114,6 +142,27 @@ describe Acclaim::Option do
|
|
114
142
|
end
|
115
143
|
end
|
116
144
|
end
|
145
|
+
|
146
|
+
context 'that specifies a description' do
|
147
|
+
let(:hash) { { description: description } }
|
148
|
+
|
149
|
+
context 'as a regular string' do
|
150
|
+
let(:description) { 'Description' }
|
151
|
+
|
152
|
+
it 'should use the string as the description' do
|
153
|
+
subject.description.should == description
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'as a block' do
|
158
|
+
let(:description) { -> { 'Description' } }
|
159
|
+
|
160
|
+
it 'should call the block and use return value as the description' do
|
161
|
+
value = description.call
|
162
|
+
subject.description.should == value
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
117
166
|
end
|
118
167
|
|
119
168
|
context 'when not given a block' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acclaim
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-06-
|
12
|
+
date: 2012-06-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: jewel
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: ribbon
|
16
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -28,7 +44,7 @@ dependencies:
|
|
28
44
|
- !ruby/object:Gem::Version
|
29
45
|
version: '0'
|
30
46
|
- !ruby/object:Gem::Dependency
|
31
|
-
name:
|
47
|
+
name: redcarpet
|
32
48
|
requirement: !ruby/object:Gem::Requirement
|
33
49
|
none: false
|
34
50
|
requirements:
|
@@ -59,7 +75,39 @@ dependencies:
|
|
59
75
|
- - ! '>='
|
60
76
|
- !ruby/object:Gem::Version
|
61
77
|
version: '0'
|
62
|
-
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: yard
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description:
|
63
111
|
email: matheus.a.m.moreira@gmail.com
|
64
112
|
executables: []
|
65
113
|
extensions: []
|
@@ -72,13 +120,17 @@ files:
|
|
72
120
|
- README.markdown
|
73
121
|
- Rakefile
|
74
122
|
- acclaim.gemspec
|
123
|
+
- acclaim.version
|
75
124
|
- lib/acclaim.rb
|
76
125
|
- lib/acclaim/command.rb
|
126
|
+
- lib/acclaim/command/dsl.rb
|
127
|
+
- lib/acclaim/command/dsl/root.rb
|
77
128
|
- lib/acclaim/command/help.rb
|
78
129
|
- lib/acclaim/command/help/template.rb
|
79
130
|
- lib/acclaim/command/help/template/command.erb
|
80
131
|
- lib/acclaim/command/parser.rb
|
81
132
|
- lib/acclaim/command/version.rb
|
133
|
+
- lib/acclaim/gem.rb
|
82
134
|
- lib/acclaim/option.rb
|
83
135
|
- lib/acclaim/option/arity.rb
|
84
136
|
- lib/acclaim/option/parser.rb
|
@@ -97,7 +149,6 @@ files:
|
|
97
149
|
- lib/acclaim/option/type/symbol.rb
|
98
150
|
- lib/acclaim/option/type/time.rb
|
99
151
|
- lib/acclaim/option/type/uri.rb
|
100
|
-
- lib/acclaim/version.rb
|
101
152
|
- spec/acclaim/command_spec.rb
|
102
153
|
- spec/acclaim/option/arity_spec.rb
|
103
154
|
- spec/acclaim/option/parser/regexp_spec.rb
|
@@ -117,7 +168,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
117
168
|
version: '0'
|
118
169
|
segments:
|
119
170
|
- 0
|
120
|
-
hash:
|
171
|
+
hash: 3293933185562874440
|
121
172
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
173
|
none: false
|
123
174
|
requirements:
|
@@ -126,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
177
|
version: '0'
|
127
178
|
segments:
|
128
179
|
- 0
|
129
|
-
hash:
|
180
|
+
hash: 3293933185562874440
|
130
181
|
requirements: []
|
131
182
|
rubyforge_project:
|
132
183
|
rubygems_version: 1.8.24
|
data/lib/acclaim/version.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
module Acclaim
|
2
|
-
|
3
|
-
# Acclaim's version.
|
4
|
-
module Version
|
5
|
-
|
6
|
-
# Major version.
|
7
|
-
#
|
8
|
-
# Increments denote backward-incompatible changes and additions.
|
9
|
-
MAJOR = 0
|
10
|
-
|
11
|
-
# Minor version.
|
12
|
-
#
|
13
|
-
# Increments denote backward-compatible changes and additions.
|
14
|
-
MINOR = 3
|
15
|
-
|
16
|
-
# Patch version.
|
17
|
-
#
|
18
|
-
# Increments denote changes in implementation.
|
19
|
-
PATCH = 2
|
20
|
-
|
21
|
-
# Build version.
|
22
|
-
#
|
23
|
-
# Used for pre-release versions.
|
24
|
-
BUILD = nil
|
25
|
-
|
26
|
-
# Complete version string, which is every individual version number joined
|
27
|
-
# by a dot (<tt>'.'</tt>), in descending order of prescedence.
|
28
|
-
STRING = [ MAJOR, MINOR, PATCH, BUILD ].compact.join '.'
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|