Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a8de418
Showing
9 changed files
with
561 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Copyright (c) 2008 Yehuda Katz | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
hermes | ||
====== | ||
|
||
Map options to a class. Simply create a class with the appropriate annotations, and have options automatically map | ||
to functions and parameters. | ||
|
||
Examples: | ||
|
||
class MyApp | ||
extend Hermes # [1] | ||
map "-L" => :list # [2] | ||
desc "install APP_NAME", "install one of the available apps" # [3] | ||
method_options :force => :boolean # [4] | ||
def install(name, opts) | ||
... code ... | ||
if opts[:force] | ||
# do something | ||
end | ||
end | ||
desc "list [SEARCH]", "list all of the available apps, limited by SEARCH" | ||
def list(search = "") | ||
# list everything | ||
end | ||
end | ||
|
||
MyApp.start | ||
|
||
Hermes automatically maps commands as follows: | ||
|
||
app install name --force | ||
|
||
That gets converted to: | ||
|
||
MyApp.new.install("name", :force => true) | ||
|
||
[1] Use `extend Hermes` to turn a class into an option mapper | ||
|
||
[2] Map additional non-valid identifiers to specific methods. In this case, | ||
convert -L to :list | ||
|
||
[3] Describe the method immediately below. The first parameter is the usage information, | ||
and the second parameter is the description. | ||
|
||
[4] Provide any additional options. These will be marshaled from -- and - params. | ||
In this case, a --force and a -f option is added. | ||
|
||
Types for `method_options` | ||
-------------------------- | ||
|
||
<dl> | ||
<dt>:boolean</dt> | ||
<dd>true if the option is passed</dd> | ||
<dt>:required</dt> | ||
<dd>A key/value option that MUST be provided</dd> | ||
<dt>:optional</dt> | ||
<dd>A key/value option that MAY be provided</dd> | ||
</dl> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
require 'rubygems' | ||
require 'rake/gempackagetask' | ||
|
||
GEM = "hermes" | ||
VERSION = "0.9.0" | ||
AUTHOR = "Yehuda Katz" | ||
EMAIL = "wycats@gmail.com" | ||
HOMEPAGE = "http://yehudakatz.com" | ||
SUMMARY = "A gem that maps options to a class" | ||
|
||
spec = Gem::Specification.new do |s| | ||
s.name = GEM | ||
s.version = VERSION | ||
s.platform = Gem::Platform::RUBY | ||
s.has_rdoc = true | ||
s.extra_rdoc_files = ["README.markdown", "LICENSE"] | ||
s.summary = SUMMARY | ||
s.description = s.summary | ||
s.author = AUTHOR | ||
s.email = EMAIL | ||
s.homepage = HOMEPAGE | ||
|
||
# Uncomment this to add a dependency | ||
# s.add_dependency "foo" | ||
|
||
s.require_path = 'lib' | ||
s.autorequire = GEM | ||
s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,specs}/**/*") | ||
end | ||
|
||
Rake::GemPackageTask.new(spec) do |pkg| | ||
pkg.gem_spec = spec | ||
end | ||
|
||
task :install => [:package] do | ||
sh %{sudo gem install pkg/#{GEM}-#{VERSION}} | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
# The last time the Getopt gem was modified was August 2007, so it's safe to vendor (it does everything we need) | ||
|
||
module Getopt | ||
|
||
REQUIRED = 0 | ||
BOOLEAN = 1 | ||
OPTIONAL = 2 | ||
INCREMENT = 3 | ||
NEGATABLE = 4 | ||
NUMERIC = 5 | ||
|
||
class Long | ||
class Error < StandardError; end | ||
|
||
VERSION = '1.3.6' | ||
|
||
# Takes an array of switches. Each array consists of up to three | ||
# elements that indicate the name and type of switch. Returns a hash | ||
# containing each switch name, minus the '-', as a key. The value | ||
# for each key depends on the type of switch and/or the value provided | ||
# by the user. | ||
# | ||
# The long switch _must_ be provided. The short switch defaults to the | ||
# first letter of the short switch. The default type is BOOLEAN. | ||
# | ||
# Example: | ||
# | ||
# opts = Getopt::Long.getopts( | ||
# ["--debug"], | ||
# ["--verbose", "-v"], | ||
# ["--level", "-l", NUMERIC] | ||
# ) | ||
# | ||
# See the README file for more information. | ||
# | ||
def self.getopts(*switches) | ||
if switches.empty? | ||
raise ArgumentError, "no switches provided" | ||
end | ||
|
||
hash = {} # Hash returned to user | ||
valid = [] # Tracks valid switches | ||
types = {} # Tracks argument types | ||
syns = {} # Tracks long and short arguments, or multiple shorts | ||
|
||
# If a string is passed, split it and convert it to an array of arrays | ||
if switches.first.kind_of?(String) | ||
switches = switches.join.split | ||
switches.map!{ |switch| switch = [switch] } | ||
end | ||
|
||
# Set our list of valid switches, and proper types for each switch | ||
switches.each{ |switch| | ||
valid.push(switch[0]) # Set valid long switches | ||
|
||
# Set type for long switch, default to BOOLEAN. | ||
if switch[1].kind_of?(Fixnum) | ||
switch[2] = switch[1] | ||
types[switch[0]] = switch[2] | ||
switch[1] = switch[0][1..2] | ||
else | ||
switch[2] ||= BOOLEAN | ||
types[switch[0]] = switch[2] | ||
switch[1] ||= switch[0][1..2] | ||
end | ||
|
||
# Create synonym hash. Default to first char of long switch for | ||
# short switch, e.g. "--verbose" creates a "-v" synonym. The same | ||
# synonym can only be used once - first one wins. | ||
syns[switch[0]] = switch[1] unless syns[switch[1]] | ||
syns[switch[1]] = switch[0] unless syns[switch[1]] | ||
|
||
switch[1].each{ |char| | ||
types[char] = switch[2] # Set type for short switch | ||
valid.push(char) # Set valid short switches | ||
} | ||
|
||
if ARGV.empty? && switch[2] == REQUIRED | ||
raise Error, "no value provided for required argument '#{switch[0]}'" | ||
end | ||
} | ||
|
||
re_long = /^(--\w+[-\w+]*)?$/ | ||
re_short = /^(-\w)$/ | ||
re_long_eq = /^(--\w+[-\w+]*)?=(.*?)$|(-\w?)=(.*?)$/ | ||
re_short_sq = /^(-\w)(\S+?)$/ | ||
|
||
ARGV.each_with_index{ |opt, index| | ||
|
||
# Allow either -x -v or -xv style for single char args | ||
if re_short_sq.match(opt) | ||
chars = opt.split("")[1..-1].map{ |s| s = "-#{s}" } | ||
|
||
chars.each_with_index{ |char, i| | ||
unless valid.include?(char) | ||
raise Error, "invalid switch '#{char}'" | ||
end | ||
|
||
# Grab the next arg if the switch takes a required arg | ||
if types[char] == REQUIRED | ||
# Deal with a argument squished up against switch | ||
if chars[i+1] | ||
arg = chars[i+1..-1].join.tr("-","") | ||
ARGV.push(char, arg) | ||
break | ||
else | ||
arg = ARGV.delete_at(index+1) | ||
if arg.nil? || valid.include?(arg) # Minor cheat here | ||
err = "no value provided for required argument '#{char}'" | ||
raise Error, err | ||
end | ||
ARGV.push(char, arg) | ||
end | ||
elsif types[char] == OPTIONAL | ||
if chars[i+1] && !valid.include?(chars[i+1]) | ||
arg = chars[i+1..-1].join.tr("-","") | ||
ARGV.push(char, arg) | ||
break | ||
elsif | ||
if ARGV[index+1] && !valid.include?(ARGV[index+1]) | ||
arg = ARGV.delete_at(index+1) | ||
ARGV.push(char, arg) | ||
end | ||
else | ||
ARGV.push(char) | ||
end | ||
else | ||
ARGV.push(char) | ||
end | ||
} | ||
next | ||
end | ||
|
||
if match = re_long.match(opt) || match = re_short.match(opt) | ||
switch = match.captures.first | ||
end | ||
|
||
if match = re_long_eq.match(opt) | ||
switch, value = match.captures.compact | ||
ARGV.push(switch, value) | ||
next | ||
end | ||
|
||
# Make sure that all the switches are valid. If 'switch' isn't | ||
# defined at this point, it means an option was passed without | ||
# a preceding switch, e.g. --option foo bar. | ||
unless valid.include?(switch) | ||
switch ||= opt | ||
raise Error, "invalid switch '#{switch}'" | ||
end | ||
|
||
# Required arguments | ||
if types[switch] == REQUIRED | ||
nextval = ARGV[index+1] | ||
|
||
# Make sure there's a value for mandatory arguments | ||
if nextval.nil? | ||
err = "no value provided for required argument '#{switch}'" | ||
raise Error, err | ||
end | ||
|
||
# If there is a value, make sure it's not another switch | ||
if valid.include?(nextval) | ||
err = "cannot pass switch '#{nextval}' as an argument" | ||
raise Error, err | ||
end | ||
|
||
# If the same option appears more than once, put the values | ||
# in array. | ||
if hash[switch] | ||
hash[switch] = [hash[switch], nextval].flatten | ||
else | ||
hash[switch] = nextval | ||
end | ||
ARGV.delete_at(index+1) | ||
end | ||
|
||
# For boolean arguments set the switch's value to true. | ||
if types[switch] == BOOLEAN | ||
if hash.has_key?(switch) | ||
raise Error, "boolean switch already set" | ||
end | ||
hash[switch] = true | ||
end | ||
|
||
# For increment arguments, set the switch's value to 0, or | ||
# increment it by one if it already exists. | ||
if types[switch] == INCREMENT | ||
if hash.has_key?(switch) | ||
hash[switch] += 1 | ||
else | ||
hash[switch] = 1 | ||
end | ||
end | ||
|
||
# For optional argument, there may be an argument. If so, it | ||
# cannot be another switch. If not, it is set to true. | ||
if types[switch] == OPTIONAL | ||
nextval = ARGV[index+1] | ||
if valid.include?(nextval) | ||
hash[switch] = true | ||
else | ||
hash[switch] = nextval | ||
ARGV.delete_at(index+1) | ||
end | ||
end | ||
} | ||
|
||
# Set synonymous switches to the same value, e.g. if -t is a synonym | ||
# for --test, and the user passes "--test", then set "-t" to the same | ||
# value that "--test" was set to. | ||
# | ||
# This allows users to refer to the long or short switch and get | ||
# the same value | ||
hash.each{ |switch, val| | ||
if syns.keys.include?(switch) | ||
syns[switch].each{ |key| | ||
hash[key] = val | ||
} | ||
end | ||
} | ||
|
||
# Get rid of leading "--" and "-" to make it easier to reference | ||
hash.each{ |key, value| | ||
if key[0,2] == '--' | ||
nkey = key.sub('--', '') | ||
else | ||
nkey = key.sub('-', '') | ||
end | ||
hash.delete(key) | ||
hash[nkey] = value | ||
} | ||
|
||
hash | ||
end | ||
|
||
end | ||
end |
Oops, something went wrong.