Use modules in Ruby implementation.

Options, arguments and the help text are now also avaialble as EasyOptions.options, EasyOptions.arguments and EasyOptions.documentation. The global variables $options, $arguments and $documentation are still present.
This commit is contained in:
Renato Silva 2014-12-01 22:48:25 -02:00
parent 02ac5bfeba
commit 6b1b678e2e
4 changed files with 215 additions and 177 deletions

View File

@ -41,25 +41,26 @@ The above comments work both as source code documentation and as help text, as w
gem install easyoptions gem install easyoptions
``` ```
After writing your documentation, you simply require this script. Then all command line options will get parsed into the `$options` hash, as described above. You can then check their values for reacting to them. All regular arguments will get stored into the `$arguments` array. Here is an example for parsing the comments above: After writing your documentation, you simply require this script. Then all command line options will get parsed into the `EasyOptions.options` hash, as described above. You can then check their values for reacting to them. All regular arguments will get stored into the `EasyOptions.arguments` array. Both the options and arguments can be accessed at once with `EasyOptions.all`. Here is an example for parsing the comments above:
```ruby ```ruby
require "easyoptions" require "easyoptions"
options, arguments = EasyOptions.all
# Boolean options # Boolean options
puts "Option specified: --some-option" if $options[:some_option] puts "Option specified: --some-option" if options[:some_option]
puts "Option specified: --some-boolean" if $options[:some_boolean] puts "Option specified: --some-boolean" if options[:some_boolean]
# Parameter option # Parameter option
value = $options[:some_value] value = options[:some_value]
if value if value
type = value.is_a?(Fixnum)? "number" : "string" type = value.is_a?(Fixnum)? "number" : "string"
puts "Option specified: --some-value is #{value} (a #{type})" puts "Option specified: --some-value is #{value} (a #{type})"
end end
# Arguments # Arguments
exit if $arguments.empty? exit if arguments.empty?
$arguments.each do |argument| arguments.each do |argument|
puts "Argument specified: #{argument}" puts "Argument specified: #{argument}"
end end
``` ```

View File

@ -1,6 +1,6 @@
Gem::Specification.new do |spec| Gem::Specification.new do |spec|
spec.name = "easyoptions" spec.name = "easyoptions"
spec.version = "2014.9.15" spec.version = "2014.12.2"
spec.license = "GPLv2" spec.license = "GPLv2"
spec.author = "Renato Silva" spec.author = "Renato Silva"
spec.email = "br.renatosilva@gmail.com" spec.email = "br.renatosilva@gmail.com"

View File

