# of lines with a key/value pair seperated by a colon and a white space ': '.
# Each record is separated by a line with three dashes '---'.
class Biopieces
- REGEX_LIST = /^(list|files|files!)$/
- REGEX_INT = /^(int|uint)$/
- REGEX_STRING = /^(string|file|file!|dir|dir!|genome)$/
-
# Initialize a Biopiece and write the status to file.
# Options are for testing purposes only.
- def initialize(no_status=nil,input=STDIN,output=STDOUT)
- status_set unless no_status
+ def initialize(test=nil, input=STDIN, output=STDOUT)
+ @test = test
@input = input
@output = output
+ status_set unless @test
end
# Check the integrity of a list of casts, followed by parsion options from argv
- # and finally checking the options according to the casts. Returns nil if
- # argv is empty, otherwise an options hash.
- def parse(argv,cast_list=[],script_path=$0)
- casts = Casts.new(cast_list)
-
- pp casts
-
- @script_path = script_path
-
- @options = {}
-
- options_template = OptionParser.new do |option|
- casts.each do |cast|
- if cast[:type] == 'flag'
- option.on("-#{cast[:short]}", "--#{cast[:long]}") do |o|
- @options[cast[:long]] = o
- end
- elsif cast[:type] =~ REGEX_LIST
- option.on( "-#{cast[:short]}", "--#{cast[:long]} A", Array) do |a|
- @options[cast[:long]] = a
- end
- elsif cast[:type] =~ REGEX_INT
- option.on("-#{cast[:short]}", "--#{cast[:long]} I", Integer) do |i|
- @options[cast[:long]] = i
- end
- elsif cast[:type] =~ REGEX_STRING
- option.on("-#{cast[:short]}", "--#{cast[:long]} S", String) do |s|
- @options[cast[:long]] = s
- end
- elsif cast[:type] == 'float'
- option.on("-#{cast[:short]}", "--#{cast[:long]} F", Float) do |f|
- @options[cast[:long]] = f
- end
- else
- raise ArgumentError, "Unknown option type: '#{cast[:type]}'"
- end
- end
- end
-
- options_template.parse!(argv)
-
- if print_usage_full?
- print_usage_and_exit(true)
- return # break for unit testing.
- elsif print_usage_short?
- print_usage_and_exit
- return # break for unit testing.
- end
-
- options_default
- options_glob
- options_check
-
- @options
+ # and finally checking the options according to the casts. Returns nil if argv
+ # is empty, otherwise an options hash.
+ def parse(argv, cast_list=[], script_path=$0)
+ casts = Casts.new(cast_list)
+ option_handler = OptionHandler.new(argv, casts, script_path, @test)
+ @options = option_handler.options_parse
end
# Open Biopiece input stream if not open and iterate over all Biopiece
private
- # Given the script name determine the path of the wiki file with the usage info.
- def wiki_path
- path = ENV["BP_DIR"] + "/bp_usage/" + File.basename(@script_path, ".rb") + ".wiki"
- raise "No such wiki file: #{path}" unless File.file? path
-
- path
- end
-
- # Check if full "usage info" should be printed.
- def print_usage_full?
- @options["help"]
- end
-
- # Check if short "usage info" should be printed.
- def print_usage_short?
- if not $stdin.tty?
- return false
- elsif @options["stream_in"]
- return false
- elsif @options["data_in"]
- return false
- elsif wiki_path =~ /^(list_biopieces|list_genomes|list_mysql_databases|biostat)$/ # TODO get rid of this!
- return false
- else
- return true
- end
- end
-
- # Print usage info by Calling an external script 'print_wiki'
- # using a system() call and exit. An optional 'full' flag
- # outputs the full usage info.
- def print_usage_and_exit(full=nil)
- if full
- system("print_wiki --data_in #{wiki_path} --help")
- else
- system("print_wiki --data_in #{wiki_path}")
- end
-
- raise "Failed printing wiki: #{wiki_path}" unless $?.success?
-
- exit
- end
-
- # Set default options value from cast unless a value is set.
- def options_default
- casts.each do |cast|
- if cast[:default]
- @options[cast[:long]] = cast[:default] unless @options.has_key? cast[:long]
- end
- end
- end
-
- # Expands glob expressions to a full list of paths.
- # Examples: "*.fna" or "foo.fna,*.fna" or "foo.fna,/bar/*.fna"
- def options_glob
- casts.each do |cast|
- if cast[:type] == 'files' or cast[:type] == 'files!'
- if @options.has_key? cast[:long]
- files = []
-
- @options[cast[:long]].each do |path|
- if path.include? "*"
- Dir.glob(path).each do |file|
- files << file if File.file? file
- end
- else
- files << path
- end
- end
-
- @options[cast[:long]] = files
- end
- end
- end
- end
-
- # Check all options according to casts.
- def options_check
- casts.each do |cast|
- options_check_mandatory(cast)
- options_check_int(cast)
- options_check_uint(cast)
- options_check_file(cast)
- options_check_files(cast)
- options_check_dir(cast)
- options_check_allowed(cast)
- options_check_disallowed(cast)
- end
- end
-
- # Check if a mandatory option is set and raise if it isn't.
- def options_check_mandatory(cast)
- if cast[:mandatory]
- raise ArgumentError, "Mandatory argument: --#{cast[:long]}" unless @options.has_key? cast[:long]
- end
- end
-
- # Check int type option and raise if not an integer.
- def options_check_int(cast)
- if cast[:type] == 'int' and @options.has_key? cast[:long]
- unless @options[cast[:long]].is_a? Integer
- raise ArgumentError, "Argument to --#{cast[:long]} must be an integer, not '#{@options[cast[:long]]}'"
- end
- end
- end
-
- # Check uint type option and raise if not an unsinged integer.
- def options_check_uint(cast)
- if cast[:type] == 'uint' and @options.has_key? cast[:long]
- unless @options[cast[:long]].is_a? Integer and @options[cast[:long]] >= 0
- raise ArgumentError, "Argument to --#{cast[:long]} must be an unsigned integer, not '#{@options[cast[:long]]}'"
- end
- end
- end
-
- # Check file! type argument and raise if file don't exists.
- def options_check_file(cast)
- if cast[:type] == 'file!' and @options.has_key? cast[:long]
- raise ArgumentError, "No such file: '#{@options[cast[:long]]}'" unless File.file? @options[cast[:long]]
- end
- end
-
- # Check files! type argument and raise if files don't exists.
- def options_check_files(cast)
- if cast[:type] == 'files!' and @options.has_key? cast[:long]
- @options[cast[:long]].each do |path|
- raise ArgumentError, "No such file: '#{path}'" unless File.file? path
- end
- end
- end
-
- # Check dir! type argument and raise if directory don't exist.
- def options_check_dir(cast)
- if cast[:type] == 'dir!' and @options.has_key? cast[:long]
- raise ArgumentError, "No such directory: '#{@options[cast[:long]]}'" unless File.directory? @options[cast[:long]]
- end
- end
-
- # Check options and raise unless allowed.
- def options_check_allowed(cast)
- if cast[:allowed] and @options.has_key? cast[:long]
- allowed_hash = {}
- cast[:allowed].split(',').each { |a| allowed_hash[a] = 1 }
-
- raise ArgumentError, "Argument '#{@options[cast[:long]]}' to --#{cast[:long]} not allowed" unless allowed_hash.has_key? @options[cast[:long]]
- end
- end
-
- # Check disallowed argument values and raise if disallowed.
- def options_check_disallowed(cast)
- if cast[:disallowed] and @options.has_key? cast[:long]
- cast[:disallowed].split(',').each do |val|
- raise ArgumentError, "Argument '#{@options[cast[:long]]}' to --#{cast[:long]} is disallowed" if val == @options[cast[:long]]
- end
- end
- end
-
# Open Biopieces input data stream for reading from either
# stdin or from a list of files specified in options["stream_in"].
def stream_in_open
- if not $stdin.tty?
- p "IN"
- stream = @input
- else
- stream = read(@options["stream_in"])
- end
-
- stream
+ $stdin.tty? ? read(@options["stream_in"]) : @input
end
# Open Biopieces output data stream for writing to stdout
# or a file specified in options["stream_out"].
def stream_out_open
- if @options["stream_out"]
- stream = write(@options["stream_out"], @options["compress"])
- else
- p "OUT"
- stream = @output
- end
-
- stream
+ @options["stream_out"] ? write(@options["stream_out"], @options["compress"]) : @output
end
# Opens a reads stream to a list of files.
def read(files)
- if zipped?(files)
- stream = zread(files)
- else
- stream = nread(files)
- end
-
- stream
+ zipped?(files) ? zread(files) : nread(files)
end
# Opens a write stream to a file and returns a _io_ object.
end
end
+
# Error class for all exceptions to do with option casts.
class CastError < StandardError
end
+
# Class to handle casts of command line options. Each cast prescribes the long and
# short name of the option, the type, if it is mandatory, the default value, and
# allowed and disallowed values. An optional list of extra casts can be supplied,
@cast_list = cast_list
ubiquitous
check
- self << @cast_list
+ self.push *@cast_list
end
+ private
+
# Add ubiquitous options casts.
def ubiquitous
@cast_list << {:long => 'help', :short => '?', :type => 'flag', :mandatory => false, :default => nil, :allowed => nil, :disallowed => nil}
end
end
+
+# Class for parsing argv using OptionParser accordind to given casts.
+# Default options are set, file glob expressions expanded, and options are
+# checked according to the casts. Usage information is printed and exit called
+# if required.
+class OptionHandler
+ REGEX_LIST = /^(list|files|files!)$/
+ REGEX_INT = /^(int|uint)$/
+ REGEX_STRING = /^(string|file|file!|dir|dir!|genome)$/
+
+ def initialize(argv, casts, script_path, test=nil)
+ @argv = argv
+ @casts = casts
+ @script_path = script_path
+ @test = test
+ end
+
+ # Parse options from argv using OptionParser and casts denoting long and
+ # short option names. Usage information is printed and exit called.
+ # A hash with options is returned.
+ def options_parse
+ @options = {}
+
+ option_parser = OptionParser.new do |option|
+ @casts.each do |cast|
+ if cast[:type] == 'flag'
+ option.on("-#{cast[:short]}", "--#{cast[:long]}") do |o|
+ @options[cast[:long]] = o
+ end
+ elsif cast[:type] =~ REGEX_LIST
+ option.on( "-#{cast[:short]}", "--#{cast[:long]} A", Array) do |a|
+ @options[cast[:long]] = a
+ end
+ elsif cast[:type] =~ REGEX_INT
+ option.on("-#{cast[:short]}", "--#{cast[:long]} I", Integer) do |i|
+ @options[cast[:long]] = i
+ end
+ elsif cast[:type] =~ REGEX_STRING
+ option.on("-#{cast[:short]}", "--#{cast[:long]} S", String) do |s|
+ @options[cast[:long]] = s
+ end
+ elsif cast[:type] == 'float'
+ option.on("-#{cast[:short]}", "--#{cast[:long]} F", Float) do |f|
+ @options[cast[:long]] = f
+ end
+ else
+ raise ArgumentError, "Unknown option type: '#{cast[:type]}'"
+ end
+ end
+ end
+
+ option_parser.parse!(@argv)
+
+ if print_usage_full?
+ print_usage_and_exit(true)
+ elsif print_usage_short?
+ print_usage_and_exit
+ end
+
+ options_default
+ options_glob
+ options_check
+
+ @options
+ end
+
+ # Given the script name determine the path of the wiki file with the usage info.
+ def wiki_path
+ path = ENV["BP_DIR"] + "/bp_usage/" + File.basename(@script_path, ".rb") + ".wiki"
+ raise "No such wiki file: #{path}" unless File.file? path
+ path
+ end
+
+ # Check if full "usage info" should be printed.
+ def print_usage_full?
+ @options["help"]
+ end
+
+ # Check if short "usage info" should be printed.
+ def print_usage_short?
+ if not $stdin.tty?
+ return false
+ elsif @options["stream_in"]
+ return false
+ elsif @options["data_in"]
+ return false
+ elsif wiki_path =~ /^(list_biopieces|list_genomes|list_mysql_databases|biostat)$/ # TODO get rid of this!
+ return false
+ else
+ return true
+ end
+ end
+
+ # Print usage info by Calling an external script 'print_wiki'
+ # using a system() call and exit. An optional 'full' flag
+ # outputs the full usage info.
+ def print_usage_and_exit(full=nil)
+ if @test
+ return
+ else
+ if full
+ system("print_wiki --data_in #{wiki_path} --help")
+ else
+ system("print_wiki --data_in #{wiki_path}")
+ end
+
+ raise "Failed printing wiki: #{wiki_path}" unless $?.success?
+
+ exit
+ end
+ end
+
+ # Set default options value from cast unless a value is set.
+ def options_default
+ @casts.each do |cast|
+ if cast[:default]
+ @options[cast[:long]] = cast[:default] unless @options.has_key? cast[:long]
+ end
+ end
+ end
+
+ # Expands glob expressions to a full list of paths.
+ # Examples: "*.fna" or "foo.fna,*.fna" or "foo.fna,/bar/*.fna"
+ def options_glob
+ @casts.each do |cast|
+ if cast[:type] == 'files' or cast[:type] == 'files!'
+ if @options.has_key? cast[:long]
+ files = []
+
+ @options[cast[:long]].each do |path|
+ if path.include? "*"
+ Dir.glob(path).each do |file|
+ files << file if File.file? file
+ end
+ else
+ files << path
+ end
+ end
+
+ @options[cast[:long]] = files
+ end
+ end
+ end
+ end
+
+ # Check all options according to casts.
+ def options_check
+ @casts.each do |cast|
+ options_check_mandatory(cast)
+ options_check_int(cast)
+ options_check_uint(cast)
+ options_check_file(cast)
+ options_check_files(cast)
+ options_check_dir(cast)
+ options_check_allowed(cast)
+ options_check_disallowed(cast)
+ end
+ end
+
+ # Check if a mandatory option is set and raise if it isn't.
+ def options_check_mandatory(cast)
+ if cast[:mandatory]
+ raise ArgumentError, "Mandatory argument: --#{cast[:long]}" unless @options.has_key? cast[:long]
+ end
+ end
+
+ # Check int type option and raise if not an integer.
+ def options_check_int(cast)
+ if cast[:type] == 'int' and @options.has_key? cast[:long]
+ unless @options[cast[:long]].is_a? Integer
+ raise ArgumentError, "Argument to --#{cast[:long]} must be an integer, not '#{@options[cast[:long]]}'"
+ end
+ end
+ end
+
+ # Check uint type option and raise if not an unsinged integer.
+ def options_check_uint(cast)
+ if cast[:type] == 'uint' and @options.has_key? cast[:long]
+ unless @options[cast[:long]].is_a? Integer and @options[cast[:long]] >= 0
+ raise ArgumentError, "Argument to --#{cast[:long]} must be an unsigned integer, not '#{@options[cast[:long]]}'"
+ end
+ end
+ end
+
+ # Check file! type argument and raise if file don't exists.
+ def options_check_file(cast)
+ if cast[:type] == 'file!' and @options.has_key? cast[:long]
+ raise ArgumentError, "No such file: '#{@options[cast[:long]]}'" unless File.file? @options[cast[:long]]
+ end
+ end
+
+ # Check files! type argument and raise if files don't exists.
+ def options_check_files(cast)
+ if cast[:type] == 'files!' and @options.has_key? cast[:long]
+ @options[cast[:long]].each do |path|
+ raise ArgumentError, "No such file: '#{path}'" unless File.file? path
+ end
+ end
+ end
+
+ # Check dir! type argument and raise if directory don't exist.
+ def options_check_dir(cast)
+ if cast[:type] == 'dir!' and @options.has_key? cast[:long]
+ raise ArgumentError, "No such directory: '#{@options[cast[:long]]}'" unless File.directory? @options[cast[:long]]
+ end
+ end
+
+ # Check options and raise unless allowed.
+ def options_check_allowed(cast)
+ if cast[:allowed] and @options.has_key? cast[:long]
+ allowed_hash = {}
+ cast[:allowed].split(',').each { |a| allowed_hash[a] = 1 }
+
+ raise ArgumentError, "Argument '#{@options[cast[:long]]}' to --#{cast[:long]} not allowed" unless allowed_hash.has_key? @options[cast[:long]]
+ end
+ end
+
+ # Check disallowed argument values and raise if disallowed.
+ def options_check_disallowed(cast)
+ if cast[:disallowed] and @options.has_key? cast[:long]
+ cast[:disallowed].split(',').each do |val|
+ raise ArgumentError, "Argument '#{@options[cast[:long]]}' to --#{cast[:long]} is disallowed" if val == @options[cast[:long]]
+ end
+ end
+ end
+end
+
+
__END__
# >>>>>>>>>>>>>>>>>>>> Testing Options.new <<<<<<<<<<<<<<<<<<<<
- test "Biopieces#parse with all cast keys don't raise" do
- argv = []
- casts = [{:long=>"foo", :short=>"f", :type=>"int", :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}]
- @bp.expects(:print_usage_and_exit).with()
- assert_nothing_raised(CastError) { @bp.parse(argv, casts, SCRIPT_PATH) }
- end
+ test "Biopieces#parse with all cast keys don't raise" do
+ argv = []
+ casts = [{:long=>"foo", :short=>"f", :type=>"int", :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}]
+ assert_nothing_raised(CastError) { @bp.parse(argv, casts, SCRIPT_PATH) }
+ end
test "Biopieces#parse with illegal long cast values raises" do
[nil, true, false, 1, 0, "a"].each do |long|
["foo", "!!", "0123"].each do |long|
argv = []
casts = [{:long=>long, :short=>"f", :type=>"int", :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}]
- @bp.expects(:print_usage_and_exit).with()
assert_nothing_raised(CastError) { @bp.parse(argv, casts, SCRIPT_PATH) }
end
end
["!", "1", "a"].each do |short|
argv = []
casts = [{:long=>"foo", :short=>short, :type=>"int", :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}]
- @bp.expects(:print_usage_and_exit).with()
assert_nothing_raised(CastError) { @bp.parse(argv, casts, SCRIPT_PATH) }
end
end
TYPES.each do |type|
argv = []
casts = [{:long=>"foo", :short=>"f", :type=>type, :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}]
- @bp.expects(:print_usage_and_exit).with()
assert_nothing_raised(CastError) { @bp.parse(argv, casts, SCRIPT_PATH) }
end
end
test "Biopieces#parse with legal mandatory cast values don't raise" do
[true, false].each do |mandatory|
- argv = []
+ argv = [ "--foo", "1" ]
casts = [{:long=>"foo", :short=>"f", :type=>"int", :mandatory=>mandatory, :default=>nil, :allowed=>nil, :disallowed=>nil}]
- @bp.expects(:print_usage_and_exit).with()
assert_nothing_raised(CastError) { @bp.parse(argv, casts, SCRIPT_PATH) }
end
end
end
test "Biopieces#parse with legal default cast values don't raise" do
- ["foo", nil, 0, 0.0, 1, 1.2].each do |default|
+ [nil, 0, 1, -1].each do |default|
argv = []
casts = [{:long=>"foo", :short=>"f", :type=>"int", :mandatory=>false, :default=>default, :allowed=>nil, :disallowed=>nil}]
- @bp.expects(:print_usage_and_exit).with()
assert_nothing_raised(CastError) { @bp.parse(argv, casts, SCRIPT_PATH) }
end
end
["foo,bar",nil].each do |allowed|
argv = []
casts = [{:long=>"foo", :short=>"f", :type=>"int", :mandatory=>false, :default=>nil, :allowed=>allowed, :disallowed=>nil}]
- @bp.expects(:print_usage_and_exit).with()
assert_nothing_raised(CastError) { @bp.parse(argv, casts, SCRIPT_PATH) }
end
end
["foo,bar",nil].each do |disallowed|
argv = []
casts = [{:long=>"foo", :short=>"f", :type=>"int", :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>disallowed}]
- @bp.expects(:print_usage_and_exit).with()
assert_nothing_raised(CastError) { @bp.parse(argv, casts, SCRIPT_PATH) }
end
end
casts = []
casts << {:long=>"foo", :short=>"f", :type=>"int", :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}
casts << {:long=>"bar", :short=>"b", :type=>"int", :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}
- @bp.expects(:print_usage_and_exit).with()
assert_nothing_raised(CastError) { @bp.parse(argv, casts, SCRIPT_PATH) }
end
test "Biopieces#parse with empty argv and existing wiki file don't raise" do
argv = []
casts = []
- @bp.expects(:print_usage_and_exit).with()
assert_nothing_raised { @bp.parse(argv, casts, SCRIPT_PATH) }
end
test "Biopieces#parse with --help in argv and existing wiki output long usage" do
argv = ["--help"]
- @bp.expects(:print_usage_and_exit).with(true)
assert_nothing_raised { @bp.parse(argv,[],SCRIPT_PATH) }
end