acclaim 0.0.1.alpha1 → 0.0.1.alpha2

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/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create 1.9.3@acclaim
data/acclaim.gemspec CHANGED
@@ -9,6 +9,7 @@ Gem::Specification.new('acclaim') do |gem|
9
9
  gem.version = Acclaim::Version::STRING
10
10
  gem.summary = 'Command-line option parser and command interface.'
11
11
  gem.description = gem.summary
12
+ gem.homepage = 'https://github.com/matheusmoreira/acclaim'
12
13
 
13
14
  gem.author = 'Matheus Afonso Martins Moreira'
14
15
  gem.email = 'matheus.a.m.moreira@gmail.com'
@@ -1,5 +1,6 @@
1
1
  require 'acclaim/option'
2
2
  require 'acclaim/option/parser'
3
+ require 'acclaim/options'
3
4
 
4
5
  module Acclaim
5
6
 
@@ -15,52 +16,112 @@ module Acclaim
15
16
  #
16
17
  # A command can be instantiated in the following form:
17
18
  #
18
- # cmd = Command.new :foo do
19
- # opt :verbose, short: '-v', long: '--verbose',
20
- # description: 'Run verbosely', default: false
19
+ # class App::Command < Acclaim::Command
20
+ # opt :verbose, names: %w(-v --verbose), description: 'Run verbosely'
21
21
  # end
22
22
  #
23
- # TODO: make these class methods instead of instance methods, with one class
24
- # per command
23
+ # A subcommand can be created by inheriting from another command:
24
+ #
25
+ # class App::Command::Do < App::Command
26
+ # opt :what, names: %w(-W --what), description: 'Do what?', arity: [1, 0], required: true
27
+ # when_called do |options, arguments|
28
+ # puts "Verbose? #{options.verbose? ? 'yes' : 'no'}"
29
+ # puts "Doing #{options.what} with #{arguments.inspect} now!"
30
+ # end
31
+ # end
32
+ #
33
+ # Then, in your application's binary, you may simply write:
34
+ #
35
+ # App::Command.run *ARGV
36
+ #
37
+ # Subcommands inherit their parent's option processing:
38
+ #
39
+ # $ app --verbose -W test do arg1 arg2
40
+ # Verbose? yes
41
+ # Doing test with ["arg1", "arg2"] now!
25
42
  class Command
26
43
 
27
- attr_accessor :name, :action
44
+ # Module containing the class methods every command class should inherit.
45
+ module ClassMethods
28
46
 
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
47
+ # String which calls this command.
48
+ def line(value = nil)
49
+ @line = value if value
50
+ @line
51
+ end
34
52
 
35
- # The options this command can take.
36
- def options
37
- @options ||= []
38
- end
53
+ # Commands which may be given to this command.
54
+ def subcommands
55
+ @subcommands ||= []
56
+ end
39
57
 
40
- def option(name, args)
41
- args.merge!(:name => name) { |key, old, new| new }
42
- options << Option.new(args)
43
- end
58
+ # The options this command can take.
59
+ def options
60
+ @options ||= []
61
+ end
44
62
 
45
- alias :opt :option
63
+ # Adds an option to this command.
64
+ def option(key, args = {})
65
+ args.merge!(key: key)
66
+ options << Option.new(args)
67
+ end
46
68
 
47
- # Executes the command with the given options and arguments.
48
- def execute(options, *args)
49
- action.call options, *args
50
- end
69
+ alias :opt :option
51
70
 
52
- def parse_options!(*args)
53
- end
71
+ # The block which is executed when this command is called. It is given 2
72
+ # parameters; the first is an Options instance which can be queried for
73
+ # settings information; the second is the remaining command line.
74
+ def action(&block)
75
+ @action = block
76
+ end
77
+
78
+ alias :when_called :action
79
+
80
+ # Parses the argument array using this command's set of options.
81
+ def parse_options!(args)
82
+ Option::Parser.new(args, options).parse!
83
+ end
84
+
85
+ # Invokes this command with a fresh set of options.
86
+ def run(*args)
87
+ invoke Options.new, args
88
+ rescue Option::Parser::Error => e
89
+ puts e.message
90
+ end
91
+
92
+ # Parses the argument array. If the first element of the argument array
93
+ # corresponds to a subcommand, it will be invoked with said array and
94
+ # with this command's parsed options. This command will be executed
95
+ # otherwise.
96
+ def invoke(opts, args = [])
97
+ opts.merge! parse_options!(args)
98
+ subcommands.find do |subcommand|
99
+ subcommand.line == args.first
100
+ end.tap do |subcommand|
101
+ if subcommand
102
+ args.delete subcommand.line
103
+ subcommand.invoke(opts, args)
104
+ else
105
+ execute(opts, args)
106
+ end
107
+ end
108
+ end
109
+
110
+ # Calls this command's action block with the given options and arguments.
111
+ def execute(opts, args)
112
+ @action.call opts, args
113
+ end
54
114
 