@ -2,7 +2,7 @@
# Encoding: ISO-8859-1 # Encoding: ISO-8859-1
## ##
## EasyOptions 2014.9.15 ## EasyOptions 2014.12.2
## Copyright (c) 2013, 2014 Renato Silva ## Copyright (c) 2013, 2014 Renato Silva
## GNU GPLv2 licensed ## GNU GPLv2 licensed
## ##
@ -22,22 +22,22 @@
## ## it shows this double-hash documentation. ## ## it shows this double-hash documentation.
## ## ## ##
## ## -o, --option This option will get stored as true value ## ## -o, --option This option will get stored as true value
## ## under $options[:option]. Long version is ## ## under EasyOptions.options[:option]. Long
## ## mandatory, and can be specified before or ## ## version is mandatory, and can be specified
## ## after short version. ## ### before or after short version.
## ## ## ##
## ## --some-boolean This will get stored as true value under ## ## --some-boolean This will get stored as true value under
## ## $options[:some_boolean]. ## ## EasyOptions.options[:some_boolean].
## ## ## ##
## ## --some-value=VALUE This is going to store the VALUE specified ## ## --some-value=VALUE This is going to store the VALUE specified
## ## under $options[:some_value]. The equal ## ## under EasyOptions.options[:some_value].
## ## sign is optional and can be replaced with ## ## The equal sign is optional and can be
## ## blank space when running the target ## ## replaced with blank space when running the
## ## script. If VALUE is composed of digits, it ## ## target script. If VALUE is composed of
## ## will be converted into an integer, ## ## digits, it will be converted into an
## ## otherwise it will get stored as a string. ## ## integer, otherwise it will get stored as a
## ## Short version is not available in this ## ## string. Short version is not available in
## ## format. ## ## this format.
## ##
## The above comments work both as source code documentation and as help ## The above comments work both as source code documentation and as help
## text, as well as define the options supported by your script. There is no ## text, as well as define the options supported by your script. There is no
@ -45,9 +45,9 @@
## replaced with the actual script name. ## replaced with the actual script name.
## ##
## After writing your documentation, you simply require this script. Then all ## After writing your documentation, you simply require this script. Then all
## command line options will get parsed into the $options hash, as described ## command line options will get parsed into the EasyOptions.options hash, as
## above. You can then check their values for reacting to them. All regular ## described above. You can then check their values for reacting to them. All
## arguments will get stored into the $arguments array. ## regular arguments will get stored into the EasyOptions.arguments array.
## ##
## In fact, this script is an example of itself. You are seeing this help ## In fact, this script is an example of itself. You are seeing this help
## message either because you are reading the source code, or you have called ## message either because you are reading the source code, or you have called
@ -72,7 +72,9 @@
## arguments ## arguments
## ##
class Option module EasyOptions
class Option
def initialize(long_version, short_version, boolean=true) def initialize(long_version, short_version, boolean=true)
raise ArgumentError.new("Long version is mandatory") if not long_version or long_version.length < 2 raise ArgumentError.new("Long version is mandatory") if not long_version or long_version.length < 2
@short = short_version.to_sym if short_version @short = short_version.to_sym if short_version
@ -94,16 +96,17 @@ class Option
attr_accessor :short attr_accessor :short
attr_accessor :long attr_accessor :long
attr_accessor :boolean attr_accessor :boolean
end end
def finish(error) class Parser
$stderr.puts "Error: #{error}." def initialize
$stderr.puts "See --help for usage and options." @known_options = [Option.new(:help, :h)]
puts "exit 1" if BashOutput @documentation = parse_doc
exit false @arguments = []
end @options = {}
end
def parse_doc def parse_doc
begin begin
doc = File.readlines($0) doc = File.readlines($0)
rescue Errno::ENOENT rescue Errno::ENOENT
@ -118,59 +121,48 @@ def parse_doc
line.gsub!(/@script.name/, File.basename($0)) line.gsub!(/@script.name/, File.basename($0))
line.gsub(/@#/, "@") line.gsub(/@#/, "@")
end end
end end
def check_bash_output def parse
$0 = ENV["from"] || $0 # Parse known options from documentation
$0 == ENV["from"] @documentation.map do |line|
end
# Initialization
known_options = [ Option.new(:help, :h)]
BashOutput = check_bash_output
$documentation = parse_doc
$arguments = []
$options = {}
# Parse known options from documentation
$documentation.map do |line|
line = line.strip line = line.strip
case line case line
when /^-h, --help.*/ then next when /^-h, --help.*/ then next
when /^--help, -h.*/ then next when /^--help, -h.*/ then next
when /^-.*, --.*/ then line = line.split(/(^-|,\s--|\s)/); known_options << Option.new(line[4], line[2]) when /^-.*, --.*/ then line = line.split(/(^-|,\s--|\s)/); @known_options << Option.new(line[4], line[2])
when /^--.*, -.*/ then line = line.split(/(--|,\s-|\s)/); known_options << Option.new(line[2], line[4]) when /^--.*, -.*/ then line = line.split(/(--|,\s-|\s)/); @known_options << Option.new(line[2], line[4])
when /^--.*=.*/ then line = line.split(/(--|=|\s)/); known_options << Option.new(line[2], nil, false) when /^--.*=.*/ then line = line.split(/(--|=|\s)/); @known_options << Option.new(line[2], nil, false)
when /^--.* .*/ then line = line.split(/(--|\s)/); known_options << Option.new(line[2], nil) when /^--.* .*/ then line = line.split(/(--|\s)/); @known_options << Option.new(line[2], nil)
end
end end
end
# Format arguments input # Format arguments input
arguments = ARGV.map do |argument| raw_arguments = ARGV.map do |argument|
if argument =~ /^-[^-].*$/i then if argument =~ /^-[^-].*$/i then
argument.split("")[1..-1].map { |char| "-#{char}" } argument.split("")[1..-1].map { |char| "-#{char}" }
else else
argument argument
end end
end.flatten end.flatten
# Parse the provided options # Parse the provided options
arguments.each_with_index do |argument, index| raw_arguments.each_with_index do |argument, index|
unknown_option = true unknown_option = true
known_options.each do |known_option| @known_options.each do |known_option|
# Boolean option # Boolean option
if known_option.in?(argument) and known_option.boolean then if known_option.in?(argument) and known_option.boolean then
$options[known_option.long] = true @options[known_option.long] = true
unknown_option = false unknown_option = false
break break
# Option with value in next parameter # Option with value in next parameter
elsif known_option.in?(argument) and not known_option.boolean then elsif known_option.in?(argument) and not known_option.boolean then
value = arguments[index + 1] value = raw_arguments[index + 1]
finish("you must specify a value for #{known_option}") if not value or value.start_with?("-") Parser.finish("you must specify a value for #{known_option}") if not value or value.start_with?("-")
value = value.to_i if value =~ /^[0-9]+$/ value = value.to_i if value =~ /^[0-9]+$/
$options[known_option.long] = value @options[known_option.long] = value
unknown_option = false unknown_option = false
break break
@ -178,53 +170,98 @@ arguments.each_with_index do |argument, index|
elsif known_option.in_with_value?(argument) and not known_option.boolean then elsif known_option.in_with_value?(argument) and not known_option.boolean then
value = argument.split("=")[1] value = argument.split("=")[1]
value = value.to_i if value =~ /^[0-9]+$/ value = value.to_i if value =~ /^[0-9]+$/
$options[known_option.long] = value @options[known_option.long] = value
unknown_option = false unknown_option = false
break break
# Long option with unnecessary value # Long option with unnecessary value
elsif known_option.in_with_value?(argument) and known_option.boolean then elsif known_option.in_with_value?(argument) and known_option.boolean then
value = argument.split("=")[1] value = argument.split("=")[1]
finish("#{known_option} does not accept a value (you specified \"#{value}\")") Parser.finish("#{known_option} does not accept a value (you specified \"#{value}\")")
end end
end end
# Unrecognized option # Unrecognized option
finish("unrecognized option \"#{argument}\"") if unknown_option and argument.start_with?("-") Parser.finish("unrecognized option \"#{argument}\"") if unknown_option and argument.start_with?("-")
end end
# Help option # Help option
if $options[:help] if @options[:help]
if BashOutput then if BashOutput then
print "printf '" print "printf '"
puts $documentation puts @documentation
puts "'" puts "'"
puts "exit" puts "exit"
else else
puts $documentation puts @documentation
end end
exit -1 exit -1
end end
# Regular arguments # Regular arguments
next_is_value = false next_is_value = false
arguments.each do |argument| raw_arguments.each do |argument|
if argument.start_with?("-") then if argument.start_with?("-") then
known_option = known_options.find { |known_option| known_option.in?(argument) } known_option = @known_options.find { |known_option| known_option.in?(argument) }
next_is_value = (known_option and not known_option.boolean) next_is_value = (known_option and not known_option.boolean)
else else
$arguments << argument if not next_is_value arguments << argument if not next_is_value
next_is_value = false next_is_value = false
end end
end end
# Bash support # Bash support
if BashOutput then if BashOutput then
$options.keys.each do |name| @options.keys.each do |name|
puts "#{name}=\"#{$options[name].to_s.sub("true", "yes")}\"" puts "#{name}=\"#{@options[name].to_s.sub("true", "yes")}\""
end end
puts "unset arguments" puts "unset arguments"
$arguments.each do |argument| arguments.each do |argument|
puts "arguments+=(\"#{argument}\")" puts "arguments+=(\"#{argument}\")"
end end
end
end
def self.finish(error)
$stderr.puts "Error: #{error}."
$stderr.puts "See --help for usage and options."
puts "exit 1" if BashOutput
exit false
end
def self.check_bash_output
$0 = ENV["from"] || $0
$0 == ENV["from"]
end
BashOutput = check_bash_output
attr_accessor :documentation
attr_accessor :arguments
attr_accessor :options
end
class << self
@@parser = Parser.new
@@parser.parse
def options
@@parser.options
end
def arguments
@@parser.arguments
end
def documentation
@@parser.documentation
end
def all
[options, arguments, documentation]
end
def finish(error)
Parser.finish(error)
end
end
# This is supposed to be eventually removed
$documentation = @@parser.documentation
$arguments = @@parser.arguments
$options = @@parser.options
end end

View File

@ -22,20 +22,20 @@
## format. ## format.
require_relative "easyoptions" require_relative "easyoptions"
options, arguments = EasyOptions.all
# Boolean options # Boolean options
puts "Option specified: --some-option" if $options[:option] puts "Option specified: --some-option" if options[:option]
puts "Option specified: --some-boolean" if $options[:some_boolean] puts "Option specified: --some-boolean" if options[:some_boolean]
# Parameter option # Parameter option
value = $options[:some_value] value = options[:some_value]
if value if value
value = $options[:some_value]
type = value.is_a?(Fixnum)? "number" : "string" type = value.is_a?(Fixnum)? "number" : "string"
puts "Option specified: --some-value is #{value} (a #{type})" puts "Option specified: --some-value is #{value} (a #{type})"
end end
# Arguments # Arguments
$arguments.each do |argument| arguments.each do |argument|
puts "Argument specified: #{argument}" puts "Argument specified: #{argument}"
end end