magni 0.1.1
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/README.rdoc +76 -0
- data/lib/magni.rb +141 -0
- data/spec/magni_spec.rb +107 -0
- data/spec/spec_helper.rb +12 -0
- metadata +64 -0
data/README.rdoc
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
= Magni
|
2
|
+
|
3
|
+
Magni is a command line flag parser, nothing more, nothing less. It's
|
4
|
+
useful for when you want to collect a bunch of flags from your script,
|
5
|
+
then do something with them all.
|
6
|
+
==
|
7
|
+
=== ARGUMENT TYPES
|
8
|
+
|
9
|
+
Magni accepts four types of arguments, <tt>boolean</tt>, <tt>string</tt>,
|
10
|
+
<tt>array</tt> and <tt>integer</tt>.
|
11
|
+
|
12
|
+
boolean: --foo #=> true
|
13
|
+
integer: --foo=4 #=> 4
|
14
|
+
string: --foo=theory #=> "theory"
|
15
|
+
array: --foo=1,2,3 #=> [1, 2, 3]
|
16
|
+
|
17
|
+
=== MAPPINGS
|
18
|
+
|
19
|
+
Magni is the son of Thor[http://github.com/wycats/thor] and so he inherited
|
20
|
+
some of his father's traits; mapping being one of them.
|
21
|
+
|
22
|
+
class Runner < Magni
|
23
|
+
map "--maxerr" => :integer
|
24
|
+
map "--name" => :string
|
25
|
+
map "--help" => :boolean
|
26
|
+
end
|
27
|
+
|
28
|
+
There are a couple special keywords that work with mappings too, <tt>:first</tt>
|
29
|
+
and <tt>:last</tt>. Each one will place the first or last, respectively,
|
30
|
+
argument in an instance variable for you. This is useful when the last
|
31
|
+
argument of your script is a file or command for example.
|
32
|
+
|
33
|
+
map :last => :file
|
34
|
+
|
35
|
+
> script --hello world.rb
|
36
|
+
@file #=> "world.rb"
|
37
|
+
|
38
|
+
=== BIN SCRIPT
|
39
|
+
|
40
|
+
Tell Magni where to go after it does it's flag parsing magic.
|
41
|
+
<tt>delegate_to</tt> takes two arguments. The first is your Magni subclass
|
42
|
+
and the second is the method you want to be called when Magni is done:
|
43
|
+
|
44
|
+
Magni.delegate_to(Runner, :method)
|
45
|
+
|
46
|
+
=== USING THE FLAGS
|
47
|
+
|
48
|
+
Flags and values are saved in <tt>@options</tt>. From the example above:
|
49
|
+
|
50
|
+
> script --maxerr=4 --name=Matte
|
51
|
+
@options #=> { :maxerr => 4, :name => "Matte" }
|
52
|
+
|
53
|
+
== License
|
54
|
+
|
55
|
+
(The MIT License)
|
56
|
+
|
57
|
+
Copyright (c) 2010 FIXME full name
|
58
|
+
|
59
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
60
|
+
a copy of this software and associated documentation files (the
|
61
|
+
'Software'), to deal in the Software without restriction, including
|
62
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
63
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
64
|
+
permit persons to whom the Software is furnished to do so, subject to
|
65
|
+
the following conditions:
|
66
|
+
|
67
|
+
The above copyright notice and this permission notice shall be
|
68
|
+
included in all copies or substantial portions of the Software.
|
69
|
+
|
70
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
71
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
72
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
73
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
74
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
75
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
76
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/magni.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
class MagniAttributeError < Exception; end
|
2
|
+
|
3
|
+
class Magni
|
4
|
+
class << self
|
5
|
+
attr_accessor :mappings
|
6
|
+
attr_accessor :keywords
|
7
|
+
|
8
|
+
# Maps a flag to a argument type. Flags are then put into the
|
9
|
+
# <tt>options</tt> instance variable. You can map two types:
|
10
|
+
#
|
11
|
+
# Literal Double Dash Flags:
|
12
|
+
#
|
13
|
+
# map "--maxerrors" => :integer
|
14
|
+
#
|
15
|
+
# These can be one of four types: <tt>:integer</tt>, <tt>:string</tt>,
|
16
|
+
# <tt>:array</tt> and <tt>:boolean</tt>.
|
17
|
+
#
|
18
|
+
# :integer #=> --count=2
|
19
|
+
# :string #=> --word=bird
|
20
|
+
# :array #=> --ravens=huginn,munnin
|
21
|
+
# :boolean #=> --all
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# Special Keywords are used to pull out a non-flag argument. This is
|
25
|
+
# useful when the first or last argument needs to be the target of
|
26
|
+
# your script; a file for example.
|
27
|
+
#
|
28
|
+
# Special keywords map to an instance variable on the instance your
|
29
|
+
# class. The following example will pull out the last argument from
|
30
|
+
# the list and put it in the <tt>@file</tt> instance variable.
|
31
|
+
# Currently there are only two keyword mappings, <tt>:first</tt> and
|
32
|
+
# <tt>last</tt>:
|
33
|
+
#
|
34
|
+
# map :first => :foo
|
35
|
+
# map :last => :bar
|
36
|
+
#
|
37
|
+
#
|
38
|
+
# Parameters
|
39
|
+
# mapping: Hash[ Flag/Keyword => Type/iVar ]
|
40
|
+
#
|
41
|
+
def map(mapping={})
|
42
|
+
@mappings ||= {}
|
43
|
+
@keywords ||= {}
|
44
|
+
|
45
|
+
mapping.each do |flag, type|
|
46
|
+
if is_map_keyword?(flag)
|
47
|
+
@keywords[flag] = type
|
48
|
+
else
|
49
|
+
@mappings[flag.gsub(/[-]+/, "")] = type
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Tells Magni where to send the processed flags and arguments.
|
55
|
+
#
|
56
|
+
# # runner.rb
|
57
|
+
# class Runner < Magni
|
58
|
+
# map "--help" => :boolean
|
59
|
+
#
|
60
|
+
# def start
|
61
|
+
# ...
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# # bin_script.rb
|
66
|
+
# Magni.delegate_to(Runner, :start)
|
67
|
+
#
|
68
|
+
# Parameters
|
69
|
+
#
|
70
|
+
# klass: Your "runner" class
|
71
|
+
# method: The method you want executed after flags are processed
|
72
|
+
#
|
73
|
+
def delegate_to(klass, method)
|
74
|
+
klass.new(ARGV.dup).send(method)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def is_map_keyword?(flag)
|
80
|
+
[:first, :last].include?(flag)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_accessor :options
|
85
|
+
|
86
|
+
def initialize(options=[])
|
87
|
+
self.class.mappings ||= {}
|
88
|
+
@options ||= {}
|
89
|
+
|
90
|
+
options = extract_keywords(options)
|
91
|
+
options.each do |opt|
|
92
|
+
flag, *values = opt.gsub(/[-]+/, "").split(/[=,]/)
|
93
|
+
process(flag, values)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Pulls the keyworded flags out of the ARGV array and puts them
|
98
|
+
# in the mapped instance variables.
|
99
|
+
#
|
100
|
+
def extract_keywords(opts=[])
|
101
|
+
self.class.keywords.each do |keyword, ivar|
|
102
|
+
Magni.class_eval { attr_accessor ivar }
|
103
|
+
instance_variable_set("@#{ ivar }", opts.delete(opts.send(keyword)))
|
104
|
+
end
|
105
|
+
opts
|
106
|
+
end
|
107
|
+
|
108
|
+
# Pulls the type of argument +flag+ is set to in +mappings+
|
109
|
+
# then sets the +options+ key to value "coerced" to the
|
110
|
+
# matching argument type.
|
111
|
+
#
|
112
|
+
def process(flag, values)
|
113
|
+
type = self.class.mappings[flag]
|
114
|
+
unless type.nil?
|
115
|
+
@options[flag.to_sym] = send("to_#{ type }", values)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def to_boolean(values=[])
|
122
|
+
raise MagniAttributeError, "Expected boolean." unless values.empty?
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_integer(values=[])
|
127
|
+
raise MagniAttributeError, "Expected integer." unless values[0] =~ /\d/
|
128
|
+
values.pop.to_i
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_array(values=[])
|
132
|
+
raise MagniAttributeError, "Expected array." if values.empty?
|
133
|
+
values.map { |value| (value.to_i if value =~ /\d/) || value }
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_string(values=[])
|
137
|
+
raise MagniAttributeError, "Expected string." if values.size > 1
|
138
|
+
values.pop.to_s
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
data/spec/magni_spec.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Magni do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@test = Test.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "mappings" do
|
10
|
+
it "should map to :boolean" do
|
11
|
+
Test.mappings['boolean'].should == :boolean
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should map to :integer" do
|
15
|
+
Test.mappings['integer'].should == :integer
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should map to :string" do
|
19
|
+
Test.mappings['string'].should == :string
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should map to :array" do
|
23
|
+
Test.mappings['array'].should == :array
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "booleans" do
|
28
|
+
it "should be processes into a boolean" do
|
29
|
+
@test.process("boolean", [])
|
30
|
+
@test.options[:boolean].should == true
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should raise an error when passed a value" do
|
34
|
+
lambda { Test.new(["--boolean=wrong"]) }.should raise_error
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "integers" do
|
39
|
+
it "should be processes into a fixnum" do
|
40
|
+
@test.process("integer", ["2"])
|
41
|
+
@test.options[:integer].should == 2
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise an error when passed a string" do
|
45
|
+
lambda { Test.new(["--integer=abc"]) }.should raise_error
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "strings" do
|
50
|
+
it "should be processes into a string" do
|
51
|
+
@test.process("string", ["hello"])
|
52
|
+
@test.options[:string].should == "hello"
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should raise an error when passed an array" do
|
56
|
+
lambda { Test.new(["--string=a,b,c"]) }.should raise_error
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "arrays" do
|
61
|
+
it "should be processes into an array" do
|
62
|
+
@test.process("array", ["a", "b", "c"])
|
63
|
+
@test.options[:array].should == ["a", "b", "c"]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should raise an error when no values are received" do
|
67
|
+
lambda { Test.new(["--array"]) }.should raise_error
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "keywords" do
|
72
|
+
it "should map the last argument to an instance variable" do
|
73
|
+
LastKeywordTest.keywords[:last].should == :file
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should populate the instance variable with the :last flag value" do
|
77
|
+
test = LastKeywordTest.new(["example.js"])
|
78
|
+
test.file.should == "example.js"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should accept multiple arguments" do
|
83
|
+
test = Test.new(["--integer=9", "--string=wing", "--array=1,2,3,4"])
|
84
|
+
test.options[:integer].should == 9
|
85
|
+
test.options[:string].should == "wing"
|
86
|
+
test.options[:array].should == [1, 2, 3, 4]
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should silently ignore non-flag command line arguments" do
|
90
|
+
lambda { Test.new(["omgdontfail", "whatthe"]) }.should_not raise_error
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should not set non-flag arguments in options" do
|
94
|
+
test = Test.new(["omgdontfail"])
|
95
|
+
test.options.should be_empty
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should delegate to the class given" do
|
99
|
+
class Controller; def invoke; end; end
|
100
|
+
controller = Controller.new
|
101
|
+
Controller.stub!(:new).and_return(controller)
|
102
|
+
|
103
|
+
controller.should_receive(:invoke)
|
104
|
+
Magni.delegate_to(Controller, :invoke)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: magni
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Matte Noble
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-23 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Simple command line flag parser.
|
22
|
+
email: me@mattenoble.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- README.rdoc
|
31
|
+
- lib/magni.rb
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://github.com/mnoble/magni
|
34
|
+
licenses: []
|
35
|
+
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options:
|
38
|
+
- --charset=UTF-8
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
version: "0"
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.3.6
|
59
|
+
signing_key:
|
60
|
+
specification_version: 3
|
61
|
+
summary: Simple command line flag parser.
|
62
|
+
test_files:
|
63
|
+
- spec/spec_helper.rb
|
64
|
+
- spec/magni_spec.rb
|