acclaim 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|