ivanvc-choice 0.1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +22 -0
- data/LICENSE +18 -0
- data/README +447 -0
- data/examples/ftpd.rb +87 -0
- data/examples/gamble.rb +42 -0
- data/lib/choice.rb +166 -0
- data/lib/choice/lazyhash.rb +67 -0
- data/lib/choice/option.rb +90 -0
- data/lib/choice/parser.rb +233 -0
- data/lib/choice/version.rb +9 -0
- data/lib/choice/writer.rb +187 -0
- data/test/test_choice.rb +234 -0
- data/test/test_lazyhash.rb +76 -0
- data/test/test_option.rb +145 -0
- data/test/test_parser.rb +388 -0
- data/test/test_writer.rb +103 -0
- metadata +69 -0
data/examples/ftpd.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
$:.unshift "../lib"
|
2
|
+
$:.unshift "lib"
|
3
|
+
require 'choice'
|
4
|
+
|
5
|
+
port = 21
|
6
|
+
PROGRAM_VERSION = 4
|
7
|
+
|
8
|
+
Choice.options do
|
9
|
+
#banner "Usage: ftpd.rb [options]"
|
10
|
+
|
11
|
+
header ""
|
12
|
+
header "Specific options:"
|
13
|
+
|
14
|
+
option :host do
|
15
|
+
short '-h'
|
16
|
+
long '--host=HOST'
|
17
|
+
desc "The hostname or ip of the host to bind to"
|
18
|
+
desc "(default 127.0.0.1)"
|
19
|
+
default '127.0.0.1'
|
20
|
+
end
|
21
|
+
|
22
|
+
option :port do
|
23
|
+
short '-p'
|
24
|
+
long '--port=PORT'
|
25
|
+
desc "The port to listen on (default 21)"
|
26
|
+
cast Integer
|
27
|
+
default port
|
28
|
+
end
|
29
|
+
|
30
|
+
option :clients do
|
31
|
+
short '-c'
|
32
|
+
long '--clients=NUM'
|
33
|
+
cast Integer
|
34
|
+
desc "The number of connections to allow at once (default 5)"
|
35
|
+
default 5
|
36
|
+
end
|
37
|
+
|
38
|
+
option :protocol do
|
39
|
+
short '-l'
|
40
|
+
long '--protocol=PROTOCOL'
|
41
|
+
desc "The protocol to use (default ftp)"
|
42
|
+
valid %w[ftp sftp]
|
43
|
+
default 'ftp'
|
44
|
+
end
|
45
|
+
|
46
|
+
option :yaml_cfg do
|
47
|
+
long '--config=FILE'
|
48
|
+
desc 'Load configuration from YAML file'
|
49
|
+
end
|
50
|
+
|
51
|
+
option :sample do
|
52
|
+
long '--sample'
|
53
|
+
desc "See a sample YAML config file"
|
54
|
+
action do
|
55
|
+
puts "See!"
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
option :debug do
|
61
|
+
short '-d'
|
62
|
+
long '--debug[=LEVEL]'
|
63
|
+
desc 'Turn on debugging mode'
|
64
|
+
end
|
65
|
+
|
66
|
+
separator ''
|
67
|
+
separator 'Common options: '
|
68
|
+
|
69
|
+
option :help do
|
70
|
+
long '--help'
|
71
|
+
desc 'Show this message'
|
72
|
+
end
|
73
|
+
|
74
|
+
option :version do
|
75
|
+
short '-v'
|
76
|
+
long '--version'
|
77
|
+
desc 'Show version'
|
78
|
+
action do
|
79
|
+
puts "ftpd.rb FTP server v#{PROGRAM_VERSION}"
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
print "Choices: "
|
87
|
+
puts Choice.choices.inspect
|
data/examples/gamble.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
$:.unshift "../lib"
|
2
|
+
$:.unshift "lib"
|
3
|
+
require 'choice'
|
4
|
+
|
5
|
+
suits = %w[clubs diamonds spades hearts]
|
6
|
+
stringed_numerics = (1..13).to_a.map { |a| a.to_s }
|
7
|
+
valid_cards = stringed_numerics + %w[jack queen king ace]
|
8
|
+
cards = {}
|
9
|
+
stringed_numerics.each { |n| cards[n] = n }
|
10
|
+
cards.merge!('1' => 'ace', '11' => 'jack', '12' => 'queen', '13' => 'king')
|
11
|
+
|
12
|
+
Choice.options do
|
13
|
+
header "Gambling is fun again! Pick a card and a suit (or two), then see if you win!"
|
14
|
+
header ""
|
15
|
+
header "Options:"
|
16
|
+
|
17
|
+
option :suit, :required => true do
|
18
|
+
short '-s'
|
19
|
+
long '--suit *SUITS'
|
20
|
+
desc "The suit you wish to choose. Required. You can pass in more than one, even."
|
21
|
+
desc " Valid suits: #{suits * ' '}"
|
22
|
+
valid suits
|
23
|
+
end
|
24
|
+
|
25
|
+
separator ''
|
26
|
+
|
27
|
+
option :card, :required => true do
|
28
|
+
short '-c'
|
29
|
+
long '--card CARD'
|
30
|
+
desc "The card you wish to gamble on. Required. Only one, please."
|
31
|
+
desc " Valid cards: 1 - 13, jack, queen, king, ace"
|
32
|
+
valid valid_cards
|
33
|
+
cast String
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
suit = suits[rand(suits.size)]
|
38
|
+
card = cards[(rand(13)+1).to_s]
|
39
|
+
|
40
|
+
puts "I drew the #{card} of #{suit}."
|
41
|
+
puts "You picked the #{Choice.choices.card} of #{Choice.choices.suit * ' or '}."
|
42
|
+
puts "You " << (Choice.choices.suit.include?(suit) && card == cards[Choice.choices.card] ? 'win!' : 'lose :(')
|
data/lib/choice.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require 'choice/option'
|
3
|
+
require 'choice/parser'
|
4
|
+
require 'choice/writer'
|
5
|
+
require 'choice/lazyhash'
|
6
|
+
|
7
|
+
#
|
8
|
+
# Usage of this module is lovingly detailed in the README file.
|
9
|
+
#
|
10
|
+
module Choice
|
11
|
+
extend self
|
12
|
+
|
13
|
+
# The main method, which defines the options
|
14
|
+
def options(hash = {}, &block)
|
15
|
+
# if we are passing in a hash to define our options, use that straight
|
16
|
+
options_from_hash(hash) unless hash.empty?
|
17
|
+
|
18
|
+
# Setup all instance variables
|
19
|
+
reset! if hash.empty?
|
20
|
+
@@args ||= ARGV
|
21
|
+
|
22
|
+
# Eval the passed block to define the options.
|
23
|
+
instance_eval(&block) if block_given?
|
24
|
+
|
25
|
+
# Parse what we've got.
|
26
|
+
parse unless parsed?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Set options from a hash, shorthand style
|
30
|
+
def options_from_hash(options_hash)
|
31
|
+
options_hash.each do |name, definition|
|
32
|
+
option = Option.new
|
33
|
+
definition.each do |key, value|
|
34
|
+
Array(value).each { |hit| option.send(key, hit) }
|
35
|
+
end
|
36
|
+
@@options << [name.to_s, option]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return an array representing the rest of the command line arguments
|
41
|
+
def rest
|
42
|
+
@@rest
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a hash representing options passed in via the command line.
|
46
|
+
def choices
|
47
|
+
@@choices
|
48
|
+
end
|
49
|
+
|
50
|
+
# Shortcut access to Choice.choices
|
51
|
+
def [](choice)
|
52
|
+
choices[choice]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Defines an option.
|
56
|
+
def option(opt, options = {}, &block)
|
57
|
+
# Notice: options is maintained as an array of arrays, the first element
|
58
|
+
# the option name and the second the option object.
|
59
|
+
@@options << [opt.to_s, Option.new(options, &block)]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Separators are text displayed by --help within the options block.
|
63
|
+
def separator(str)
|
64
|
+
# We store separators as simple strings in the options array to maintain
|
65
|
+
# order. They are ignored by the parser.
|
66
|
+
@@options << str
|
67
|
+
end
|
68
|
+
|
69
|
+
# Define the banner, header, footer methods. All are just getters/setters
|
70
|
+
# of class variables.
|
71
|
+
%w[banner header footer].each do |method|
|
72
|
+
define_method(method) do |string|
|
73
|
+
variable = "@@#{method}"
|
74
|
+
return class_variable_get(variable) if string.nil?
|
75
|
+
val = class_variable_get(variable) || ''
|
76
|
+
class_variable_set(variable, val << string)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
# Parse the provided args against the defined options.
|
82
|
+
def parse #:nodoc:
|
83
|
+
# Do nothing if options are not defined.
|
84
|
+
return unless @@options.size > 0
|
85
|
+
|
86
|
+
# Show help if it's anywhere in the argument list.
|
87
|
+
if @@args.include?('--help')
|
88
|
+
help
|
89
|
+
else
|
90
|
+
begin
|
91
|
+
# Delegate parsing to our parser class, passing it our defined
|
92
|
+
# options and the passed arguments.
|
93
|
+
@@choices, @@rest = Parser.parse(@@options, @@args)
|
94
|
+
@@choices = LazyHash.new(@@choices)
|
95
|
+
rescue Choice::Parser::ParseError
|
96
|
+
# If we get an expected exception, show the help file.
|
97
|
+
help
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Did we already parse the arguments?
|
103
|
+
def parsed? #:nodoc:
|
104
|
+
@@choices ||= false
|
105
|
+
end
|
106
|
+
|
107
|
+
# Print the help screen by calling our Writer object
|
108
|
+
def help #:nodoc:
|
109
|
+
Writer.help( { :banner => @@banner, :header => @@header,
|
110
|
+
:options => @@options, :footer => @@footer },
|
111
|
+
output_to, exit_on_help? )
|
112
|
+
end
|
113
|
+
|
114
|
+
# Set the args, potentially to something other than ARGV.
|
115
|
+
def args=(args) #:nodoc:
|
116
|
+
@@args = args.dup.map { |a| a + '' }
|
117
|
+
parse if parsed?
|
118
|
+
end
|
119
|
+
|
120
|
+
# Return the args.
|
121
|
+
def args #:nodoc:
|
122
|
+
@@args
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns the arguments that follow an argument
|
126
|
+
def args_of(opt)
|
127
|
+
args_of_opt = []
|
128
|
+
|
129
|
+
# Return an array of the arguments between opt and the next option,
|
130
|
+
# which all start with "-"
|
131
|
+
@@args.slice(@@args.index(opt)+1, @@args.length).select do |arg|
|
132
|
+
if arg[0].chr != "-"
|
133
|
+
args_of_opt << arg
|
134
|
+
else
|
135
|
+
break
|
136
|
+
end
|
137
|
+
end
|
138
|
+
args_of_opt
|
139
|
+
end
|
140
|
+
|
141
|
+
# You can choose to not kill the script after the help screen is printed.
|
142
|
+
def dont_exit_on_help=(val) #:nodoc:
|
143
|
+
@@exit = true
|
144
|
+
end
|
145
|
+
|
146
|
+
# Do we want to exit on help?
|
147
|
+
def exit_on_help? #:nodoc:
|
148
|
+
@@exit rescue false
|
149
|
+
end
|
150
|
+
|
151
|
+
# If we want to write to somewhere other than STDOUT.
|
152
|
+
def output_to(target = nil) #:nodoc:
|
153
|
+
@@output_to ||= STDOUT
|
154
|
+
return @@output_to if target.nil?
|
155
|
+
@@output_to = target
|
156
|
+
end
|
157
|
+
|
158
|
+
# Reset all the class variables.
|
159
|
+
def reset! #:nodoc:
|
160
|
+
@@args = false
|
161
|
+
@@banner = false
|
162
|
+
@@header = Array.new
|
163
|
+
@@options = Array.new
|
164
|
+
@@footer = Array.new
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Choice
|
2
|
+
|
3
|
+
# This class lets us get away with really bad, horrible, lazy hash accessing.
|
4
|
+
# Like so:
|
5
|
+
# hash = LazyHash.new
|
6
|
+
# hash[:someplace] = "somewhere"
|
7
|
+
# puts hash[:someplace]
|
8
|
+
# puts hash['someplace']
|
9
|
+
# puts hash.someplace
|
10
|
+
#
|
11
|
+
# If you'd like, you can pass in a current hash when initializing to convert
|
12
|
+
# it into a lazyhash. Or you can use the .to_lazyhash method attached to the
|
13
|
+
# Hash object (evil!).
|
14
|
+
class LazyHash < Hash
|
15
|
+
|
16
|
+
# Keep the old methods around.
|
17
|
+
alias_method :old_store, :store
|
18
|
+
alias_method :old_fetch, :fetch
|
19
|
+
|
20
|
+
# You can pass in a normal hash to convert it to a LazyHash.
|
21
|
+
def initialize(hash = nil)
|
22
|
+
hash.each { |key, value| self[key] = value } if !hash.nil? && hash.is_a?(Hash)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Wrapper for []
|
26
|
+
def store(key, value)
|
27
|
+
self[key] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
# Wrapper for []=
|
31
|
+
def fetch(key)
|
32
|
+
self[key]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Store every key as a string.
|
36
|
+
def []=(key, value)
|
37
|
+
key = key.to_s if key.is_a? Symbol
|
38
|
+
self.old_store(key, value)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Every key is stored as a string. Like a normal hash, nil is returned if
|
42
|
+
# the key does not exist.
|
43
|
+
def [](key)
|
44
|
+
key = key.to_s if key.is_a? Symbol
|
45
|
+
self.old_fetch(key) rescue return nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# You can use hash.something or hash.something = 'thing' since this is
|
49
|
+
# truly a lazy hash.
|
50
|
+
def method_missing(meth, *args)
|
51
|
+
meth = meth.to_s
|
52
|
+
if meth =~ /=/
|
53
|
+
self[meth.sub('=','')] = args.first
|
54
|
+
else
|
55
|
+
self[meth]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Really ugly, horrible, extremely fun hack.
|
63
|
+
class Hash #:nodoc:
|
64
|
+
def to_lazyhash
|
65
|
+
return Choice::LazyHash.new(self)
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Choice
|
2
|
+
|
3
|
+
# The Option class parses and stores all the information about a specific
|
4
|
+
# option.
|
5
|
+
class Option #:nodoc: all
|
6
|
+
|
7
|
+
# Since we define getters/setters on the fly, we need a white list of
|
8
|
+
# which to accept. Here's the list.
|
9
|
+
CHOICES = %w[short long desc default filter action cast validate valid]
|
10
|
+
|
11
|
+
# You can instantiate an option on its own or by passing it a name and
|
12
|
+
# a block. If you give it a block, it will eval() the block and set itself
|
13
|
+
# up nicely.
|
14
|
+
def initialize(options = {}, &block)
|
15
|
+
# Here we store the definitions this option contains, to make to_a and
|
16
|
+
# to_h easier.
|
17
|
+
@choices = []
|
18
|
+
|
19
|
+
# If we got a block, eval it and set everything up.
|
20
|
+
instance_eval(&block) if block_given?
|
21
|
+
|
22
|
+
# Is this option required?
|
23
|
+
@required = options[:required] || false
|
24
|
+
@choices << 'required'
|
25
|
+
end
|
26
|
+
|
27
|
+
# This is the catch all for the getter/setter choices defined in CHOICES.
|
28
|
+
# It also gives us choice? methods.
|
29
|
+
def method_missing(method, *args, &block)
|
30
|
+
# Get the name of the choice we want, as a class variable string.
|
31
|
+
var = "@#{method.to_s.sub('?','')}"
|
32
|
+
|
33
|
+
# To string, for regex purposes.
|
34
|
+
method = method.to_s
|
35
|
+
|
36
|
+
# Don't let in any choices not defined in our white list array.
|
37
|
+
raise ParseError, "I don't know `#{method}'" unless CHOICES.include? method.sub('?','')
|
38
|
+
|
39
|
+
# If we're asking a question, give an answer. Like 'short?'.
|
40
|
+
return !!instance_variable_get(var) if method =~ /\?/
|
41
|
+
|
42
|
+
# If we were called with no arguments, we want a get.
|
43
|
+
return instance_variable_get(var) unless args[0] || block_given?
|
44
|
+
|
45
|
+
# If we were given a block or an argument, save it.
|
46
|
+
instance_variable_set(var, args[0]) if args[0]
|
47
|
+
instance_variable_set(var, block) if block_given?
|
48
|
+
|
49
|
+
# Add the choice to the @choices array if we're setting it for the first
|
50
|
+
# time.
|
51
|
+
@choices << method if args[0] || block_given? unless @choices.index(method)
|
52
|
+
end
|
53
|
+
|
54
|
+
# The desc method is slightly special: it stores itself as an array and
|
55
|
+
# each subsequent call adds to that array, rather than overwriting it.
|
56
|
+
# This is so we can do multi-line descriptions easily.
|
57
|
+
def desc(string = nil)
|
58
|
+
return @desc if string.nil?
|
59
|
+
|
60
|
+
@desc ||= []
|
61
|
+
@desc.push(string)
|
62
|
+
|
63
|
+
# Only add to @choices array if it's not already present.
|
64
|
+
@choices << 'desc' unless @choices.index('desc')
|
65
|
+
end
|
66
|
+
|
67
|
+
# Simple, desc question method.
|
68
|
+
def desc?() !!@desc end
|
69
|
+
|
70
|
+
# Returns Option converted to an array.
|
71
|
+
def to_a
|
72
|
+
@choices.inject([]) do |array, choice|
|
73
|
+
return array unless @choices.include? choice
|
74
|
+
array + [instance_variable_get("@#{choice}")]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns Option converted to a hash.
|
79
|
+
def to_h
|
80
|
+
@choices.inject({}) do |hash, choice|
|
81
|
+
return hash unless @choices.include? choice
|
82
|
+
hash.merge choice => instance_variable_get("@#{choice}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# In case someone tries to use a method we don't know about in their
|
87
|
+
# option block.
|
88
|
+
class ParseError < Exception; end
|
89
|
+
end
|
90
|
+
end
|