55
- alias :call :execute
115
+ alias :call :execute
56
116
 
57
- # The commands that may be given to this command.
58
- def subcommands
59
- @subcommands ||= []
60
117
  end
61
118
 
62
- def subcommand(*args, &block)
63
- subcommands << Command.new(*args, &block)
119
+ # Add the class methods to the subclass and add it to this command's list of
120
+ # subcommands.
121
+ def self.inherited(sub)
122
+ sub.extend ClassMethods
123
+ sub.line sub.name.gsub(/^.*::/, '').downcase
124
+ subcommands << sub if respond_to? :subcommands
64
125
  end
65
126
 
66
127
  end
@@ -3,7 +3,7 @@ module Acclaim
3
3
  # Represents a command-line option.
4
4
  class Option
5
5
 
6
- attributes = %w(name short long description arity default).map!(&:to_sym).freeze
6
+ attributes = %w(key names description arity default).map!(&:to_sym).freeze
7
7
 
8
8
  attr_accessor *attributes
9
9
 
@@ -15,8 +15,19 @@ module Acclaim
15
15
  end
16
16
 
17
17
  def =~(str)
18
- str = str.strip
19
- long == str or short == str
18
+ names.include? str.strip
19
+ end
20
+
21
+ def required?
22
+ @required
23
+ end
24
+
25
+ def required=(value)
26
+ @required = value
27
+ end
28
+
29
+ def require
30
+ self.required = true
20
31
  end
21
32
 
22
33
  def flag?
@@ -6,7 +6,17 @@ module Acclaim
6
6
  # Parses arrays of strings and returns an Options instance containing data.
7
7
  class Parser
8
8
 
9
- class Error < StandardError; end
9
+ class Error < StandardError
10
+
11
+ def self.raise_wrong_arg_number(actual, minimum, optional)
12
+ raise self, "Wrong number of arguments (#{actual} for #{minimum})"
13
+ end
14
+
15
+ def self.raise_missing_arg(arg)
16
+ raise self, "Missing required argument (#{arg})"
17
+ end
18
+
19
+ end
10
20
 
11
21
  attr_accessor :argv, :options
12
22
 
@@ -55,7 +65,7 @@ module Acclaim
55
65
  def build_options_instance!
56
66
  Options.new.tap do |options_instance|
57
67
  options.each do |option|
58
- key = option.name.to_s.to_sym
68
+ key = option.key.to_sym
59
69
  options_instance[key] = option.default
60
70
  args = argv.find_all { |arg| option =~ arg }
61
71
  if args.any?
@@ -73,13 +83,15 @@ module Acclaim
73
83
  params = argv[arg_index + 1, len]
74
84
  values = []
75
85
  params.each do |param|
76
- break if param.nil? or param =~ /^-{1,2}/ or param =~ /^-{2,}$/
77
- values << param
86
+ case param
87
+ when nil, /^-{1,2}/, /^-{2,}$/ then break
88
+ else
89
+ break if optional >= 0 and values.count >= minimum + optional
90
+ values << param
91
+ end
78
92
  end
79
93
  count = values.count
80
- if count < minimum
81
- raise Error, "Wrong number of arguments (%d for %d)" % [count, minimum]
82
- end
94
+ Error.raise_wrong_arg_number count, *option.arity if count < minimum
83
95
  options_instance[key] = if minimum == 1 and optional.zero?
84
96
  values.first
85
97
  else
@@ -89,6 +101,8 @@ module Acclaim
89
101
  end
90
102
  end
91
103
  args.each { |arg| argv.delete arg }
104
+ else
105
+ Error.raise_missing_arg(option.names.join ' | ') if option.required?
92
106
  end
93
107
  end
94
108
  end
@@ -11,6 +11,10 @@ module Acclaim
11
11
  data[key] = value
12
12
  end
13
13
 
