#### libremiliacr
#### Copyright(C) 2020-2024 Remilia Scarlet <remilia@posteo.jp>
####
#### This program is free software: you can redistribute it and/or modify it
#### under the terms of the GNU General Public License as published the Free
#### Software Foundation, either version 3 of the License, or (at your option)
#### any later version.
####
#### This program is distributed in the hope that it will be useful, but WITHOUT
#### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
#### FITNESS FOR A PARTICULAR PURPOSE.See the GNU General Public License for
#### more details.
####
#### You should have received a copy of the GNU General Public License along
#### with this program.If not, see<http:####www.gnu.org/licenses/.>
require "../strings"
require "./arg-types"
module RemiLib::Args
# Represents an error that occurs during argument parsing.
class ArgumentError < Exception
end
# An `ArgCallbackFunc` is a proc that will be called at the end of parsing if
# the argument was called.
alias ArgCallbackFunc = ArgParser, Argument -> Nil
# This is used by the `ArgParser` class when a help argument
# (e.g. `"--help"`/`"-h"`) is found on the command line.
alias HelpPrinterFunc = ArgParser -> Nil
# This is used by the `ArgParser` class when a version argument
# (e.g. `"--version"`/`"-V"`) is found on the command line.
alias VerPrinterFunc = ArgParser -> Nil
##############################################################################
# The main class for parsing command line arguments.
class ArgParser
# The `Argument`s defined for this parser. Do not add arguments directly to
# this field - use one of the `add*` methods instead.
getter args : Hash(String, Argument) = {} of String => Argument
@argsSN : Hash(String, Argument) = {} of String => Argument
# Any non-argument strings left on the command line after parsing.
property positionalArgs : Array(String) = Array(String).new(1)
# This will be called when a help argument (e.g. `"--help"`/`"-h"`) is found
# on the command line.
property helpPrinter : HelpPrinterFunc = ->ArgParser.defaultHelpPrinter(ArgParser)
# This will be called when a version argument (e.g. `"--version"`/`"-V"`) is
# found on the command line.
property verPrinter : VerPrinterFunc = ->ArgParser.defaultVerPrinter(ArgParser)
# The name of the program using this parser. Used when a version argument
# (e.g. `"--version"`/`"-V"`) is found on the command line.
property progName : String
# The version of the program using this parser. Used when a version
# argument (e.g. `"--version"`/`"-V"`) is found on the command line.
property progVersion : String
# When non-nil, this will be used for the "Usage:" line instead of
# the generated one.
property usageLine : String? = nil
# A string that is to be printed after the "Usage:" line but before the list
# of arguments when a help argument (e.g. `"--help"`/`"-h"`) is found on the
# command line. A newline will be appended automatically.
property preHelpText : String = ""
# A string that is to be printed after the list of arguments when a help
# argument (e.g. `"--help"`/`"-h"`) is found on the command line. A newline
# will be appended automatically.
property postHelpText : String = ""
# A string that is to be printed before version information when a version
# argument (e.g. `"--version"`/`"-V"`) is found on the command line. A
# newline will be appended automatically.
property preVerText : String = ""
# A string that is to be printed after the version information when a
# version argument (e.g. `"--version"`/`"-V"`) is found on the command line.
# A newline will be appended automatically.
property postVerText : String = ""
# When `true`, `#parse` will call `exit(0)` after printing help when a help
# argument (e.g. `"--help"`/`"-h"`) is found on the command line.
property? quitOnHelp : Bool = true
# When `true`, `#parse` will call `exit(0)` after printing version
# information when a version argument (e.g. `"--version"`/`"-V"`) is found
# on the command line.
property? quitOnVer : Bool = true
# When `true`, the help argument (e.g. `"--help"`/`"-h"`) will be processed.
# Otherwise it is ignored.
property? detectHelp : Bool = true
# When `true`, the version argument (e.g. `"--version"`/`"-V"`) will be
# processed. Otherwise it is ignored.
property? detectVer : Bool = true
# The name of the binary using this `ArgParser`.
getter progBinName : String
# When true, the double dash `--` will be treated as a positional argument.
property? doubleDashPositional : Bool = false
# When true, a single dash `-` will be and stored as a positional argument.
property? singleDashPositional : Bool = false
# :nodoc:
protected getter argv0 : String
# Creates a new `ArgParser` instance. A help argument and a version
# argument will always be added as `FlagArgument`s.
def initialize(@progName : String, @progVersion : String,
*, @helpLongName : String = "--help", @helpShortName : Char = 'h',
@verLongName : String = "--version", @verShortName : Char = 'V',
newProgBinName : String = PROGRAM_NAME, newArgv0 : String = PROGRAM_NAME)
harg : FlagArgument = FlagArgument.new(@helpLongName, @helpShortName, help: "Show this help text")
addArg(harg)
varg : FlagArgument = FlagArgument.new(@verLongName, @verShortName, help: "Show version information")
addArg(varg)
@progBinName = newProgBinName
@argv0 = newArgv0
end
# Sets the name of the binary using this `ArgParser`.
@[AlwaysInline]
def progBinName=(newName : String)
@progBinName = newName
@argv0 = newName
end
# Adds a new `Argument` to this parser. The argument must have a
# `Argument#longName`. Arguments with duplicate `Argument#longName`s and
# `Argument#shortName`s will raise an `Exception`.
def addArg(arg : Argument)
raise "Invalid long name: empty strings not allowed" if arg.longName.strip.empty?
arg.called = false
if @args.has_key?(arg.longName)
raise "Duplicate argument: '#{arg.longName}'"
else
@args[arg.longName] = arg
end
if !arg.shortName.nil?
raise "Duplicate argument (short name): '#{arg.longName}' ('-#{arg.shortName}')" if @argsSN.has_key?("#{arg.shortName}")
@argsSN["#{arg.shortName}"] = arg
end
end
# Adds a new `FlagArgument`.
@[AlwaysInline]
def addFlag(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "") : FlagArgument
newArg = FlagArgument.new(longName, shortName, group, help)
addArg(newArg)
newArg
end
# :ditto:
def addFlag(longName : String, shortName : Char? = nil, &) : Nil
yield addFlag(longName, shortName)
end
# Adds a new `MultiFlagArgument`.
@[AlwaysInline]
def addMultiFlag(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "") : MultiFlagArgument
newArg = MultiFlagArgument.new(longName, shortName, group, help)
addArg(newArg)
newArg
end
# :ditto:
def addMultiFlag(longName : String, shortName : Char? = nil, &) : Nil
yield addMultiFlag(longName, shortName)
end
# Adds a new `StringArgument`.
@[AlwaysInline]
def addString(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "", default : String = "",
constraints : Array(String)? = nil) : StringArgument
newArg = StringArgument.new(longName, shortName, group, help)
newArg.setValue!(default)
addArg(newArg)
newArg.oneOf = constraints unless constraints.nil?
newArg
end
# :ditto:
def addString(longName : String, shortName : Char? = nil, *, group : String = "", &) : Nil
yield addString(longName, shortName, group: group)
end
# Adds a new `MultiStringArgument`.
@[AlwaysInline]
def addMultiString(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "",
default : Array(String) = [] of String, constraints : Array(String)? = nil) : MultiStringArgument
newArg = MultiStringArgument.new(longName, shortName, group, help)
newArg.setValues!(default)
addArg(newArg)
newArg.oneOf = constraints unless constraints.nil?
newArg
end
# :ditto:
def addMultiString(longName : String, shortName : Char? = nil, *, group : String = "", &) : Nil
yield addMultiString(longName, shortName, group: group)
end
# Adds a new `IntArgument`.
@[AlwaysInline]
def addInt(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "", default : Int = 0,
minimum : Int = Int64::MIN, maximum : Int = Int64::MAX) : IntArgument
newArg = IntArgument.new(longName, shortName, group, help)
newArg.setValue!(default.to_i64)
newArg.minimum = minimum.to_i64
newArg.maximum = maximum.to_i64
addArg(newArg)
newArg
end
# :ditto:
def addInt(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "", default : Int = 0,
minimum : Int = Int64::MIN, maximum : Int = Int64::MAX, &) : Nil
yield addInt(longName, shortName, default: default, group: group, help: help,
minimum: minimum, maximum: maximum)
end
# Adds a new `FloatArgument`.
@[AlwaysInline]
def addFloat(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "", default : Float64 = 0.0,
minimum : Float64 = Float64::MIN, maximum : Float64 = Float64::MAX) : FloatArgument
newArg = FloatArgument.new(longName, shortName, group, help)
newArg.setValue!(default)
newArg.minimum = minimum
newArg.maximum = maximum
addArg(newArg)
newArg
end
# :ditto:
def addFloat(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "", default : Float64 = 0.0,
minimum : Float64 = Float64::MIN, maximum : Float64 = Float64::MAX, &) : Nil
yield addFloat(longName, shortName, default: default, group: group, help: help,
minimum: minimum, maximum: maximum)
end
# Adds a new `MultiIntArgument`.
@[AlwaysInline]
def addMultiInt(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "",
default : Array(Int64) = [] of Int64) : MultiIntArgument
newArg = MultiIntArgument.new(longName, shortName, group, help)
newArg.values = default
addArg(newArg)
newArg
end
# :ditto:
def addMultiInt(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "",
default : Array(Int64) = [] of Int64, &) : Nil
yield addMultiInt(longName, shortName, group: group, help: help, default: default)
end
# Adds a new `MultiIntArgument`.
@[AlwaysInline]
def addMultiFloat(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "",
default : Array(Float64) = [] of Float64) : MultiIntArgument
newArg = MultiFloatArgument.new(longName, shortName, group, help)
newArg.values = default
addArg(newArg)
newArg
end
# :ditto:
def addMultiInt(longName : String, shortName : Char? = nil,
*, group : String = "", help : String = "",
default : Array(Float64) = [] of Float64, &) : Nil
yield addMultiFloat(longName, shortName, group: group, help: help, default: default)
end
# Returns an array of Strings containing all fo the argument groups. Note
# that the default argument group is an empty string.
@[AlwaysInline]
def allArgGroups : Array(String)
ret : Array(String) = [] of String
@args.each_value { |val| ret << val.group }
ret.uniq
end
# Returns an array of `Argument`s that are in the given group.
@[AlwaysInline]
def allArgsInGroup(group : String) : Array(Argument)
ret : Array(Argument) = [] of Argument
@args.each_value { |val| ret << val if val.group == group }
ret
end
# Returns the length of the longest `Argument#longName` found in `#args`.
@[AlwaysInline]
def longestArgName : Int32
longest : Int32 = 0
@args.each_key { |key| longest = key.size if key.size > longest }
longest
end
# :nodoc:
@[AlwaysInline]
protected def handleArgument(arg : Argument|MultiArgument|ValArgument) : Bool
if arg.responds_to?(:times)
arg.times = arg.times + 1
else
raise ArgumentError.new("#{arg.longName} cannot be called more than once") if arg.called?
end
arg.called = true
arg.responds_to?(:value) || arg.responds_to?(:<<)
end
# :nodoc:
private enum Pstate
ReadArg
ReadToken
ReadPositional
end
# Executes a block on every argument that was `Argument#called`.
def eachCalled(&) : Nil
@args.each_value { |arg| yield arg if arg.called? }
end
# Executes a block on every argument.
def each(&) : Nil
@args.each_value { |arg| yield arg }
end
# If an argument named `name` was called, then this yields that argument to
# the block. Otherwise this does nothing. This returns either the result
# of the block, or nil if it wasn't called.
def withCalledArg(name : String, &)
arg : Argument = self.[name]
if arg.called?
yield arg
else
nil
end
end
# Looks up the argument named `name`, casts it to an `FlagArgument`, then
# yields it to the block. If the argument is not an `FlagArgument`, this
# raises an exception. Returns the last value of the block.
def withFlagArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(FlagArgument)
yield arg.as(FlagArgument)
else
raise "Attempted to call ArgParser#withFlagArg on an argument that is not an FlagArgument"
end
end
# Looks up the argument named `name`, casts it to an `MultiFlagArgument`,
# then yields it to the block. If the argument is not an
# `MultiFlagArgument`, this raises an exception. Returns the last value of
# the block.
def withMultiFlagArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(MultiFlagArgument)
yield arg.as(MultiFlagArgument)
else
raise "Attempted to call ArgParser#withFlagArg on an argument that is not an FlagArgument"
end
end
# Looks up the argument named `name`, casts it to an `StringArgument`, then
# yields it to the block. If the argument is not an `StringArgument`, this
# raises an exception. Returns the last value of the block.
def withStringArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(StringArgument)
yield arg.as(StringArgument)
else
raise "Attempted to call ArgParser#withStringArg on an argument that is not an StringArgument"
end
end
# Looks up the argument named `name`, casts it to an `MultiStringArgument`,
# then yields it to the block. If the argument is not an
# `MultiStringArgument`, this raises an exception. Returns the last value
# of the block.
def withMultiStringArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(MultiStringArgument)
yield arg.as(MultiStringArgument)
else
raise "Attempted to call ArgParser#withMultiStringArg on an argument that is not an MultiStringArgument"
end
end
# Looks up the argument named `name`, casts it to an `IntArgument`, then
# yields it to the block. If the argument is not an `IntArgument`, this
# raises an exception. Returns the last value of the block.
def withIntArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(IntArgument)
yield arg.as(IntArgument)
else
raise "Attempted to call ArgParser#withIntArg on an argument that is not an IntArgument"
end
end
# Looks up the argument named `name`, casts it to a `MultiIntArgument`, then
# yields it to the block. If the argument is not a `MultiIntArgument`, this
# raises an exception. Returns the last value of the block.
def withMultiIntArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(MultiIntArgument)
yield arg.as(MultiIntArgument)
else
raise "Attempted to call ArgParser#withMultiIntArg on an argument that is not a " \
"MultiIntArgument"
end
end
# Looks up the argument named `name`, casts it to a `FloatArgument`, then
# yields it to the block. If the argument is not a `FloatArgument`, this
# raises an exception. Returns the last value of the block.
def withFloatArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(FloatArgument)
yield arg.as(FloatArgument)
else
raise "Attempted to call ArgParser#withFloatArg on an argument that is not a FloatArgument"
end
end
# Looks up the argument named `name`, casts it to a `MultiFloatArgument`,
# then yields it to the block. If the argument is not a
# `MultiFloatArgument`, this raises an exception. Returns the last value of
# the block.
def withMultiFloatArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(MultiFloatArgument)
yield arg.as(MultiFloatArgument)
else
raise "Attempted to call ArgParser#withMultiFloatArg on an argument that is not a " \
"MultiFloatArgument"
end
end
# Looks up the argument named `name`, casts it to an `StringArgument`. If
# the argument was called, this yields it to the block, otherwise it does
# nothing. If the argument is not an `StringArgument`, this raises an
# exception. This returns either the result of the block, or nil if it
# wasn't called.
def withCalledStringArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(StringArgument)
if arg.called?
yield arg.as(StringArgument)
else
nil
end
else
raise "Attempted to call ArgParser#withStringArg on an argument that is not an StringArgument"
end
end
# Looks up the argument named `name`, casts it to an `MultiStringArgument`.
# If the argument was called, this yields it to the block, otherwise it does
# nothing. If the argument is not an `MultiStringArgument`, this raises an
# exception. This returns either the result of the block, or nil if it
# wasn't called.
def withCalledMultiStringArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(MultiStringArgument)
if arg.called?
yield arg.as(MultiStringArgument)
else
nil
end
else
raise "Attempted to call ArgParser#withMultiStringArg on an argument that is not an MultiStringArgument"
end
end
# Looks up the argument named `name`, casts it to an `IntArgument`. If the
# argument was called, this yields it to the block, otherwise it does
# nothing. If the argument is not an `IntArgument`, this raises an
# exception. This returns either the result of the block, or nil if it
# wasn't called.
def withCalledIntArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(IntArgument)
if arg.called?
yield arg.as(IntArgument)
else
nil
end
else
raise "Attempted to call ArgParser#withCalledIntArg on an argument that is not an IntArgument"
end
end
# Looks up the argument named `name`, casts it to a `MultiIntArgument`. If
# the argument was called, this yields it to the block, otherwise it does
# nothing. If the argument is not a `MultiIntArgument`, this raises an
# exception. This returns either the result of the block, or nil if it
# wasn't called.
def withCalledMultiIntArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(MultiIntArgument)
if arg.called?
yield arg.as(MultiIntArgument)
else
nil
end
else
raise "Attempted to call ArgParser#withCalledMultiIntArg on an argument that is not a " \
"MultiIntArgument"
end
end
# Looks up the argument named `name`, casts it to a `FloatArgument`. If the
# argument was called, this yields it to the block, otherwise it does
# nothing. If the argument is not a `FloatArgument`, this raises an
# exception. This returns either the result of the block, or nil if it
# wasn't called.
def withCalledFloatArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(FloatArgument)
if arg.called?
yield arg.as(FloatArgument)
else
nil
end
else
raise "Attempted to call ArgParser#withFloatArg on an argument that is not a FloatArgument"
end
end
# Looks up the argument named `name`, casts it to a `MultiFloatArgument`.
# If the argument was called, this yields it to the block, otherwise it does
# nothing. If the argument is not a `MultiFloatArgument`, this raises an
# exception. This returns either the result of the block, or nil if it
# wasn't called.
def withCalledMultiFloatArg(name : String, &)
arg : Argument = self.[name]
if arg.is_a?(MultiFloatArgument)
if arg.called?
yield arg.as(MultiFloatArgument)
else
nil
end
else
raise "Attempted to call ArgParser#withMultiFloatArg on an argument that is not a " \
"MultiFloatArgument"
end
end
# Returns the `Argument` that has the given `Argument#longName`. The `"--"`
# prefix is optional.
@[AlwaysInline]
def [](name : String) : Argument
name.starts_with?("--") ? @args[name] : @args["--#{name}"]
end
# Returns the `Argument` that has the given `Argument#shortName`.
def [](name : Char) : Argument
@argsSN[name]
end
# Parses `ARGV`.
#
# If a help argument or version argument is found on the command line, and
# `#detectHelp`/`#detectVer` is true, then the corresponding
# `helpPrinter`/`#verPrinter` will be called.
#
# If a help argument or version argument is found on the command line, and
# `#quitOnHelp`/`#quitOnVer` is true, then `exit(0)` will be called after
# executing the corresponding method.
#
# After parsing, any argument that has a `Argument#callback` method will
# have that method executed.
def parse : Nil
parse(ARGV)
end
# Parses an array containing command line arguments.
#
# If a help argument or version argument is found on the command line, and
# `#detectHelp`/`#detectVer` is true, then the corresponding
# `helpPrinter`/`#verPrinter` will be called.
#
# If a help argument or version argument is found on the command line, and
# `#quitOnHelp`/`#quitOnVer` is true, then `exit(0)` will be called after
# executing the corresponding method.
#
# After parsing, any argument that has a `Argument#callback` method will
# have that method executed.
def parse(theseArgs : Array(String)) : Nil
curArg : Argument = FlagArgument.new("foo", help: "foo")
state : Pstate = Pstate::ReadArg
return if theseArgs.size == 0
theseArgs.each do |argStr|
case state
when Pstate::ReadPositional
@positionalArgs << argStr
next
when Pstate::ReadArg
# Check for a long argument first
if argStr.starts_with?("--")
if argStr == "--" && @doubleDashPositional
state = Pstate::ReadPositional
elsif !@args.has_key?(argStr)
raise ArgumentError.new("Invalid argument: #{argStr}")
else
curArg = @args[argStr]
state = Pstate::ReadToken if self.handleArgument(curArg)
end
# Now check for a short argument
elsif argStr.starts_with?("-")
if argStr == "-"
if @singleDashPositional
@positionalArgs << argStr
next
else
raise ArgumentError.new("Invalid argument: -")
end
end
argSub : String = argStr[1..]
shortArg : Argument? = @argsSN[argSub]?
if shortArg.nil?
# There's a possibility this is a positional argument
# that's also a negative number. Check that now.
if argStr.to_i64? != nil || argStr.to_f64? != nil
@positionalArgs << argStr
next
else
raise ArgumentError.new("Invalid argument: #{argStr}")
end
end
curArg = shortArg
state = Pstate::ReadToken if self.handleArgument(curArg)
else # This is a positional argument
@positionalArgs << argStr
end
when Pstate::ReadToken
# Check to see if this "value" is actually an argument
# (meaning the user forgot to pass a value to curArg)
raise ArgumentError.new("#{curArg.longName} expects a value passed to it") if @args.has_key?(argStr)
case
when curArg.responds_to?(:value) then curArg.value = argStr
when curArg.responds_to?(:<<) then curArg << argStr
else raise "I don't know how to set an argument of type #{typeof(curArg)}"
end
state = Pstate::ReadArg
end
end # .each do
raise ArgumentError.new("#{curArg.longName} expects a value passed to it") if state == Pstate::ReadToken
if @detectHelp && @args[@helpLongName].called?
@helpPrinter.call(self)
exit(0) if @quitOnHelp
end
if @detectVer && @args[@verLongName].called?
@verPrinter.call(self)
exit(0) if @quitOnVer
end
eachCalled do |arg|
arg.callback.try(&.call(self, arg))
end
end # parse
# The default printer for help arguments (e.g. `"--help"`/`"-h"`).
#
# The default output looks something like this (depending on the values of
# `#preHelpText` and `#postHelpText`:
#
# ```
# Usage: /path/to/binary [options]
# General Options
# ================================================================================
# --help / -h : Show this help text
# --version / -V : Show version information
# --foo x / -f x : Adds a foo
# ```
def self.defaultHelpPrinter(parser : ArgParser) : Nil
finalStr : String = String.build do |str|
longestLong : Int32 = parser.longestArgName
if parser.usageLine.nil?
str << "Usage: #{parser.argv0} [options]"
else
str << parser.usageLine
end
unless parser.preHelpText.empty?
str << '\n' << parser.preHelpText << '\n'
end
isFlag : Bool = false
parser.allArgGroups.each do |group|
group.empty? ? str << "\nGeneral Options\n" : str << "\n#{group}\n"
str << "================================================================================\n"
parser.allArgsInGroup(group).each do |arg|
str << "#{arg.longName}"
if arg.is_a?(FlagArgument) || arg.is_a?(MultiFlagArgument)
isFlag = true
str << " "
else
str << " x"
end
str << " " * (longestLong - arg.longName.size + 1)
if !arg.shortName.nil?
str << " / -#{arg.shortName}"
isFlag ? str << " : " : str << " x : "
else
str << " : "
end
RemiLib.buildWrapped(arg.help, str, indent: longestLong + 13)
str << '\n'
isFlag = false
end
end
unless parser.postHelpText.empty?
str << '\n' << parser.postHelpText << '\n'
end
end
STDOUT << finalStr
end
# The default printer for version arguments (e.g. `"--version"`/`"-V"`).
# The default output looks something like this (depending on the values of
# `#preVerText` and `#postVerText`:
#
# ```
# My Program v0.1.0
# ```
def self.defaultVerPrinter(parser : ArgParser) : Nil
finalStr : String = String.build do |str|
str << parser.preVerText << '\n' if parser.preVerText != ""
str << "#{parser.progName} v#{parser.progVersion}\n"
str << parser.postVerText << '\n' if parser.postVerText != ""
end
STDOUT << finalStr
end
end # ArgParser
end