shu-san-scripts 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/store +50 -0
- data/lib/SANStore.rb +24 -0
- data/lib/SANStore/cli.rb +29 -0
- data/lib/SANStore/cli/base.rb +103 -0
- data/lib/SANStore/cli/commands.rb +31 -0
- data/lib/SANStore/cli/commands/delete_vol.rb +113 -0
- data/lib/SANStore/cli/commands/help.rb +106 -0
- data/lib/SANStore/cli/commands/list_vols.rb +103 -0
- data/lib/SANStore/cli/commands/new_vol.rb +134 -0
- data/lib/SANStore/cli/logger.rb +92 -0
- data/lib/SANStore/cri.rb +12 -0
- data/lib/SANStore/cri/base.rb +153 -0
- data/lib/SANStore/cri/command.rb +104 -0
- data/lib/SANStore/cri/core_ext.rb +8 -0
- data/lib/SANStore/cri/core_ext/string.rb +41 -0
- data/lib/SANStore/cri/option_parser.rb +186 -0
- data/lib/SANStore/helpers/uuid.rb +35 -0
- data/lib/SANStore/iSCSI/comstar.rb +239 -0
- data/lib/SANStore/zfs/zfs.rb +59 -0
- metadata +24 -5
@@ -0,0 +1,103 @@
|
|
1
|
+
# Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
|
2
|
+
#
|
3
|
+
# Permission to use, copy, modify, and/or distribute this software for
|
4
|
+
# any purpose with or without fee is hereby granted, provided that the
|
5
|
+
# above copyright notice and this permission notice appear in all copies.
|
6
|
+
#
|
7
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
8
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
9
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
10
|
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
11
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
12
|
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
13
|
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
14
|
+
|
15
|
+
# Volume names are based on RFC 4122 UUIDs
|
16
|
+
require "uuidtools"
|
17
|
+
|
18
|
+
# Use the ZFS library
|
19
|
+
require "SANStore/zfs/zfs"
|
20
|
+
|
21
|
+
# Use the COMStar iSCSI library
|
22
|
+
require "SANStore/iSCSI/comstar.rb"
|
23
|
+
|
24
|
+
module SANStore::CLI::Commands
|
25
|
+
|
26
|
+
# @author David Love
|
27
|
+
#
|
28
|
+
# The +list_vols+ command show the current iSCSI targets available
|
29
|
+
# on this host
|
30
|
+
class ListVols < Cri::Command
|
31
|
+
|
32
|
+
# The name of the sub-command (as it appears in the command line app)
|
33
|
+
def name
|
34
|
+
'list_vols'
|
35
|
+
end
|
36
|
+
|
37
|
+
# The aliases this sub-command is known by
|
38
|
+
def aliases
|
39
|
+
[
|
40
|
+
"list_vol", "list", "ls"
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
# A short help text describing the purpose of this command
|
45
|
+
def short_desc
|
46
|
+
'Show the currently defined iSCSI targets on this host.'
|
47
|
+
end
|
48
|
+
|
49
|
+
# A longer description, detailing both the purpose and the
|
50
|
+
# use of this command
|
51
|
+
def long_desc
|
52
|
+
'Displays a list of valid iSCSI targets, all of which ' +
|
53
|
+
'should be available on the local network' + "\n\n"
|
54
|
+
'NOTE: Because of the way iSCSI works, this host has no ' +
|
55
|
+
'way of knowing what is actually '+ ANSI.bold{ "in" } + ' the volume. So even ' +
|
56
|
+
'if a target is defined, this host has no way of knowing if ' +
|
57
|
+
'a given initiator can actually ' + ANSI.bold{ "use" } +
|
58
|
+
'the contents of this volume. If something appears to be '
|
59
|
+
'wrong, check the set-up of the host to make sure it can '
|
60
|
+
'actually connect to the targets defined here.'
|
61
|
+
end
|
62
|
+
|
63
|
+
# Show the user the basic syntax of this command
|
64
|
+
def usage
|
65
|
+
"store list_vols"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Define the options for this command
|
69
|
+
def option_definitions
|
70
|
+
[
|
71
|
+
]
|
72
|
+
end
|
73
|
+
|
74
|
+
# Execute the command
|
75
|
+
def run(options, arguments)
|
76
|
+
|
77
|
+
# Get the list of defined volumes
|
78
|
+
volumes = COMStar.list_vols
|
79
|
+
|
80
|
+
# Show the list to the caller
|
81
|
+
text = String.new
|
82
|
+
volumes.each{|target|
|
83
|
+
text << sprintf("%-60s ", target[:name])
|
84
|
+
|
85
|
+
if target[:state] == "online" then
|
86
|
+
text << sprintf("%-20s ", ANSI.green{ target[:state] })
|
87
|
+
else
|
88
|
+
text << sprintf("%-20s ", ANSI.black{ target[:state] })
|
89
|
+
end
|
90
|
+
|
91
|
+
if target[:sessions].to_i > 0 then
|
92
|
+
text << sprintf("%-10s\n", ANSI.white{ target[:sessions] })
|
93
|
+
else
|
94
|
+
text << sprintf("%-10s\n", ANSI.black{ target[:sessions] })
|
95
|
+
end
|
96
|
+
}
|
97
|
+
puts sprintf("%-68s %-19s %-10s\n", ANSI.bold{ "Target Name"}, ANSI.bold{ "Status" }, ANSI.bold{ "Open Sessions" })
|
98
|
+
puts text
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
|
2
|
+
#
|
3
|
+
# Permission to use, copy, modify, and/or distribute this software for
|
4
|
+
# any purpose with or without fee is hereby granted, provided that the
|
5
|
+
# above copyright notice and this permission notice appear in all copies.
|
6
|
+
#
|
7
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
8
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
9
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
10
|
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
11
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
12
|
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
13
|
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
14
|
+
|
15
|
+
# Volume names are based on RFC 4122 UUIDs
|
16
|
+
require "uuidtools"
|
17
|
+
|
18
|
+
# Use the ANSI library to report the status to the user
|
19
|
+
require "ansi/code"
|
20
|
+
|
21
|
+
# Use the Socket API to get the first IPv4 address
|
22
|
+
require "socket"
|
23
|
+
|
24
|
+
# Use the ZFS library
|
25
|
+
require "SANStore/zfs/zfs"
|
26
|
+
|
27
|
+
# Use the COMStar iSCSI library
|
28
|
+
require "SANStore/iSCSI/comstar.rb"
|
29
|
+
|
30
|
+
module SANStore::CLI::Commands
|
31
|
+
|
32
|
+
# @author David Love
|
33
|
+
#
|
34
|
+
# The +new_vol+ command adds a new ZFS volume to the specified volume
|
35
|
+
# store, and sets it up as an iSCSI target. Defaults are supplied to
|
36
|
+
# all arguments, but can be overridden by the user for slightly
|
37
|
+
# customised set-up of the volume store. However, the user interface
|
38
|
+
# should be kept as simple as possible.
|
39
|
+
class NewVol < Cri::Command
|
40
|
+
|
41
|
+
# The name of the sub-command (as it appears in the command line app)
|
42
|
+
def name
|
43
|
+
'new_vol'
|
44
|
+
end
|
45
|
+
|
46
|
+
# The aliases this sub-command is known by
|
47
|
+
def aliases
|
48
|
+
[
|
49
|
+
"new", "add", "add_vol"
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
# A short help text describing the purpose of this command
|
54
|
+
def short_desc
|
55
|
+
'Create a new iSCSI volume in the SAN volume store.'
|
56
|
+
end
|
57
|
+
|
58
|
+
# A longer description, detailing both the purpose and the
|
59
|
+
# use of this command
|
60
|
+
def long_desc
|
61
|
+
'By default, this command creates a 20G ZFS volume, and marks it for ' +
|
62
|
+
'sharing as an iSCSI target on the local network.' + "\n\n" +
|
63
|
+
'Warning: By default this commands sets up the iSCSI target with NO ' +
|
64
|
+
'security. This is fine for testing and use in the labs, but obviously ' +
|
65
|
+
'is not ideal if you care about the data stored on this new volume...'
|
66
|
+
end
|
67
|
+
|
68
|
+
# Show the user the basic syntax of this command
|
69
|
+
def usage
|
70
|
+
"store new_volume [--volume-store ZFS_PATH][--name GUID] [--size INTEGER]"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Define the options for this command
|
74
|
+
def option_definitions
|
75
|
+
[
|
76
|
+
{ :short => 'v', :long => 'volume_store', :argument => :optional,
|
77
|
+
:desc => 'specifify the ZFS root of the new iSCSI volume. Defaults to "store/volumes".'
|
78
|
+
},
|
79
|
+
{ :short => 'n', :long => 'name', :argument => :optional,
|
80
|
+
:desc => 'the name of the new volume. This must be a valid ZFS volume name, and defaults to ' +
|
81
|
+
'an RFC 4122 GUID.'
|
82
|
+
},
|
83
|
+
{ :short => 's', :long => 'size', :argument => :optional,
|
84
|
+
:desc => 'the size of the new iSCSI volume. Note that while ZFS allows you to change the size ' +
|
85
|
+
'of the new volume relatively easily, because the iSCSI initiator sees this volume as a raw ' +
|
86
|
+
'device changing the size later may be very easy or very difficult depending on the initiators ' +
|
87
|
+
'operating system (and the specific file system being used). In other words, choose with care: ' +
|
88
|
+
'by default this command uses a size of 20G, which should be enough for most tasks in the labs.'
|
89
|
+
},
|
90
|
+
]
|
91
|
+
end
|
92
|
+
|
93
|
+
# Execute the command
|
94
|
+
def run(options, arguments)
|
95
|
+
|
96
|
+
# Look at the options, and if we don't find any (or some are
|
97
|
+
# missing), set them to the default values
|
98
|
+
if options[:volume_store].nil? or options[:volume_store].empty? then
|
99
|
+
volume_store = "store/volumes"
|
100
|
+
options[:volume_store] = volume_store
|
101
|
+
end
|
102
|
+
|
103
|
+
if options[:name].nil? or options[:name].empty? then
|
104
|
+
name = UUIDTools::UUID.timestamp_create
|
105
|
+
options[:name] = name.to_s
|
106
|
+
end
|
107
|
+
|
108
|
+
if options[:size].nil? or options[:size].empty? then
|
109
|
+
size = "20G"
|
110
|
+
options[:size] = size
|
111
|
+
end
|
112
|
+
|
113
|
+
SANStore::CLI::Logger.instance.log_level(:low, :info, "Using #{options[:name]} as the volume identifier")
|
114
|
+
|
115
|
+
# Ask for a new volume
|
116
|
+
ZFS.new_volume(options[:volume_store] + "/" + options[:name], options[:size])
|
117
|
+
|
118
|
+
# Set the volume up as an iSCSI target
|
119
|
+
target_name = COMStar.new_target(options[:volume_store] + "/" + options[:name], options[:name])
|
120
|
+
|
121
|
+
# Tell the caller what the new volume name is
|
122
|
+
text = "\n"
|
123
|
+
text << "A new iSCSI target has been created with the following properties\n"
|
124
|
+
text << "\n"
|
125
|
+
text << sprintf(" %-25s %s\n", ANSI.red{ "Name:" }, target_name.wrap_and_indent(78, 20).lstrip)
|
126
|
+
text << sprintf(" %-25s %s\n", ANSI.red{ "IPv4 Address:" }, Socket::getaddrinfo(Socket.gethostname,"echo",Socket::AF_INET)[0][3])
|
127
|
+
text << "\n"
|
128
|
+
|
129
|
+
puts text
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
|
2
|
+
#
|
3
|
+
# Permission to use, copy, modify, and/or distribute this software for
|
4
|
+
# any purpose with or without fee is hereby granted, provided that the
|
5
|
+
# above copyright notice and this permission notice appear in all copies.
|
6
|
+
#
|
7
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
8
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
9
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
10
|
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
11
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
12
|
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
13
|
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
14
|
+
#
|
15
|
+
|
16
|
+
require 'singleton'
|
17
|
+
require 'facets'
|
18
|
+
|
19
|
+
module SANStore::CLI
|
20
|
+
|
21
|
+
# SANStore::CLI::Logger is a singleton class responsible for generating
|
22
|
+
# feedback in the terminal.
|
23
|
+
class Logger
|
24
|
+
|
25
|
+
# ANSI console codes (escape sequences) for highlighting particular
|
26
|
+
# log outputs.
|
27
|
+
ACTION_COLORS = {
|
28
|
+
:create => "\e[1m" + "\e[32m", # bold + green
|
29
|
+
:delete => "\e[1m" + "\e[31m", # bold + red
|
30
|
+
:error => "\e[1m" + "\e[31m", # bold + red
|
31
|
+
:identical => "\e[1m" + "\e[34m", # bold + blue
|
32
|
+
:info => "\e[1m" + "\e[34m", # bold + blue
|
33
|
+
:update => "\e[1m" + "\e[33m", # bold + yellow
|
34
|
+
:warning => "\e[1m" + "\e[33m", # bold + yellow
|
35
|
+
}
|
36
|
+
|
37
|
+
include Singleton
|
38
|
+
|
39
|
+
# The log level, which can be :high, :low or :off (which will log all
|
40
|
+
# messages, only high-priority messages, or no messages at all,
|
41
|
+
# respectively).
|
42
|
+
attr_accessor :level
|
43
|
+
|
44
|
+
# Whether to use color in log messages or not
|
45
|
+
attr_accessor :color
|
46
|
+
alias_method :color?, :color
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@level = :high
|
50
|
+
@color = true
|
51
|
+
end
|
52
|
+
|
53
|
+
# Logs a messsage, using appropriate colours to highlight different
|
54
|
+
# levels.
|
55
|
+
#
|
56
|
+
# +level+:: The importance of this action. Can be :high or :low.
|
57
|
+
#
|
58
|
+
# +action+:: The kind of file action. Can be :create, :update or
|
59
|
+
# :identical.
|
60
|
+
#
|
61
|
+
# +message+:: The identifier of the item the action was performed on.
|
62
|
+
def log_level(level, action, message)
|
63
|
+
log(
|
64
|
+
level,
|
65
|
+
'%s%12s%s: %s' % [
|
66
|
+
color? ? ACTION_COLORS[action.to_sym] : '',
|
67
|
+
action.to_s.capitalize,
|
68
|
+
color? ? "\e[0m" : '',
|
69
|
+
message.word_wrap(60).indent(15).lstrip
|
70
|
+
]
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Logs a message.
|
75
|
+
#
|
76
|
+
# +level+:: The importance of this message. Can be :high or :low.
|
77
|
+
#
|
78
|
+
# +message+:: The message to be logged.
|
79
|
+
#
|
80
|
+
# +io+:: The IO instance to which the message will be written. Defaults to
|
81
|
+
# standard output.
|
82
|
+
def log(level, message, io=$stdout)
|
83
|
+
# Don't log when logging is disabled
|
84
|
+
return if @level == :off
|
85
|
+
|
86
|
+
# Log when level permits it
|
87
|
+
io.puts(message) if (@level == :low or @level == level)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
data/lib/SANStore/cri.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
module Cri
|
2
|
+
|
3
|
+
# Cri::Base is the central class representing a commandline tool. It has a
|
4
|
+
# list of commands.
|
5
|
+
class Base
|
6
|
+
|
7
|
+
# The CLI's list of commands (should also contain the help command)
|
8
|
+
attr_reader :commands
|
9
|
+
|
10
|
+
# The CLI's help command (required)
|
11
|
+
attr_accessor :help_command
|
12
|
+
|
13
|
+
# Creates a new instance of the commandline tool.
|
14
|
+
def initialize(tool_name)
|
15
|
+
@tool_name = tool_name
|
16
|
+
|
17
|
+
@commands = []
|
18
|
+
end
|
19
|
+
|
20
|
+
# Parses the given commandline arguments and executes the requested
|
21
|
+
# command.
|
22
|
+
def run(args)
|
23
|
+
# Check arguments
|
24
|
+
if args.length == 0
|
25
|
+
@help_command.run([], [])
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
|
29
|
+
# Partition options
|
30
|
+
opts_before_command = []
|
31
|
+
command_name = nil
|
32
|
+
opts_and_args_after_command = []
|
33
|
+
stage = 0
|
34
|
+
args.each do |arg|
|
35
|
+
# Update stage if necessary
|
36
|
+
stage = 1 if stage == 0 && !is_option?(arg)
|
37
|
+
|
38
|
+
# Add
|
39
|
+
opts_before_command << arg if stage == 0
|
40
|
+
command_name = arg if stage == 1
|
41
|
+
opts_and_args_after_command << arg if stage == 2
|
42
|
+
|
43
|
+
# Update stage if necessary
|
44
|
+
stage = 2 if stage == 1
|
45
|
+
end
|
46
|
+
|
47
|
+
# Handle options before command
|
48
|
+
begin
|
49
|
+
parsed_arguments = Cri::OptionParser.parse(opts_before_command, global_option_definitions)
|
50
|
+
rescue Cri::OptionParser::IllegalOptionError => e
|
51
|
+
$stderr.puts "illegal option -- #{e}"
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
parsed_arguments[:options].keys.each do |option|
|
55
|
+
handle_option(option)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get command
|
59
|
+
if command_name.nil?
|
60
|
+
$stderr.puts "no command given"
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
command = command_named(command_name)
|
64
|
+
if command.nil?
|
65
|
+
$stderr.puts "no such command: #{command_name}"
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
|
69
|
+
# Parse arguments
|
70
|
+
option_definitions = command.option_definitions + global_option_definitions
|
71
|
+
begin
|
72
|
+
parsed_arguments = Cri::OptionParser.parse(opts_and_args_after_command, option_definitions)
|
73
|
+
rescue Cri::OptionParser::IllegalOptionError => e
|
74
|
+
$stderr.puts "illegal option -- #{e}"
|
75
|
+
exit 1
|
76
|
+
rescue Cri::OptionParser::OptionRequiresAnArgumentError => e
|
77
|
+
$stderr.puts "option requires an argument -- #{e}"
|
78
|
+
exit 1
|
79
|
+
end
|
80
|
+
|
81
|
+
# Handle global options
|
82
|
+
global_options = global_option_definitions.map { |o| o[:long] }
|
83
|
+
global_options.delete_if { |o| !parsed_arguments[:options].keys.include?(o.to_sym) }
|
84
|
+
global_options.each { |o| handle_option(o.to_sym) }
|
85
|
+
|
86
|
+
if parsed_arguments[:options].has_key?(:help)
|
87
|
+
# Show help for this command
|
88
|
+
show_help(command)
|
89
|
+
else
|
90
|
+
# Run command
|
91
|
+
command.run(parsed_arguments[:options], parsed_arguments[:arguments])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the command with the given name.
|
96
|
+
def command_named(name)
|
97
|
+
# Find by exact name or alias
|
98
|
+
command = @commands.find { |c| c.name == name or c.aliases.include?(name) }
|
99
|
+
return command unless command.nil?
|
100
|
+
|
101
|
+
# Find by approximation
|
102
|
+
commands = @commands.select { |c| c.name[0, name.length] == name }
|
103
|
+
if commands.length > 1
|
104
|
+
$stderr.puts "#{@tool_name}: '#{name}' is ambiguous:"
|
105
|
+
$stderr.puts " #{commands.map { |c| c.name }.join(' ') }"
|
106
|
+
exit 1
|
107
|
+
elsif commands.length == 0
|
108
|
+
$stderr.puts "#{@tool_name}: unknown command '#{name}'\n"
|
109
|
+
show_help
|
110
|
+
exit 1
|
111
|
+
else
|
112
|
+
return commands[0]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Shows the help text for the given command, or shows the general help
|
117
|
+
# text if no command is given.
|
118
|
+
def show_help(command=nil)
|
119
|
+
if command.nil?
|
120
|
+
@help_command.run([], [])
|
121
|
+
else
|
122
|
+
@help_command.run([], [ command.name ])
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns the list of global option definitionss.
|
127
|
+
def global_option_definitions
|
128
|
+
[]
|
129
|
+
end
|
130
|
+
|
131
|
+
# Adds the given command to the list of commands. Adding a command will
|
132
|
+
# also cause the command's +base+ to be set to this instance.
|
133
|
+
def add_command(command)
|
134
|
+
@commands << command
|
135
|
+
command.base = self
|
136
|
+
end
|
137
|
+
|
138
|
+
# Handles the given optio
|
139
|
+
def handle_option(option)
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
# Returns true if the given string is an option (i.e. -foo or --foo),
|
146
|
+
# false otherwise.
|
147
|
+
def is_option?(string)
|
148
|
+
string =~ /^-/
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|