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.
@@ -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.
@@ -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
@@ -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
@@ -0,0 +1,12 @@
1
+ require File.dirname(__FILE__) + '/../lib/magni'
2
+
3
+ class Test < Magni
4
+ map "--boolean" => :boolean
5
+ map "--array" => :array
6
+ map "--integer" => :integer
7
+ map "--string" => :string
8
+ end
9
+
10
+ class LastKeywordTest < Magni
11
+ map :last => :file
12
+ end
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