14
+ def merge!(other, &block)
15
+ data.merge! other.data, &block
16
+ end
17
+
14
18
  # Handles the following cases:
15
19
  #
16
20
  # options.method = value
@@ -28,7 +32,7 @@ module Acclaim
28
32
  end
29
33
  end
30
34
 
31
- private
35
+ protected
32
36
 
33
37
  # The option values
34
38
  def data
@@ -4,7 +4,7 @@ module Acclaim
4
4
  MAJOR = 0
5
5
  MINOR = 0
6
6
  PATCH = 1
7
- BUILD = 'alpha1'
7
+ BUILD = 'alpha2'
8
8
 
9
9
  STRING = [ MAJOR, MINOR, PATCH, BUILD ].compact.join '.'
10
10
 
@@ -6,7 +6,7 @@ describe Acclaim::Option::Parser do
6
6
  describe '#parse!' do
7
7
 
8
8
  let!(:args) do
9
- %w(cmd subcmd -a -b PARAM1 -cdef PARAM2 --long --parameters PARAM3 PARAM4 -- FILE1 FILE2)
9
+ %w(cmd -a subcmd -b PARAM1 -cdef PARAM2 --long --parameters PARAM3 PARAM4 PARAM5 -- FILE1 FILE2)
10
10
  end
11
11
 
12
12
  subject { Acclaim::Option::Parser.new(args) }
@@ -32,13 +32,14 @@ describe Acclaim::Option::Parser do
32
32
  let(:options) do
33
33
  [].tap do |opts|
34
34
  ('a'..'f').each do |c|
35
- hash = { name: c, short: "-#{c}" }
35
+ hash = { key: c, names: ["-#{c}"] }
36
36
  hash[:arity] = [1, 0] if c == 'b' or c == 'f'
37
+ hash[:required] = true if c == 'd'
37
38
  opts << Acclaim::Option.new(hash)
38
39
  end
39
- opts << Acclaim::Option.new(name: 'long', long: '--long')
40
- opts << Acclaim::Option.new(name: 'params', long: '--parameters',
41
- arity: [1, -1], default: [])
40
+ opts << Acclaim::Option.new(key: 'long', names: ['--long'])
41
+ opts << Acclaim::Option.new(key: 'params', names: ['--parameters'],
42
+ arity: [1, 1], default: [])
42
43
  end
43
44
  end
44
45
 
@@ -63,7 +64,29 @@ describe Acclaim::Option::Parser do
63
64
 
64
65
  it 'should leave unparsed arguments in argv' do
65
66
  subject.parse!
66
- args.should == %w(cmd subcmd -- FILE1 FILE2)
67
+ args.should == %w(cmd subcmd PARAM5 -- FILE1 FILE2)
68
+ end
69
+
70
+ context 'but not given a required parameter' do
71
+
72
+ let!(:args) { %w(-db) }
73
+
74
+ it 'should raise an error' do
75
+ expect { subject.parse! }.to raise_error Acclaim::Option::Parser::Error,
76
+ /number of arguments/
77
+ end
78
+
79
+ end
80
+
81
+ context 'but not passed a required option' do
82
+
83
+ let!(:args) { [] }
84
+
85
+ it 'should raise an error' do
86
+ expect { subject.parse! }.to raise_error Acclaim::Option::Parser::Error,
87
+ /required/
88
+ end
89
+
67
90
  end
68
91
 
69
92
  end
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.0.1.alpha1
4
+ version: 0.0.1.alpha2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-16 00:00:00.000000000 Z
12
+ date: 2011-12-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &14045320 !ruby/object:Gem::Requirement
16
+ requirement: &7316860 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *14045320
24
+ version_requirements: *7316860
25
25
  description: Command-line option parser and command interface.
26
26
  email: matheus.a.m.moreira@gmail.com
27
27
  executables: []
@@ -29,6 +29,7 @@ extensions: []
29
29
  extra_rdoc_files: []
30
30
  files:
31
31
  - .gitignore
32
+ - .rvmrc
32
33
  - Gemfile
33
34
  - LICENSE.GPLv3
34
35
  - README.markdown
@@ -41,7 +42,7 @@ files:
41
42
  - lib/acclaim/options.rb
42
43
  - lib/acclaim/version.rb
43
44
  - spec/acclaim/option/parser_spec.rb
44
- homepage:
45
+ homepage: https://github.com/matheusmoreira/acclaim
45
46
  licenses: []
46
47
  post_install_message:
47
48
  rdoc_options: []