acclaim 0.0.1.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|