acclaim 0.0.1.alpha1
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/Gemfile +3 -0
- data/LICENSE.GPLv3 +14 -0
- data/README.markdown +5 -0
- data/Rakefile +44 -0
- data/acclaim.gemspec +20 -0
- data/lib/acclaim.rb +1 -0
- data/lib/acclaim/command.rb +68 -0
- data/lib/acclaim/option.rb +31 -0
- data/lib/acclaim/option/parser.rb +99 -0
- data/lib/acclaim/options.rb +41 -0
- data/lib/acclaim/version.rb +12 -0
- data/spec/acclaim/option/parser_spec.rb +73 -0
- metadata +68 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.lock
|
data/Gemfile
ADDED
data/LICENSE.GPLv3
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright © 2011 Matheus Afonso Martins Moreira
|
2
|
+
|
3
|
+
This program is free software: you can redistribute it and/or modify
|
4
|
+
it under the terms of the GNU General Public License as published by
|
5
|
+
the Free Software Foundation, either version 3 of the License, or
|
6
|
+
(at your option) any later version.
|
7
|
+
|
8
|
+
This program is distributed in the hope that it will be useful,
|
9
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
GNU General Public License for more details.
|
12
|
+
|
13
|
+
You should have received a copy of the GNU General Public License
|
14
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
data/README.markdown
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
this_dir = File.expand_path('..', __FILE__)
|
2
|
+
gem_dir = File.join this_dir, 'gem'
|
3
|
+
spec_file = File.join this_dir, 'acclaim.gemspec'
|
4
|
+
|
5
|
+
spec = Gem::Specification.load spec_file
|
6
|
+
|
7
|
+
task :mkdir do
|
8
|
+
FileUtils.mkdir_p gem_dir
|
9
|
+
end
|
10
|
+
|
11
|
+
task :gem => :mkdir do
|
12
|
+
gem_file = File.join this_dir, Gem::Builder.new(spec).build
|
13
|
+
FileUtils.mv gem_file, gem_dir
|
14
|
+
end
|
15
|
+
|
16
|
+
namespace :gem do
|
17
|
+
|
18
|
+
task :build => :gem
|
19
|
+
|
20
|
+
gem_file = File.join gem_dir, "#{spec.name}-#{spec.version}.gem"
|
21
|
+
|
22
|
+
task :push => :gem do
|
23
|
+
sh "gem push #{gem_file}"
|
24
|
+
end
|
25
|
+
|
26
|
+
task :install => :gem do
|
27
|
+
sh "gem install #{gem_file}"
|
28
|
+
end
|
29
|
+
|
30
|
+
task :uninstall do
|
31
|
+
sh "gem uninstall #{spec.name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
task :clean do
|
35
|
+
FileUtils.rm_rf gem_dir
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
task :clean => 'gem:clean'
|
41
|
+
|
42
|
+
task :setup => [ 'gem:install', :clean ]
|
43
|
+
|
44
|
+
task :default => :setup
|
data/acclaim.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env gem build
|
2
|
+
# encoding: utf-8
|
3
|
+
$:.unshift File.expand_path('../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'acclaim/version'
|
6
|
+
|
7
|
+
Gem::Specification.new('acclaim') do |gem|
|
8
|
+
|
9
|
+
gem.version = Acclaim::Version::STRING
|
10
|
+
gem.summary = 'Command-line option parser and command interface.'
|
11
|
+
gem.description = gem.summary
|
12
|
+
|
13
|
+
gem.author = 'Matheus Afonso Martins Moreira'
|
14
|
+
gem.email = 'matheus.a.m.moreira@gmail.com'
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split "\n"
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
|
20
|
+
end
|
data/lib/acclaim.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
%w(command option option/parser options version).each { |file| require file.prepend 'acclaim/' }
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'acclaim/option'
|
2
|
+
require 'acclaim/option/parser'
|
3
|
+
|
4
|
+
module Acclaim
|
5
|
+
|
6
|
+
# A command is a single word whose meaning the program understands. It calls
|
7
|
+
# upon a function of the program, which may be fine-tuned with options and
|
8
|
+
# given arguments.
|
9
|
+
#
|
10
|
+
# app --global-option do --option
|
11
|
+
#
|
12
|
+
# A subcommand benefits from its parent's option processing.
|
13
|
+
#
|
14
|
+
# app do something --option --option-for-something
|
15
|
+
#
|
16
|
+
# A command can be instantiated in the following form:
|
17
|
+
#
|
18
|
+
# cmd = Command.new :foo do
|
19
|
+
# opt :verbose, short: '-v', long: '--verbose',
|
20
|
+
# description: 'Run verbosely', default: false
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# TODO: make these class methods instead of instance methods, with one class
|
24
|
+
# per command
|
25
|
+
class Command
|
26
|
+
|
27
|
+
attr_accessor :name, :action
|
28
|
+
|
29
|
+
# Initializes a command with a name and evalutes the block if one is given.
|
30
|
+
def initialize(name, &block)
|
31
|
+
self.name = name.to_s
|
32
|
+
instance_eval &block if block
|
33
|
+
end
|
34
|
+
|
35
|
+
# The options this command can take.
|
36
|
+
def options
|
37
|
+
@options ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def option(name, args)
|
41
|
+
args.merge!(:name => name) { |key, old, new| new }
|
42
|
+
options << Option.new(args)
|
43
|
+
end
|
44
|
+
|
45
|
+
alias :opt :option
|
46
|
+
|
47
|
+
# Executes the command with the given options and arguments.
|
48
|
+
def execute(options, *args)
|
49
|
+
action.call options, *args
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_options!(*args)
|
53
|
+
end
|
54
|
+
|
55
|
+
alias :call :execute
|
56
|
+
|
57
|
+
# The commands that may be given to this command.
|
58
|
+
def subcommands
|
59
|
+
@subcommands ||= []
|
60
|
+
end
|
61
|
+
|
62
|
+
def subcommand(*args, &block)
|
63
|
+
subcommands << Command.new(*args, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Acclaim
|
2
|
+
|
3
|
+
# Represents a command-line option.
|
4
|
+
class Option
|
5
|
+
|
6
|
+
attributes = %w(name short long description arity default).map!(&:to_sym).freeze
|
7
|
+
|
8
|
+
attr_accessor *attributes
|
9
|
+
|
10
|
+
def initialize(args = {})
|
11
|
+
args.each do |attribute, value|
|
12
|
+
instance_variable_set :"@#{attribute}", value
|
13
|
+
end
|
14
|
+
yield self if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
def =~(str)
|
18
|
+
str = str.strip
|
19
|
+
long == str or short == str
|
20
|
+
end
|
21
|
+
|
22
|
+
def flag?
|
23
|
+
not arity or arity.empty? or arity == [0, 0]
|
24
|
+
end
|
25
|
+
|
26
|
+
alias :bool? :flag?
|
27
|
+
alias :boolean? :flag?
|
28
|
+
alias :switch? :flag?
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'acclaim/options'
|
2
|
+
|
3
|
+
module Acclaim
|
4
|
+
class Option
|
5
|
+
|
6
|
+
# Parses arrays of strings and returns an Options instance containing data.
|
7
|
+
class Parser
|
8
|
+
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
attr_accessor :argv, :options
|
12
|
+
|
13
|
+
# Initializes a new parser, with the given argument array and set of
|
14
|
+
# options. If no option array is given, the argument array will be
|
15
|
+
# preprocessed only.
|
16
|
+
def initialize(argv, options = nil)
|
17
|
+
self.argv = argv
|
18
|
+
self.options = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse!
|
22
|
+
preprocess_argv!
|
23
|
+
instance = build_options_instance! unless options.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Argument array preprocessing. Does not touch
|
29
|
+
def preprocess_argv!
|
30
|
+
split_multiple_short_options!
|
31
|
+
# TODO: normalize parameter formats?
|
32
|
+
# --switch=PARAM1[,PARAM2,PARAM3] - split on =, then split on comma,
|
33
|
+
# then reinsert them into argv
|
34
|
+
# -sPARAM1[,PARAM2,PARAM3...] - possibly incompatible with split_multiple_short_options!
|
35
|
+
argv.compact!
|
36
|
+
end
|
37
|
+
|
38
|
+
def split_multiple_short_options!
|
39
|
+
argv.find_all { |arg| arg =~ /^-\w{2,}/ }.each do |multiples|
|
40
|
+
multiples_index = argv.index multiples
|
41
|
+
argv.delete multiples
|
42
|
+
options, *parameters = multiples.split /\s+/
|
43
|
+
separated_options = options.sub!(/^-/, '').split(//).map! { |option| option.prepend '-' }
|
44
|
+
separated_options.each_index do |option_index|
|
45
|
+
argv.insert multiples_index + option_index, separated_options[option_index]
|
46
|
+
end
|
47
|
+
last_option_index = argv.index separated_options.last
|
48
|
+
parameters.each_index do |parameter_index|
|
49
|
+
argv.insert last_option_index + paramter_index + 1,
|
50
|
+
parameters[parameter_index]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_options_instance!
|
56
|
+
Options.new.tap do |options_instance|
|
57
|
+
options.each do |option|
|
58
|
+
key = option.name.to_s.to_sym
|
59
|
+
options_instance[key] = option.default
|
60
|
+
args = argv.find_all { |arg| option =~ arg }
|
61
|
+
if args.any?
|
62
|
+
if option.flag?
|
63
|
+
options_instance[key] = true
|
64
|
+
else
|
65
|
+
minimum, optional = option.arity
|
66
|
+
args.each do |arg|
|
67
|
+
arg_index = argv.index arg
|
68
|
+
len = if optional >= 0
|
69
|
+
arg_index + minimum + optional
|
70
|
+
else
|
71
|
+
argv.length - 1
|
72
|
+
end
|
73
|
+
params = argv[arg_index + 1, len]
|
74
|
+
values = []
|
75
|
+
params.each do |param|
|
76
|
+
break if param.nil? or param =~ /^-{1,2}/ or param =~ /^-{2,}$/
|
77
|
+
values << param
|
78
|
+
end
|
79
|
+
count = values.count
|
80
|
+
if count < minimum
|
81
|
+
raise Error, "Wrong number of arguments (%d for %d)" % [count, minimum]
|
82
|
+
end
|
83
|
+
options_instance[key] = if minimum == 1 and optional.zero?
|
84
|
+
values.first
|
85
|
+
else
|
86
|
+
values
|
87
|
+
end
|
88
|
+
values.each { |value| argv.delete value }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
args.each { |arg| argv.delete arg }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Acclaim
|
2
|
+
|
3
|
+
# Represents a set of options.
|
4
|
+
class Options
|
5
|
+
|
6
|
+
def [](key)
|
7
|
+
data[key]
|
8
|
+
end
|
9
|
+
|
10
|
+
def []=(key, value)
|
11
|
+
data[key] = value
|
12
|
+
end
|
13
|
+
|
14
|
+
# Handles the following cases:
|
15
|
+
#
|
16
|
+
# options.method = value
|
17
|
+
# options.method?
|
18
|
+
# options.method
|
19
|
+
def method_missing(method, *args, &block)
|
20
|
+
m = method.to_s
|
21
|
+
case m
|
22
|
+
when /=$/
|
23
|
+
self[:"#{m.chop!}"] = args.first
|
24
|
+
when /\?$/
|
25
|
+
self[:"#{m.chop!}"] ? true : false
|
26
|
+
else
|
27
|
+
self[method]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# The option values
|
34
|
+
def data
|
35
|
+
@options ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'acclaim/option'
|
2
|
+
require 'acclaim/option/parser'
|
3
|
+
|
4
|
+
describe Acclaim::Option::Parser do
|
5
|
+
|
6
|
+
describe '#parse!' do
|
7
|
+
|
8
|
+
let!(:args) do
|
9
|
+
%w(cmd subcmd -a -b PARAM1 -cdef PARAM2 --long --parameters PARAM3 PARAM4 -- FILE1 FILE2)
|
10
|
+
end
|
11
|
+
|
12
|
+
subject { Acclaim::Option::Parser.new(args) }
|
13
|
+
|
14
|
+
it 'should split multiple short options' do
|
15
|
+
new_argv = args.dup.tap do |args|
|
16
|
+
args[args.index('-cdef')] = %w[-c -d -e -f]
|
17
|
+
end.flatten!
|
18
|
+
subject.parse!
|
19
|
+
args.should == new_argv
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when not given an array of options' do
|
23
|
+
|
24
|
+
it 'should not return an options instance' do
|
25
|
+
subject.parse!.should be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when given an array of options' do
|
31
|
+
|
32
|
+
let(:options) do
|
33
|
+
[].tap do |opts|
|
34
|
+
('a'..'f').each do |c|
|
35
|
+
hash = { name: c, short: "-#{c}" }
|
36
|
+
hash[:arity] = [1, 0] if c == 'b' or c == 'f'
|
37
|
+
opts << Acclaim::Option.new(hash)
|
38
|
+
end
|
39
|
+
opts << Acclaim::Option.new(name: 'long', long: '--long')
|
40
|
+
opts << Acclaim::Option.new(name: 'params', long: '--parameters',
|
41
|
+
arity: [1, -1], default: [])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
subject { Acclaim::Option::Parser.new(args, options) }
|
46
|
+
|
47
|
+
it 'should return an options instance' do
|
48
|
+
subject.parse!.should_not be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should parse arguments correctly' do
|
52
|
+
subject.parse!.instance_eval do
|
53
|
+
a?.should be_true
|
54
|
+
b.should == 'PARAM1'
|
55
|
+
c?.should be_true
|
56
|
+
d?.should be_true
|
57
|
+
e?.should be_true
|
58
|
+
f.should == 'PARAM2'
|
59
|
+
long?.should be_true
|
60
|
+
params.should == %w(PARAM3 PARAM4)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should leave unparsed arguments in argv' do
|
65
|
+
subject.parse!
|
66
|
+
args.should == %w(cmd subcmd -- FILE1 FILE2)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acclaim
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.alpha1
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matheus Afonso Martins Moreira
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &14045320 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *14045320
|
25
|
+
description: Command-line option parser and command interface.
|
26
|
+
email: matheus.a.m.moreira@gmail.com
|
27
|
+
executables: []
|
28
|
+
extensions: []
|
29
|
+
extra_rdoc_files: []
|
30
|
+
files:
|
31
|
+
- .gitignore
|
32
|
+
- Gemfile
|
33
|
+
- LICENSE.GPLv3
|
34
|
+
- README.markdown
|
35
|
+
- Rakefile
|
36
|
+
- acclaim.gemspec
|
37
|
+
- lib/acclaim.rb
|
38
|
+
- lib/acclaim/command.rb
|
39
|
+
- lib/acclaim/option.rb
|
40
|
+
- lib/acclaim/option/parser.rb
|
41
|
+
- lib/acclaim/options.rb
|
42
|
+
- lib/acclaim/version.rb
|
43
|
+
- spec/acclaim/option/parser_spec.rb
|
44
|
+
homepage:
|
45
|
+
licenses: []
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>'
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.1
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.8.10
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Command-line option parser and command interface.
|
68
|
+
test_files: []
|