From 6b1b678e2e40aaf6f13f0ff8ead26a959970340e Mon Sep 17 00:00:00 2001 From: Renato Silva Date: Mon, 1 Dec 2014 22:48:25 -0200 Subject: [PATCH] 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. --- README.md | 13 +- easyoptions.gemspec | 2 +- easyoptions.rb | 367 ++++++++++++++++++++++++-------------------- example.rb | 10 +- 4 files changed, 215 insertions(+), 177 deletions(-) diff --git a/README.md b/README.md index a18531b..7c9415a 100644 --- a/README.md +++ b/README.md @@ -41,25 +41,26 @@ The above comments work both as source code documentation and as help text, as w 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 require "easyoptions" +options, arguments = EasyOptions.all # Boolean options -puts "Option specified: --some-option" if $options[:some_option] -puts "Option specified: --some-boolean" if $options[:some_boolean] +puts "Option specified: --some-option" if options[:some_option] +puts "Option specified: --some-boolean" if options[:some_boolean] # Parameter option -value = $options[:some_value] +value = options[:some_value] if value type = value.is_a?(Fixnum)? "number" : "string" puts "Option specified: --some-value is #{value} (a #{type})" end # Arguments -exit if $arguments.empty? -$arguments.each do |argument| +exit if arguments.empty? +arguments.each do |argument| puts "Argument specified: #{argument}" end ``` diff --git a/easyoptions.gemspec b/easyoptions.gemspec index e4914de..dc3f852 100644 --- a/easyoptions.gemspec +++ b/easyoptions.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "easyoptions" - spec.version = "2014.9.15" + spec.version = "2014.12.2" spec.license = "GPLv2" spec.author = "Renato Silva" spec.email = "br.renatosilva@gmail.com" diff --git a/easyoptions.rb b/easyoptions.rb index ade0dad..d2ee154 100644 --- a/easyoptions.rb +++ b/easyoptions.rb @@ -2,7 +2,7 @@ # Encoding: ISO-8859-1 ## -## EasyOptions 2014.9.15 +## EasyOptions 2014.12.2 ## Copyright (c) 2013, 2014 Renato Silva ## GNU GPLv2 licensed ## @@ -22,22 +22,22 @@ ## ## it shows this double-hash documentation. ## ## ## ## -o, --option This option will get stored as true value -## ## under $options[:option]. Long version is -## ## mandatory, and can be specified before or -## ## after short version. +## ## under EasyOptions.options[:option]. Long +## ## version is mandatory, and can be specified +## ### before or after short version. ## ## ## ## --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 -## ## under $options[:some_value]. The equal -## ## sign is optional and can be replaced with -## ## blank space when running the target -## ## script. If VALUE is composed of digits, it -## ## will be converted into an integer, -## ## otherwise it will get stored as a string. -## ## Short version is not available in this -## ## format. +## ## under EasyOptions.options[:some_value]. +## ## The equal sign is optional and can be +## ## replaced with blank space when running the +## ## target script. If VALUE is composed of +## ## digits, it will be converted into an +## ## integer, otherwise it will get stored as a +## ## string. Short version is not available in +## ## this format. ## ## 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 @@ -45,9 +45,9 @@ ## replaced with the actual script name. ## ## 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. +## 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. ## ## 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 @@ -72,159 +72,196 @@ ## arguments ## -class Option - def initialize(long_version, short_version, boolean=true) - raise ArgumentError.new("Long version is mandatory") if not long_version or long_version.length < 2 - @short = short_version.to_sym if short_version - @long = long_version.to_s.gsub("-", "_").to_sym - @boolean = boolean +module EasyOptions + + class Option + def initialize(long_version, short_version, boolean=true) + raise ArgumentError.new("Long version is mandatory") if not long_version or long_version.length < 2 + @short = short_version.to_sym if short_version + @long = long_version.to_s.gsub("-", "_").to_sym + @boolean = boolean + end + def to_s + "--#{long_dashed}" + end + def in?(string) + string =~ /^--#{long_dashed}$/ or (@short and string =~ /^-#{@short}$/) + end + def in_with_value?(string) + string =~ /^--#{long_dashed}=.*$/ + end + def long_dashed + @long.to_s.gsub("_", "-") + end + attr_accessor :short + attr_accessor :long + attr_accessor :boolean end - def to_s - "--#{long_dashed}" + + class Parser + def initialize + @known_options = [Option.new(:help, :h)] + @documentation = parse_doc + @arguments = [] + @options = {} + end + + def parse_doc + begin + doc = File.readlines($0) + rescue Errno::ENOENT + exit false + end + doc = doc.find_all do |line| + line =~ /^##[^#]*/ + end + doc = doc.map do |line| + line.strip! + line.sub!(/^## ?/, "") + line.gsub!(/@script.name/, File.basename($0)) + line.gsub(/@#/, "@") + end + end + + def parse + # Parse known options from documentation + @documentation.map do |line| + line = line.strip + case line + when /^-h, --help.*/ 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[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) + end + end + + # Format arguments input + raw_arguments = ARGV.map do |argument| + if argument =~ /^-[^-].*$/i then + argument.split("")[1..-1].map { |char| "-#{char}" } + else + argument + end + end.flatten + + # Parse the provided options + raw_arguments.each_with_index do |argument, index| + unknown_option = true + @known_options.each do |known_option| + + # Boolean option + if known_option.in?(argument) and known_option.boolean then + @options[known_option.long] = true + unknown_option = false + break + + # Option with value in next parameter + elsif known_option.in?(argument) and not known_option.boolean then + value = raw_arguments[index + 1] + 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]+$/ + @options[known_option.long] = value + unknown_option = false + break + + # Option with value after equal sign + elsif known_option.in_with_value?(argument) and not known_option.boolean then + value = argument.split("=")[1] + value = value.to_i if value =~ /^[0-9]+$/ + @options[known_option.long] = value + unknown_option = false + break + + # Long option with unnecessary value + elsif known_option.in_with_value?(argument) and known_option.boolean then + value = argument.split("=")[1] + Parser.finish("#{known_option} does not accept a value (you specified \"#{value}\")") + end + end + + # Unrecognized option + Parser.finish("unrecognized option \"#{argument}\"") if unknown_option and argument.start_with?("-") + end + + # Help option + if @options[:help] + if BashOutput then + print "printf '" + puts @documentation + puts "'" + puts "exit" + else + puts @documentation + end + exit -1 + end + + # Regular arguments + next_is_value = false + raw_arguments.each do |argument| + if argument.start_with?("-") then + known_option = @known_options.find { |known_option| known_option.in?(argument) } + next_is_value = (known_option and not known_option.boolean) + else + arguments << argument if not next_is_value + next_is_value = false + end + end + + # Bash support + if BashOutput then + @options.keys.each do |name| + puts "#{name}=\"#{@options[name].to_s.sub("true", "yes")}\"" + end + puts "unset arguments" + arguments.each do |argument| + puts "arguments+=(\"#{argument}\")" + 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 - def in?(string) - string =~ /^--#{long_dashed}$/ or (@short and string =~ /^-#{@short}$/) - end - def in_with_value?(string) - string =~ /^--#{long_dashed}=.*$/ - end - def long_dashed - @long.to_s.gsub("_", "-") - end - attr_accessor :short - attr_accessor :long - attr_accessor :boolean -end -def finish(error) - $stderr.puts "Error: #{error}." - $stderr.puts "See --help for usage and options." - puts "exit 1" if BashOutput - exit false -end - -def parse_doc - begin - doc = File.readlines($0) - rescue Errno::ENOENT - exit false - end - doc = doc.find_all do |line| - line =~ /^##[^#]*/ - end - doc = doc.map do |line| - line.strip! - line.sub!(/^## ?/, "") - line.gsub!(/@script.name/, File.basename($0)) - line.gsub(/@#/, "@") - end -end - -def check_bash_output - $0 = ENV["from"] || $0 - $0 == ENV["from"] -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 - case line - when /^-h, --help.*/ 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[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) - end -end - -# Format arguments input -arguments = ARGV.map do |argument| - if argument =~ /^-[^-].*$/i then - argument.split("")[1..-1].map { |char| "-#{char}" } - else - argument - end -end.flatten - -# Parse the provided options -arguments.each_with_index do |argument, index| - unknown_option = true - known_options.each do |known_option| - - # Boolean option - if known_option.in?(argument) and known_option.boolean then - $options[known_option.long] = true - unknown_option = false - break - - # Option with value in next parameter - elsif known_option.in?(argument) and not known_option.boolean then - value = arguments[index + 1] - finish("you must specify a value for #{known_option}") if not value or value.start_with?("-") - value = value.to_i if value =~ /^[0-9]+$/ - $options[known_option.long] = value - unknown_option = false - break - - # Option with value after equal sign - elsif known_option.in_with_value?(argument) and not known_option.boolean then - value = argument.split("=")[1] - value = value.to_i if value =~ /^[0-9]+$/ - $options[known_option.long] = value - unknown_option = false - break - - # Long option with unnecessary value - elsif known_option.in_with_value?(argument) and known_option.boolean then - value = argument.split("=")[1] - finish("#{known_option} does not accept a value (you specified \"#{value}\")") + 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 - # Unrecognized option - finish("unrecognized option \"#{argument}\"") if unknown_option and argument.start_with?("-") -end - -# Help option -if $options[:help] - if BashOutput then - print "printf '" - puts $documentation - puts "'" - puts "exit" - else - puts $documentation - end - exit -1 -end - -# Regular arguments -next_is_value = false -arguments.each do |argument| - if argument.start_with?("-") then - known_option = known_options.find { |known_option| known_option.in?(argument) } - next_is_value = (known_option and not known_option.boolean) - else - $arguments << argument if not next_is_value - next_is_value = false - end -end - -# Bash support -if BashOutput then - $options.keys.each do |name| - puts "#{name}=\"#{$options[name].to_s.sub("true", "yes")}\"" - end - puts "unset arguments" - $arguments.each do |argument| - puts "arguments+=(\"#{argument}\")" - end + # This is supposed to be eventually removed + $documentation = @@parser.documentation + $arguments = @@parser.arguments + $options = @@parser.options end diff --git a/example.rb b/example.rb index ba2b7aa..1fd9991 100644 --- a/example.rb +++ b/example.rb @@ -22,20 +22,20 @@ ## format. require_relative "easyoptions" +options, arguments = EasyOptions.all # Boolean options -puts "Option specified: --some-option" if $options[:option] -puts "Option specified: --some-boolean" if $options[:some_boolean] +puts "Option specified: --some-option" if options[:option] +puts "Option specified: --some-boolean" if options[:some_boolean] # Parameter option -value = $options[:some_value] +value = options[:some_value] if value - value = $options[:some_value] type = value.is_a?(Fixnum)? "number" : "string" puts "Option specified: --some-value is #{value} (a #{type})" end # Arguments -$arguments.each do |argument| +arguments.each do |argument| puts "Argument specified: #{argument}" end