shebang 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gems +3 -0
- data/.gitignore +10 -0
- data/.rvmrc +1 -0
- data/.travis.yml +16 -0
- data/LICENSE +19 -0
- data/README.md +154 -0
- data/Rakefile +11 -0
- data/example/basic.rb +35 -0
- data/lib/.gitkeep +0 -0
- data/lib/shebang.rb +104 -0
- data/lib/shebang/.gitkeep +0 -0
- data/lib/shebang/command.rb +274 -0
- data/lib/shebang/option.rb +95 -0
- data/lib/shebang/spec/bacon/color_output.rb +39 -0
- data/lib/shebang/spec/helper.rb +65 -0
- data/lib/shebang/version.rb +4 -0
- data/pkg/.gitkeep +0 -0
- data/shebang.gemspec +21 -0
- data/spec/.gitkeep +0 -0
- data/spec/fixtures/command.rb +21 -0
- data/spec/helper.rb +2 -0
- data/spec/shebang/command.rb +39 -0
- data/spec/shebang/option.rb +23 -0
- data/spec/shebang/shebang.rb +66 -0
- data/task/build.rake +33 -0
- data/task/test.rake +6 -0
- metadata +113 -0
data/.gems
ADDED
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create 1.9.2@shebang
|
data/.travis.yml
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011, Yorick Peterse
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# README
|
2
|
+
|
3
|
+
Shebang is a nice wrapper around OptionParser that makes it easier to write
|
4
|
+
commandline executables. I wrote it after getting fed up of having to re-invent
|
5
|
+
the same wheel every time I wanted to write a commandline executable. For
|
6
|
+
example, a relatively simple command using OptionParser directly may look like
|
7
|
+
the following:
|
8
|
+
|
9
|
+
require 'optparse'
|
10
|
+
|
11
|
+
@options = {:force => false, :f => false, :name => nil, :n => nil}
|
12
|
+
parser = OptionParser.new do |opt|
|
13
|
+
opt.banner = <<-TXT.strip
|
14
|
+
Runs an example command.
|
15
|
+
|
16
|
+
Usage:
|
17
|
+
$ foobar.rb [OPTIONS]
|
18
|
+
TXT
|
19
|
+
|
20
|
+
opt.summary_indent = ' '
|
21
|
+
|
22
|
+
opt.separator "\nOptions:\n"
|
23
|
+
|
24
|
+
opt.on('-h', '--help', 'Shows this help message') do
|
25
|
+
puts parser
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
opt.on('-v', '--version', 'Shows the current version') do
|
30
|
+
puts '0.1'
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
|
34
|
+
opt.on('-f', '--force', 'Forces the command to run') do
|
35
|
+
@options[:force] = @options[:f] = true
|
36
|
+
end
|
37
|
+
|
38
|
+
opt.on('-n', '--name NAME', 'A person\'s name') do |name|
|
39
|
+
@options[:name] = @options[:n] = name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
parser.parse!
|
44
|
+
|
45
|
+
puts "Your name is #{@options[:name]}"
|
46
|
+
|
47
|
+
Using Shebang this can be done as following:
|
48
|
+
|
49
|
+
require 'shebang'
|
50
|
+
|
51
|
+
class Greet < Shebang::Command
|
52
|
+
command :default
|
53
|
+
banner 'Runs an example command.'
|
54
|
+
usage '$ foobar.rb [OPTIONS]'
|
55
|
+
|
56
|
+
o :h, :help , 'Shows this help message' , :method => :help
|
57
|
+
o :v, :version, 'Shows the current version', :method => :version
|
58
|
+
o :f, :force , 'Forces the command to run'
|
59
|
+
o :n, :name , 'A person\'s name', :type => String
|
60
|
+
|
61
|
+
def version
|
62
|
+
puts '0.1'
|
63
|
+
exit
|
64
|
+
end
|
65
|
+
|
66
|
+
def index
|
67
|
+
puts "Your name is #{@options[:n]}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
Shebang.run
|
72
|
+
|
73
|
+
## Usage
|
74
|
+
|
75
|
+
As shown in the example above commands can be created by extending the class
|
76
|
+
``Shebang::Command`` and calling the class method ``command()``. Each command
|
77
|
+
required an instance method called ``index()`` to be defined, this method is
|
78
|
+
called once OptionParser has been set up and the commandline arguments have
|
79
|
+
been parsed:
|
80
|
+
|
81
|
+
require 'shebang'
|
82
|
+
|
83
|
+
class Greet < Shebang::Command
|
84
|
+
command :default
|
85
|
+
|
86
|
+
def index
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
Options can be retrieved using the method ``option()`` which takes either the
|
92
|
+
short or long name of an option, in both cases it will result in the same value
|
93
|
+
(given the option names belong to the same option):
|
94
|
+
|
95
|
+
option(:h) === option(:help) # => true
|
96
|
+
|
97
|
+
Options can be added using the class method ``option()`` or it's alias ``o()``.
|
98
|
+
Besides the features offered by OptionParser options can specify a method to
|
99
|
+
execute in case that particular option has been specified. This can be done by
|
100
|
+
passing the ``:method`` key to the option method:
|
101
|
+
|
102
|
+
option :f, :foobar, 'Calls a method', :method => :foobar
|
103
|
+
|
104
|
+
Now whenever the ``-f`` of ``--foobar`` option is set the method ``foobar()``
|
105
|
+
will be executed **without** stopping the rest of the command. This means that
|
106
|
+
you'll have to manually call ``Kernel.exit()`` if you want to stop the execution
|
107
|
+
process if a certain option is specified.
|
108
|
+
|
109
|
+
Shebang comes with support for defining sub commands. Sub commands are nothing
|
110
|
+
more than different methods than the default one. Say you have a class
|
111
|
+
``Git::Submodule``, to run the command itself you'd invoke the default method on
|
112
|
+
this class (index by default):
|
113
|
+
|
114
|
+
cmd = Git::Submodule.new
|
115
|
+
|
116
|
+
cmd.parse([...])
|
117
|
+
cmd.index
|
118
|
+
|
119
|
+
To show the status of a submodule you'd invoke ``git submodule status``, this
|
120
|
+
translates to the following code:
|
121
|
+
|
122
|
+
cmd = Git::Submodule.new
|
123
|
+
|
124
|
+
cmd.parse([...])
|
125
|
+
cmd.status
|
126
|
+
|
127
|
+
Shebang takes care of this using ``Shebang.run()``. The first commandline
|
128
|
+
argument that does not start with ``-`` (and this isn't an option) is considered
|
129
|
+
the command name. If there's another argument that's not a switch following that
|
130
|
+
one will be used as the method name. This means that in order to invoke
|
131
|
+
``Git::Submodule#index`` you'd type the following into your terminal:
|
132
|
+
|
133
|
+
git.rb submodule
|
134
|
+
|
135
|
+
If you want to invoke a method other than the default one you'd do the
|
136
|
+
following:
|
137
|
+
|
138
|
+
git.rb submodule status
|
139
|
+
|
140
|
+
In other words, the syntax of a Shebang command is the following:
|
141
|
+
|
142
|
+
script [COMMAND] [METHOD] [ARGS] [OPTIONS]
|
143
|
+
|
144
|
+
## Configuration
|
145
|
+
|
146
|
+
Various options of Shebang can be configured by modifying the hash
|
147
|
+
``Shebang::Config``. For example, if you want to change the format of all the
|
148
|
+
headers in the help message you can do so as following:
|
149
|
+
|
150
|
+
Shebang::Config[:heading] = "\n== %s:\n"
|
151
|
+
|
152
|
+
You can also change the name of the default command:
|
153
|
+
|
154
|
+
Shebang::Config[:default_command] = :my_default_command
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path('../lib/shebang', __FILE__)
|
2
|
+
|
3
|
+
module Shebang
|
4
|
+
Gemspec = Gem::Specification::load(File.expand_path('../shebang.gemspec', __FILE__))
|
5
|
+
end
|
6
|
+
|
7
|
+
task_dir = File.expand_path('../task', __FILE__)
|
8
|
+
|
9
|
+
Dir.glob("#{task_dir}/*.rake").each do |f|
|
10
|
+
import(f)
|
11
|
+
end
|
data/example/basic.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.expand_path('../../lib/shebang', __FILE__)
|
2
|
+
|
3
|
+
class Greet < Shebang::Command
|
4
|
+
command :default
|
5
|
+
banner 'Runs an example command.'
|
6
|
+
usage '$ ruby example/basic.rb [OPTIONS]'
|
7
|
+
|
8
|
+
o :h, :help , 'Shows this help message' , :method => :help
|
9
|
+
o :v, :version, 'Shows the current version', :method => :version
|
10
|
+
o :f, :force , 'Forces the command to run'
|
11
|
+
o :n, :name , 'A person\'s name', :type => String,
|
12
|
+
:required => true, :default => 'Shebang'
|
13
|
+
|
14
|
+
# $ ruby example/basic.rb
|
15
|
+
# $ ruby example/basic.rb default
|
16
|
+
# $ ruby example/basic.rb default index
|
17
|
+
def index
|
18
|
+
puts "Your name is #{option(:n)}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# $ ruby example/basic.rb test
|
22
|
+
# $ ruby example/basic.rb default test
|
23
|
+
def test
|
24
|
+
puts 'This is a test method'
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def version
|
30
|
+
puts Shebang::Version
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Shebang.run
|
data/lib/.gitkeep
ADDED
File without changes
|
data/lib/shebang.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'optparse'
|
3
|
+
require File.expand_path('../shebang/version', __FILE__)
|
4
|
+
require File.expand_path('../shebang/command', __FILE__)
|
5
|
+
require File.expand_path('../shebang/option' , __FILE__)
|
6
|
+
|
7
|
+
##
|
8
|
+
# Shebang is a nice wrapper around OptionParser that makes it easier to write
|
9
|
+
# commandline executables.
|
10
|
+
#
|
11
|
+
# @author Yorick Peterse
|
12
|
+
# @since 0.1
|
13
|
+
#
|
14
|
+
module Shebang
|
15
|
+
#:nodoc:
|
16
|
+
class Error < StandardError; end
|
17
|
+
|
18
|
+
# Hash containing various configuration options.
|
19
|
+
Config = {
|
20
|
+
# The name of the default command to invoke when no command is specified.
|
21
|
+
:default_command => :default,
|
22
|
+
|
23
|
+
# The name of the default method to invoke.
|
24
|
+
:default_method => :index,
|
25
|
+
|
26
|
+
# The amount of spaces to insert before each option.
|
27
|
+
:indent => ' ',
|
28
|
+
|
29
|
+
# The format for each header for help topics, options, etc.
|
30
|
+
:heading => "\n%s:\n",
|
31
|
+
|
32
|
+
# When set to true Shebang will raise an exception for errors instead of
|
33
|
+
# just printing a message.
|
34
|
+
:raise => true
|
35
|
+
}
|
36
|
+
|
37
|
+
# Hash containing the names of all commands and their classes.
|
38
|
+
Commands = {}
|
39
|
+
|
40
|
+
class << self
|
41
|
+
##
|
42
|
+
# Runs a command based on the command line arguments. If no command is given
|
43
|
+
# this method will try to invoke the default command.
|
44
|
+
#
|
45
|
+
# @author Yorick Peterse
|
46
|
+
# @since 0.1
|
47
|
+
# @param [Array] argv Array containing the command line arguments to parse.
|
48
|
+
#
|
49
|
+
def run(argv = ARGV)
|
50
|
+
self.error("No commands have been registered") if Commands.empty?
|
51
|
+
|
52
|
+
command = Config[:default_command].to_sym
|
53
|
+
method = Config[:default_method].to_sym
|
54
|
+
|
55
|
+
if !argv.empty?
|
56
|
+
# Get the command name
|
57
|
+
if argv[0][0] != '-' and Commands.key?(argv[0].to_sym)
|
58
|
+
command = argv.delete_at(0).to_sym
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if Commands.key?(command)
|
63
|
+
klass = Commands[command].new
|
64
|
+
|
65
|
+
# Get the method to call.
|
66
|
+
if argv[0] and argv[0][0] != '-' and klass.respond_to?(argv[0].to_sym)
|
67
|
+
method = argv.delete_at(0).to_sym
|
68
|
+
end
|
69
|
+
|
70
|
+
# Parse the arguments and prepare all the options.
|
71
|
+
argv = klass.parse(argv)
|
72
|
+
|
73
|
+
# Call the method and pass the commandline arguments to it.
|
74
|
+
if klass.respond_to?(method)
|
75
|
+
if klass.class.instance_method(method).arity != 0
|
76
|
+
klass.send(method, argv)
|
77
|
+
else
|
78
|
+
klass.send(method)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
error("The command #{command} does not have a #{method}() method")
|
82
|
+
end
|
83
|
+
else
|
84
|
+
error("The command #{command} does not exist")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Raises an exception or prints a regular error message to STDERR based on
|
90
|
+
# the :raise configuration option.
|
91
|
+
#
|
92
|
+
# @author Yorick Peterse
|
93
|
+
# @since 0.1
|
94
|
+
# @param [String] message The message to display.
|
95
|
+
#
|
96
|
+
def error(message)
|
97
|
+
if Config[:raise] === true
|
98
|
+
raise(Error, message)
|
99
|
+
else
|
100
|
+
abort "\e[0;31mError:\e[0m #{message}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end # class << self
|
104
|
+
end # Shebang
|
File without changes
|
@@ -0,0 +1,274 @@
|
|
1
|
+
module Shebang
|
2
|
+
##
|
3
|
+
# Shebang::Command is where the party really starts. By extending this class
|
4
|
+
# other classes can become fully fledged commands with their own options,
|
5
|
+
# banners, callbacks, and so on. In it's most basic form a command looks like
|
6
|
+
# the following:
|
7
|
+
#
|
8
|
+
# class MyCommand < Shebang::Command
|
9
|
+
# command 'my-command'
|
10
|
+
#
|
11
|
+
# def index
|
12
|
+
#
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# The class method command() is used to register the class to the specified
|
17
|
+
# name, without this Shebang would be unable to call it.
|
18
|
+
#
|
19
|
+
# Defining options can be done by calling the class method option() or it's
|
20
|
+
# alias o():
|
21
|
+
#
|
22
|
+
# class MyCommand < Shebang::Command
|
23
|
+
# command 'my-command'
|
24
|
+
#
|
25
|
+
# o :h, :help, 'Shows this help message', :method => :help
|
26
|
+
#
|
27
|
+
# def index
|
28
|
+
#
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# If you're going to define a help option, and you most likely will, you don't
|
33
|
+
# have to manually add a method that shows the message as the Command class
|
34
|
+
# already comes with an instance method for this, simply called help().
|
35
|
+
#
|
36
|
+
# For more information on options see Shebang::Option#initialize().
|
37
|
+
#
|
38
|
+
# @author Yorick Peterse
|
39
|
+
# @since 0.1
|
40
|
+
#
|
41
|
+
class Command
|
42
|
+
# Several methods that become available as class methods once
|
43
|
+
# Shebang::Command is extended by another class.
|
44
|
+
module ClassMethods
|
45
|
+
##
|
46
|
+
# Binds a class to the specified command name.
|
47
|
+
#
|
48
|
+
# @author Yorick Peterse
|
49
|
+
# @since 0.1
|
50
|
+
# @param [#to_sym] name The name of the command.
|
51
|
+
# @param [Hash] options Hash containing various options for the command.
|
52
|
+
# @option options :parent The name of the parent command.
|
53
|
+
#
|
54
|
+
def command(name, options = {})
|
55
|
+
name = name.to_sym
|
56
|
+
|
57
|
+
if Shebang::Commands.key?(name)
|
58
|
+
Shebang.error("The command #{name} has already been registered")
|
59
|
+
end
|
60
|
+
|
61
|
+
Shebang::Commands[name] = self
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Sets the banner for the command, trailing or leading newlines will
|
66
|
+
# be removed.
|
67
|
+
#
|
68
|
+
# @author Yorick Peterse
|
69
|
+
# @since 0.1
|
70
|
+
# @param [String] text The content of the banner.
|
71
|
+
#
|
72
|
+
def banner(text)
|
73
|
+
@__banner = text.strip
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# A small shortcut for defining the syntax of a command. This method is
|
78
|
+
# just a shortcut for the following:
|
79
|
+
#
|
80
|
+
# help('Usage', 'foobar [OPTIONS]'
|
81
|
+
#
|
82
|
+
# @author Yorick Peterse
|
83
|
+
# @since 0.1
|
84
|
+
# @param [String] text The content of the usage block.
|
85
|
+
#
|
86
|
+
def usage(text)
|
87
|
+
help('Usage', text)
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Sets a general "help topic" with a custom title and content.
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# help('License', 'MIT License')
|
95
|
+
#
|
96
|
+
# @author Yorick Peterse
|
97
|
+
# @since 0.1
|
98
|
+
# @param [String] title The title of the topic.
|
99
|
+
# @param [String] text The content of the topic.
|
100
|
+
#
|
101
|
+
def help(title, text)
|
102
|
+
@__help_topics ||= {}
|
103
|
+
@__help_topics[title] = text.strip
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Creates a new option for a command.
|
108
|
+
#
|
109
|
+
# @example
|
110
|
+
# o :h, :help, 'Shows this help message', :method => :help
|
111
|
+
# o :l, :list, 'A list of numbers' , :type => Array
|
112
|
+
#
|
113
|
+
# @author Yorick Peterse
|
114
|
+
# @since 0.1
|
115
|
+
# @see Shebang::Option#initialize
|
116
|
+
#
|
117
|
+
def option(short, long, desc = nil, options = {})
|
118
|
+
@__options ||= []
|
119
|
+
option = Shebang::Option.new(short, long, desc, options)
|
120
|
+
|
121
|
+
@__options.push(option)
|
122
|
+
end
|
123
|
+
alias :o :option
|
124
|
+
end # ClassMethods
|
125
|
+
|
126
|
+
##
|
127
|
+
# Modifies the class that inherits this class so that the module
|
128
|
+
# Shebang::Comand::ClassMethods extends the class.
|
129
|
+
#
|
130
|
+
# @author Yorick Peterse
|
131
|
+
# @since 09-08-2011
|
132
|
+
# @param [Class] by The class that inherits from Shebang::Command.
|
133
|
+
#
|
134
|
+
def self.inherited(by)
|
135
|
+
by.extend(Shebang::Command::ClassMethods)
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Creates a new instance of the command and sets up OptionParser.
|
140
|
+
#
|
141
|
+
# @author Yorick Peterse
|
142
|
+
# @since 0.1
|
143
|
+
#
|
144
|
+
def initialize
|
145
|
+
@option_parser = OptionParser.new do |opt|
|
146
|
+
opt.banner = banner
|
147
|
+
opt.summary_indent = Shebang::Config[:indent]
|
148
|
+
|
149
|
+
# Process each help topic
|
150
|
+
help_topics.each do |title, text|
|
151
|
+
opt.separator "#{Shebang::Config[:heading]}#{
|
152
|
+
Shebang::Config[:indent]}#{text}" % title
|
153
|
+
end
|
154
|
+
|
155
|
+
opt.separator "#{Shebang::Config[:heading]}" % 'Options'
|
156
|
+
|
157
|
+
# Add all the options
|
158
|
+
options.each do |option|
|
159
|
+
opt.on(*option.option_parser) do |value|
|
160
|
+
option.value = value
|
161
|
+
|
162
|
+
# Run a method?
|
163
|
+
if !option.options[:method].nil? \
|
164
|
+
and respond_to?(option.options[:method])
|
165
|
+
# Pass the value to the method?
|
166
|
+
if self.class.instance_method(option.options[:method]).arity != 0
|
167
|
+
send(option.options[:method], value)
|
168
|
+
else
|
169
|
+
send(option.options[:method])
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Parses the command line arguments using OptionParser.
|
179
|
+
#
|
180
|
+
# @author Yorick Peterse
|
181
|
+
# @since 0.1
|
182
|
+
# @param [Array] argv Array containing the command line arguments to parse.
|
183
|
+
# @return [Array] argv Array containing all the command line arguments after
|
184
|
+
# it has been processed.
|
185
|
+
#
|
186
|
+
def parse(argv = [])
|
187
|
+
@option_parser.parse!(argv)
|
188
|
+
|
189
|
+
options.each do |option|
|
190
|
+
if option.required? and !option.has_value?
|
191
|
+
Shebang.error("The -#{option.short} option is required")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
return argv
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
# Returns the banner of the current class.
|
200
|
+
#
|
201
|
+
# @author Yorick Peterse
|
202
|
+
# @since 0.1
|
203
|
+
# @return [String]
|
204
|
+
#
|
205
|
+
def banner
|
206
|
+
self.class.instance_variable_get(:@__banner)
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# Returns all help topics for the current class.
|
211
|
+
#
|
212
|
+
# @author Yorick Peterse
|
213
|
+
# @since 0.1
|
214
|
+
# @return [Hash]
|
215
|
+
#
|
216
|
+
def help_topics
|
217
|
+
self.class.instance_variable_get(:@__help_topics) || {}
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Returns an array of all the options for the current class.
|
222
|
+
#
|
223
|
+
# @author Yorick Peterse
|
224
|
+
# @since 0.1
|
225
|
+
# @return [Array]
|
226
|
+
#
|
227
|
+
def options
|
228
|
+
self.class.instance_variable_get(:@__options) || []
|
229
|
+
end
|
230
|
+
|
231
|
+
##
|
232
|
+
# Method that is called whenever a command has to be executed.
|
233
|
+
#
|
234
|
+
# @author Yorick Peterse
|
235
|
+
# @since 0.1
|
236
|
+
#
|
237
|
+
def index
|
238
|
+
raise(NotImplementedError, "You need to define your own index() method")
|
239
|
+
end
|
240
|
+
|
241
|
+
##
|
242
|
+
# Returns the value of a given option. The option can be specified using
|
243
|
+
# either the short or long name.
|
244
|
+
#
|
245
|
+
# @example
|
246
|
+
# puts "Hello #{option(:name)}
|
247
|
+
#
|
248
|
+
# @author Yorick Peterse
|
249
|
+
# @since 0.1
|
250
|
+
# @param [#to_sym] opt The name of the option.
|
251
|
+
# @return [Mixed]
|
252
|
+
#
|
253
|
+
def option(opt)
|
254
|
+
opt = opt.to_sym
|
255
|
+
|
256
|
+
options.each do |op|
|
257
|
+
if op.short === opt or op.long === opt
|
258
|
+
return op.value
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
##
|
264
|
+
# Shows the help message for the current class.
|
265
|
+
#
|
266
|
+
# @author Yorick Peterse
|
267
|
+
# @since 0.1
|
268
|
+
#
|
269
|
+
def help
|
270
|
+
puts @option_parser
|
271
|
+
exit
|
272
|
+
end
|
273
|
+
end # Command
|
274
|
+
end # Shebang
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Shebang
|
2
|
+
##
|
3
|
+
# Class that represents a single option that's passed to OptionParser.
|
4
|
+
#
|
5
|
+
# @author Yorick Peterse
|
6
|
+
# @since 0.1
|
7
|
+
#
|
8
|
+
class Option
|
9
|
+
attr_reader :short, :long, :description, :options
|
10
|
+
attr_accessor :value
|
11
|
+
|
12
|
+
##
|
13
|
+
# Creates a new instance of the Option class.
|
14
|
+
#
|
15
|
+
# @author Yorick Peterse
|
16
|
+
# @since 0.1
|
17
|
+
# @param [#to_sym] short The short option name such as :h.
|
18
|
+
# @param [#to_sym] long The long option name such as :help.
|
19
|
+
# @param [String] desc The description of the option.
|
20
|
+
# @param [Hash] options Hash containing various configuration options for
|
21
|
+
# the OptionParser option.
|
22
|
+
# @option options :type The type of value for the option, set to TrueClass
|
23
|
+
# by default.
|
24
|
+
# @option options :key The key to use to indicate a value whenever the type
|
25
|
+
# of an option is something else than TrueClass or FalseClass. This option
|
26
|
+
# is set to "VALUE" by default.
|
27
|
+
# @option options :method A symbol that refers to a method that should be
|
28
|
+
# called whenever the option is specified.
|
29
|
+
# @option options :required Indicates that the option has to be specified.
|
30
|
+
# @option options :default The default value of the option.
|
31
|
+
#
|
32
|
+
def initialize(short, long, desc = nil, options = {})
|
33
|
+
@short, @long = short.to_sym, long.to_sym
|
34
|
+
@description = desc
|
35
|
+
@options = {
|
36
|
+
:type => TrueClass,
|
37
|
+
:key => 'VALUE',
|
38
|
+
:method => nil,
|
39
|
+
:required => false,
|
40
|
+
:default => nil
|
41
|
+
}.merge(options)
|
42
|
+
|
43
|
+
@value = @options[:default]
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Builds an array containing all the required parameters for
|
48
|
+
# OptionParser#on().
|
49
|
+
#
|
50
|
+
# @author Yorick Peterse
|
51
|
+
# @since 0.1
|
52
|
+
# @return [Array]
|
53
|
+
#
|
54
|
+
def option_parser
|
55
|
+
params = ["-#{@short}", "--#{@long}", nil, @options[:type]]
|
56
|
+
|
57
|
+
if !@description.nil? and !@description.empty?
|
58
|
+
params[2] = @description
|
59
|
+
end
|
60
|
+
|
61
|
+
# Set the correct format for the long/short option based on the type.
|
62
|
+
if ![TrueClass, FalseClass].include?(@options[:type])
|
63
|
+
params[1] += " #{@options[:key]}"
|
64
|
+
end
|
65
|
+
|
66
|
+
return params
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Checks if the value of an option is not nil and not empty.
|
71
|
+
#
|
72
|
+
# @author Yorick Peterse
|
73
|
+
# @since 0.1
|
74
|
+
# @return [TrueClass|FalseClass]
|
75
|
+
#
|
76
|
+
def has_value?
|
77
|
+
if !@value.nil? and !@value.empty?
|
78
|
+
return true
|
79
|
+
else
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Indicates whether or not the option requires a value.
|
86
|
+
#
|
87
|
+
# @author Yorick Peterse
|
88
|
+
# @since 0.1
|
89
|
+
# @return [TrueClass|FalseClass]
|
90
|
+
#
|
91
|
+
def required?
|
92
|
+
return @options[:required]
|
93
|
+
end
|
94
|
+
end # Option
|
95
|
+
end # Shebang
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#:nodoc:
|
2
|
+
module Bacon
|
3
|
+
#:nodoc:
|
4
|
+
module ColorOutput
|
5
|
+
#:nodoc:
|
6
|
+
def handle_specification(name)
|
7
|
+
puts spaces + name
|
8
|
+
yield
|
9
|
+
puts if Counter[:context_depth] == 1
|
10
|
+
end
|
11
|
+
|
12
|
+
#:nodoc:
|
13
|
+
def handle_requirement(description)
|
14
|
+
error = yield
|
15
|
+
|
16
|
+
if !error.empty?
|
17
|
+
puts "#{spaces} \e[31m- #{description} [FAILED]\e[0m"
|
18
|
+
else
|
19
|
+
puts "#{spaces} \e[32m- #{description}\e[0m"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
#:nodoc:
|
24
|
+
def handle_summary
|
25
|
+
print ErrorLog if Backtraces
|
26
|
+
puts "%d specifications (%d requirements), %d failures, %d errors" %
|
27
|
+
Counter.values_at(:specifications, :requirements, :failed, :errors)
|
28
|
+
end
|
29
|
+
|
30
|
+
#:nodoc:
|
31
|
+
def spaces
|
32
|
+
if Counter[:context_depth] === 0
|
33
|
+
Counter[:context_depth] = 1
|
34
|
+
end
|
35
|
+
|
36
|
+
return ' ' * (Counter[:context_depth] - 1)
|
37
|
+
end
|
38
|
+
end # ColorOutput
|
39
|
+
end # Bacon
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'bacon'
|
2
|
+
require 'stringio'
|
3
|
+
require File.expand_path('../bacon/color_output', __FILE__)
|
4
|
+
|
5
|
+
Bacon.extend(Bacon::ColorOutput)
|
6
|
+
Bacon.summary_on_exit
|
7
|
+
|
8
|
+
##
|
9
|
+
# Runs the block in a new thread and redirects $stdout and $stderr. The output
|
10
|
+
# normally stored in these variables is stored in an instance of StringIO which
|
11
|
+
# is returned as a hash.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# out = catch_output do
|
15
|
+
# puts 'hello'
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# puts out # => {:stdout => "hello\n", :stderr => ""}
|
19
|
+
#
|
20
|
+
# @author Yorick Peterse
|
21
|
+
# @return [Hash]
|
22
|
+
#
|
23
|
+
def catch_output
|
24
|
+
data = {
|
25
|
+
:stdout => nil,
|
26
|
+
:stderr => nil
|
27
|
+
}
|
28
|
+
|
29
|
+
Thread.new do
|
30
|
+
$stdout, $stderr = StringIO.new, StringIO.new
|
31
|
+
|
32
|
+
yield
|
33
|
+
|
34
|
+
$stdout.rewind
|
35
|
+
$stderr.rewind
|
36
|
+
|
37
|
+
data[:stdout], data[:stderr] = $stdout.read, $stderr.read
|
38
|
+
|
39
|
+
$stdout, $stderr = STDOUT, STDERR
|
40
|
+
end.join
|
41
|
+
|
42
|
+
return data
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Allows developers to create stubbed objects similar to Mocha's stub() method.
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# obj = stub(:language => 'Ruby')
|
50
|
+
# puts obj.language # => "Ruby"
|
51
|
+
#
|
52
|
+
# @author Yorick Peterse
|
53
|
+
# @param [Hash] attributes A hash containing all the attributes to set and
|
54
|
+
# their values.
|
55
|
+
# @return [Class]
|
56
|
+
#
|
57
|
+
def stub(attributes)
|
58
|
+
obj = Struct.new(*attributes.keys).new
|
59
|
+
|
60
|
+
attributes.each do |k, v|
|
61
|
+
obj.send("#{k}=", v)
|
62
|
+
end
|
63
|
+
|
64
|
+
return obj
|
65
|
+
end
|
data/pkg/.gitkeep
ADDED
File without changes
|
data/shebang.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path('../lib/shebang/version', __FILE__)
|
2
|
+
|
3
|
+
path = File.expand_path('../', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'shebang'
|
7
|
+
s.version = Shebang::Version
|
8
|
+
s.date = '09-08-2011'
|
9
|
+
s.authors = ['Yorick Peterse']
|
10
|
+
s.email = 'yorickpeterse@gmail.com'
|
11
|
+
s.summary = 'Shebang is a nice wrapper around OptionParser that makes it
|
12
|
+
easier to write commandline executables.'
|
13
|
+
s.homepage = 'https://github.com/yorickpeterse/shebang'
|
14
|
+
s.description = s.summary
|
15
|
+
s.files = `cd #{path}; git ls-files`.split("\n").sort
|
16
|
+
s.has_rdoc = 'yard'
|
17
|
+
|
18
|
+
s.add_development_dependency('rake' , ['~> 0.9.2'])
|
19
|
+
s.add_development_dependency('yard' , ['~> 0.7.2'])
|
20
|
+
s.add_development_dependency('bacon', ['~> 1.1.0'])
|
21
|
+
end
|
data/spec/.gitkeep
ADDED
File without changes
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class SpecCommand < Shebang::Command
|
2
|
+
command :default
|
3
|
+
banner 'The default command.'
|
4
|
+
usage 'shebang.rb [COMMAND] [OPTIONS]'
|
5
|
+
|
6
|
+
o :v, :version, 'Shows the current version', :method => :version
|
7
|
+
|
8
|
+
def index
|
9
|
+
puts 'index method'
|
10
|
+
end
|
11
|
+
|
12
|
+
def test
|
13
|
+
puts 'test method'
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def version
|
19
|
+
puts '0.1'
|
20
|
+
end
|
21
|
+
end # SpecCommand
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path('../../helper', __FILE__)
|
2
|
+
require File.expand_path('../../fixtures/command', __FILE__)
|
3
|
+
|
4
|
+
describe('Shebang::Command') do
|
5
|
+
it('The name should be registered') do
|
6
|
+
Shebang::Commands[:default].should == SpecCommand
|
7
|
+
end
|
8
|
+
|
9
|
+
it('The banner should be set') do
|
10
|
+
Shebang::Commands[:default].instance_variable_get(:@__banner).should \
|
11
|
+
=== 'The default command.'
|
12
|
+
|
13
|
+
Shebang::Commands[:default].new.banner.should === 'The default command.'
|
14
|
+
end
|
15
|
+
|
16
|
+
it('A help topic should be set') do
|
17
|
+
Shebang::Commands[:default].instance_variable_get(
|
18
|
+
:@__help_topics
|
19
|
+
)['Usage'].should === 'shebang.rb [COMMAND] [OPTIONS]'
|
20
|
+
|
21
|
+
Shebang::Commands[:default].new.help_topics['Usage'].should \
|
22
|
+
=== 'shebang.rb [COMMAND] [OPTIONS]'
|
23
|
+
end
|
24
|
+
|
25
|
+
it('An option should be set') do
|
26
|
+
option = Shebang::Commands[:default].instance_variable_get(:@__options)[0]
|
27
|
+
|
28
|
+
option.short.should === :v
|
29
|
+
option.long.should === :version
|
30
|
+
option.description.should === 'Shows the current version'
|
31
|
+
option.options[:method].should === :version
|
32
|
+
option.value = '0.1'
|
33
|
+
|
34
|
+
Shebang::Commands[:default].new.options[0].short.should === option.short
|
35
|
+
|
36
|
+
Shebang::Commands[:default].new.option(:v).should === option.value
|
37
|
+
Shebang::Commands[:default].new.option(:version).should === option.value
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path('../../helper', __FILE__)
|
2
|
+
|
3
|
+
describe('Shebang::Option') do
|
4
|
+
it('Create a new option') do
|
5
|
+
option = Shebang::Option.new(:h, :help, 'help message', :method => :test)
|
6
|
+
|
7
|
+
option.short.should === :h
|
8
|
+
option.long.should === :help
|
9
|
+
option.description.should === 'help message'
|
10
|
+
|
11
|
+
option.options[:method].should === :test
|
12
|
+
option.options[:type].should == TrueClass
|
13
|
+
|
14
|
+
option.required?.should === false
|
15
|
+
option.has_value?.should === false
|
16
|
+
end
|
17
|
+
|
18
|
+
it('Convert to OptionParser arguments') do
|
19
|
+
option = Shebang::Option.new(:h, :help, 'help message', :method => :test)
|
20
|
+
|
21
|
+
option.option_parser.should === ['-h', '--help', 'help message', TrueClass]
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.expand_path('../../helper', __FILE__)
|
2
|
+
require File.expand_path('../../fixtures/command', __FILE__)
|
3
|
+
|
4
|
+
module Kernel
|
5
|
+
def abort(*args)
|
6
|
+
$stderr.puts(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def exit(*args); end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe('Shebang') do
|
13
|
+
it('Raise an error message') do
|
14
|
+
should.raise?(Shebang::Error) do
|
15
|
+
Shebang.error('test')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it('Display an error message') do
|
20
|
+
Shebang::Config[:raise] = false
|
21
|
+
|
22
|
+
output = catch_output do
|
23
|
+
Shebang.error('test')
|
24
|
+
end
|
25
|
+
|
26
|
+
output[:stderr].include?('test').should === true
|
27
|
+
end
|
28
|
+
|
29
|
+
it('Invoke the default command') do
|
30
|
+
[[], ['default'], ['default', 'index']].each do |argv|
|
31
|
+
output = catch_output do
|
32
|
+
Shebang.run(argv)
|
33
|
+
end
|
34
|
+
|
35
|
+
output[:stdout].strip.should === 'index method'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it('Invoke the default command with an alternative method') do
|
40
|
+
[['test'], ['default', 'test']].each do |argv|
|
41
|
+
output = catch_output do
|
42
|
+
Shebang.run(argv)
|
43
|
+
end
|
44
|
+
|
45
|
+
output[:stdout].strip.should === 'test method'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it('Show a help message') do
|
50
|
+
output = catch_output do
|
51
|
+
Shebang.run(['--help'])
|
52
|
+
end
|
53
|
+
|
54
|
+
output[:stdout].include?('The default command').should === true
|
55
|
+
output[:stdout].include?('shebang.rb [COMMAND] [OPTIONS]').should === true
|
56
|
+
output[:stdout].include?('Options').should === true
|
57
|
+
end
|
58
|
+
|
59
|
+
it('Shows the current version') do
|
60
|
+
output = catch_output do
|
61
|
+
Shebang.run(['--version'])
|
62
|
+
end
|
63
|
+
|
64
|
+
output[:stdout].include?('0.1').should === true
|
65
|
+
end
|
66
|
+
end # describe
|
data/task/build.rake
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Task group used for building various elements such as the Gem and the
|
2
|
+
# documentation.
|
3
|
+
namespace :build do
|
4
|
+
desc 'Builds the documentation using YARD'
|
5
|
+
task :doc do
|
6
|
+
gem_path = File.expand_path('../../', __FILE__)
|
7
|
+
command = "yard doc #{gem_path}/lib -m markdown -M rdiscount -o #{gem_path}/doc "
|
8
|
+
command += "-r #{gem_path}/README.md --private --protected"
|
9
|
+
|
10
|
+
sh(command)
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Builds a new Gem'
|
14
|
+
task :gem do
|
15
|
+
gem_path = File.expand_path('../../', __FILE__)
|
16
|
+
gemspec_path = File.join(
|
17
|
+
gem_path,
|
18
|
+
"#{Shebang::Gemspec.name}-#{Shebang::Gemspec.version.version}.gem"
|
19
|
+
)
|
20
|
+
|
21
|
+
pkg_path = File.join(
|
22
|
+
gem_path,
|
23
|
+
'pkg',
|
24
|
+
"#{Shebang::Gemspec.name}-#{Shebang::Gemspec.version.version}.gem"
|
25
|
+
)
|
26
|
+
|
27
|
+
# Build and install the gem
|
28
|
+
sh('gem', 'build' , File.join(gem_path, 'shebang.gemspec'))
|
29
|
+
sh('mv' , gemspec_path, pkg_path)
|
30
|
+
sh('gem', 'install' , pkg_path)
|
31
|
+
end
|
32
|
+
end # namespace :build
|
33
|
+
|
data/task/test.rake
ADDED
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shebang
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: "0.1"
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Yorick Peterse
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-09 00:00:00 +02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rake
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.9.2
|
25
|
+
type: :development
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.7.2
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: bacon
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.1.0
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
description: Shebang is a nice wrapper around OptionParser that makes it easier to write commandline executables.
|
50
|
+
email: yorickpeterse@gmail.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
files:
|
58
|
+
- .gems
|
59
|
+
- .gitignore
|
60
|
+
- .rvmrc
|
61
|
+
- .travis.yml
|
62
|
+
- LICENSE
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- example/basic.rb
|
66
|
+
- lib/.gitkeep
|
67
|
+
- lib/shebang.rb
|
68
|
+
- lib/shebang/.gitkeep
|
69
|
+
- lib/shebang/command.rb
|
70
|
+
- lib/shebang/option.rb
|
71
|
+
- lib/shebang/spec/bacon/color_output.rb
|
72
|
+
- lib/shebang/spec/helper.rb
|
73
|
+
- lib/shebang/version.rb
|
74
|
+
- pkg/.gitkeep
|
75
|
+
- shebang.gemspec
|
76
|
+
- spec/.gitkeep
|
77
|
+
- spec/fixtures/command.rb
|
78
|
+
- spec/helper.rb
|
79
|
+
- spec/shebang/command.rb
|
80
|
+
- spec/shebang/option.rb
|
81
|
+
- spec/shebang/shebang.rb
|
82
|
+
- task/build.rake
|
83
|
+
- task/test.rake
|
84
|
+
has_rdoc: yard
|
85
|
+
homepage: https://github.com/yorickpeterse/shebang
|
86
|
+
licenses: []
|
87
|
+
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "0"
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: "0"
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.6.2
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: Shebang is a nice wrapper around OptionParser that makes it easier to write commandline executables.
|
112
|
+
test_files: []
|
113
|
+
|