Merge pull request #339 from bucaran/getopts

~ fish getopts plugin ~
This commit is contained in:
Bruno 2015-01-23 15:12:50 -02:00
commit 845ddfdca1
2 changed files with 551 additions and 0 deletions

View File

@ -0,0 +1,409 @@
# NAME
# getopts -- getopts for fish
#
# SYNOPSIS
# getopts <option string> [ARGV...]
#
# OPTIONS
# <option string> [:][<letter>][[:]<word>[:[^]]]
#
# A string containing the option characters recognized by the utility
# calling getopts. If a <letter> or <word> ends in `:`, the option is
# expected to have an argument, which may be supplied separately or
# next to the option without spaces in the same string.
#
# To indicate long options: <letter>:<word> and <word> are both valid
# option strings that will attempt to match -<letter> and --<word>.
#
# For only short options, do not specify a :<word> after <letter>.
# For example, `a b` will match `-a` and/or `-b`.
#
# To indicate optional arguments, use a `^` character after a `:` at
# the end of the option in the option string. For example <letter>:^
# and <letter>:<word>:^ are both valid. Optional arguments should be
# supplied in the same string as the option and without spaces, e.g,
# -<letter>value will correctly assign `value` as the argument to the
# the option <letter>, but -<letter> value, will parse `value` as the
# next argument in <argv>.
#
# To specify optional arguments using the option's long form, use a
# `=` character after the option: --<word>=value.
#
# Use a `:` at the beginning of the option string to enable strict
# mode. If enabled, getopts will exit with a status > 0 if or when
# an unknown option is found. See DIAGNOSTICS.
#
# <ARGV>
#
# List of options and operands to parse. getopts prints any matched
# options as well as available argument separated by a \n to stdout
# and returns with a status of 0 if there are still arguments; else
# returns with a status > 0 if the end of the options is reached or
# an error occurs. See DIAGNOSTICS.
#
# DESCRIPTION
# getopts obtains options and their arguments from a list of parameters
# that, as indicated by <option string>, are single letters preceded by
# a - or words preceded by -- and possibly followed by an argument value.
#
# fish getopts follows the specifications described in the Utility Syntax
# Guidelines (see LINKS); the following is a summary of the features:
#
# + Short options; single letters preceded by -, and long options;
# words preceded by --, are both supported.
#
# + Single letters may be grouped. -abc-a -b -c
#
# + Options required to take an argument can specify the argument
# either in the same string as the option or separated from the
# by a space. (1) -a argument, (2) -aargument
#
# + Options that can take an argument optionally shall specify the
# argument in the same string as the option argument if in short
# option style: -aargument, or separated by a = if in long form:
# --long-form=argument. If a blank space is used, the following
# argument will be treated independently.
#
# + Options can appear multiple times in the same argument list.
# getopts will print every match sequentally on each call, and
# should default to the short form of the option if available.
#
# + The option delimiter `:` and optional argument character `^`
# shall not be used as an option.
#
# + getopts will return the remaining operands when the end of the
# options is reached, i.e, a `--` argument that is not an option
# is found, or an argument that does not begin with `-` is found.
#
# ENVIRONMENT VARIABLES
# The following environment variables are used internally by getopts.
# These variables are erased from memory when the functions returns 1.
#
# + __getopts_optstr Whitespace trimmed option string.
# + __getopts_argv Preprocessed copy of arguments.
# + __getopts_index Index of the next argument to handle.
# + __getopts_required List of options with required arguments.
# + __getopts_optional List of options with optional arguments.
#
# DIAGNOSTICS
# Possible exit status values are:
#
# 0 An argument formed like an option was found. This causes getopts
# to print the option short-style and its argument if avaiable. If
# strict-mode is enabled setting the first character of the option
# to `:`, an unknown option will cause getopts to fail. See below.
#
# 1 The end of the options was reached. Remaining operands are also
# sent to stdout.
#
# 2 An option argument was missing.
#
# 3 An unknown option was found. Only if strict-mode is enabled.
#
# EXAMPLES
# function my_utility
# while set optarg (getopts "l:long x: o:optional:^" $argv)
# switch $optarg[1]
# case l
# echo handle `-l --long`
# case x
# echo handle `-x` w/ argument `$optarg[2]`
# case o
# echo handle `-o --optional` w/ optional argument `$optarg[2]`
# case \*
# echo unknown option `$optarg[1]`
# end
# end
# echo -n operands: "`$optarg`"
# end
#
# LINKS
# UNIX Utility Conventions
# → http://pubs.opengroup.org/onlinepubs/7908799/xbd/utilconv.html
#
# AUTHORS
# Jorge Bucaran <@bucaran>
#/
function getopts
# Currently supported return success/error conditions.
set -l __CONTINUE 0
set -l __END_OF_OPTIONS 1
set -l __OPTION_ARGUMENT_EXPECTED 2
set -l __UNKNOWN_OPTION_FOUND 3
# Self-destroying global variable cleanup utility.
# Should be called before returning 1.
function __getopts_cleanup
set -e __getopts_optstr
set -e __getopts_argv
set -e __getopts_index
set -e __getopts_required
set -e __getopts_optional
set -e __getopts_strict_mode
functions -e __getopts_increase_index
end
function __getopts_increase_index
set __getopts_index (math $__getopts_index + 1)
end
# Options string pre-processing.
if not set -q __getopts_optstr
set -g __getopts_optstr $argv[1]
set -e argv[1]
# Trim option string and collect required / optional options.
if [ -n "$__getopts_optstr" ]
set __getopts_optstr (printf $__getopts_optstr | tr '[:space:]' \n)
# Setting the first token of the option string to `:` enables
# strict mode. This causes getopts to abort the process if an
# unknown option is found.
set -l first_token (printf $__getopts_optstr | head -c1)
if [ : = "$first_token" ]
set -g __getopts_strict_mode --true
# We can safely remove the `:` character now.
set __getopts_optstr[1] (printf $__getopts_optstr[1] | cut -c2-)
end
# Collect options with optional and required option-arguments.
function __getopts_collect_optargs
for string in (printf "%s\n" $__getopts_optstr)
set -l token (printf $string | tail -c1)
set -l split_string (printf "%s\n" $string | tr : \n)
# Erase last to make sure not to append `^` token.
switch $token
case :
set -g __getopts_required $__getopts_required $split_string
set -e __getopts_required[-1]
case \^
set -g __getopts_optional $__getopts_optional $split_string
set -e __getopts_optional[-1]
end
end
functions -e __getopts_collect_optargs
end
__getopts_collect_optargs
end
end
# Sanitize arguments. Break up flags: -abc-a -b -c, but skip optional
# arguments. If -w is a flag that can take an optional argument, -abw123
# should be parsed to → -a -b -w 123.
if not set -q __getopts_argv
function __getopts_sanitize_argv
for token in $argv
switch $token
case --\* # Skip!
case -\*
# Split each token into single characters with `.`
for char in (printf $token | cut -c2- | grep --only-matching .)
# Do not split short option characters if this option can
# take optional required arguments.
if [ -z "$suspend_break" ]
printf "-%s\n" $char
else
printf "%s" $char
end
# Suspend option break-up if the current option is either
# a required or an optional option type.
contains -- $char $__getopts_required $__getopts_optional
and set suspend_break --true
set last_char $char
end
# Break options if we were in suspend mode and reset flag.
# This makes sure to add a blank space if no argument was
# specified for the optional option-argument, but not for
# the required option-argument.
if [ -n "$suspend_break" ]
not contains -- $last_char $__getopts_required
and printf "\n"
set suspend_break ""
end
continue
end
printf "%s\n" $token
end
functions -e __getopts_sanitize_argv
end
set -g __getopts_argv (__getopts_sanitize_argv $argv)
end
# Always use our preprocessed argument list.
set argv $__getopts_argv
if not set -q __getopts_index
set -g __getopts_index 1
end
# Handle next argument as indicated by the current index.
if set -q argv[$__getopts_index]
set -l option $argv[$__getopts_index]
# Potential matches for $option are: (1) end of arguments `--`,
# (2) long/short options --* or -* or (3) begin of operands, if
# (1) and (2) fail.
switch $option
# End of arguments. Assume everything from this point are operands.
case --
set -e argv[1..$__getopts_index]
printf "%s\n" $argv
__getopts_cleanup
return $__END_OF_OPTIONS
# Looks like we have a well-formed long/short option. Parse.
case --\* -\*
# It will be useful later to know if the current option is in long
# style. Notice we add a leading blank, because cut fails when the
# first character is a dash `-`.
set -e is_long_option
test (printf " "$option | cut -c2-3) = "--"
and set -l is_long_option
# Trim leading dashes and prepare to match with valid options.
set option (printf $option | sed 's/^-*//g')
for substring in $__getopts_optstr
# Split up by token separator `:`. The resulting list contains
# all valid options, both in short and long style.
set -l tokens (printf $substring | tr : \n)
# Start last to first to avoid mistaking long w/ short options.
for index in (seq (count $tokens) 1)
set -l last_token (printf $substring | tail -c1)
# Find options with optional argument in long-style and
# try to split by required delimiter `=`
# Note: Optional values shall be separated from options in
# long style by an equals sign. See documentation.
if [ $last_token = \^ ]
if set -q is_long_option
set -l option_value (printf $option | tr = \n)
if set -q option_value[2]
# Check if it is a valid option match.
if [ $option_value[1] = $tokens[$index] ]
printf "%s\n" $tokens[1]
printf $option_value[2]
__getopts_increase_index
return $__CONTINUE
end
end
end
end
# We are already inside a possible short/long option match.
# Option is the argument in __getopts_argv[__getopts_index]
# The following compares option against each valid argument
# in the option string __getopts_optstr. If no match can be
# found, print the argument after the loop.
switch $option
case $tokens[$index]
# Success. Send first token to stdout to make sure short
# options, if available, always get the highest priority.
printf "%s\n" $tokens[1]
switch $last_token
# At this point, both required and optional arguments
# look the same to the parser, so we match last_token
# to either `:` required or `^` optional tokens. This
# is the result of preprocessing __getopts_argv.
case : \^
# Find options with optional argument in long-style.
if [ $last_token = \^ ]
if set -q is_long_option
# An optional argument in long-style here means it
# didn't split by = in previous checks, so we can
# handle it as a missing argument option and exit.
# Print any non-empty list character.
printf "\n"
__getopts_increase_index
return $__CONTINUE
end
end
# If we reach this, we are either handling a required
# option or an optional in short-style form. Both are
# seen by the parser the same after preprocessing.
# For example:
# Given the option string `a b:^`, -abX-a -b X
# We need to peek at the next option
__getopts_increase_index
# Check if next argument exists.
if set -q argv[$__getopts_index]
# Sanitize the option-argument.
set -l value (printf $argv[$__getopts_index])
if [ -z "$value" ]
# Print any non-empty list character.
printf "\n"
else
printf $value
end
__getopts_increase_index
return $__CONTINUE
else
__getopts_cleanup
return $__OPTION_ARGUMENT_EXPECTED
end
# Option was a flag that takes no arguments.
case \*
__getopts_increase_index
return $__CONTINUE
end
end
end
end
# No match found. Print unknown option to stdout first, and
# if strict mode is enabled, abort, else continue.
printf $option
__getopts_increase_index
# If the first character of the option string is a `:`, this
# enables strict mode and any unknown options will cause the
# process to return > 0.
if set -q __getopts_strict_mode
__getopts_cleanup
return $__UNKNOWN_OPTION_FOUND
else
return $__CONTINUE
end
# Looks like we run out of options. Print operands return.
case \*
if [ $__getopts_index -gt 1 ]
# Sans the current index, get rid of everything up to here.
set -e argv[1..(math $__getopts_index-1)]
end
printf "%s\n" $argv
__getopts_cleanup
return $__END_OF_OPTIONS
end
else
# End of arguments, cleanup and exit.
__getopts_cleanup
return $__END_OF_OPTIONS
end
end

View File

@ -0,0 +1,142 @@
import plugins/fish-spec
import plugins/getopts
function describe_getotps -d "fish getopts"
function __getopts
set -l options $argv[1]
set -e argv[1]
while set option (getopts $options $argv)
if [ (count $option) -gt 1 ] # w/ argument
set result "$result$option[1]($option[2])"
else
set result "$result$option"
end
end
printf "$result$option"
end
function it_returns_1_if_empty
getopts
expect $status --to-equal 1
end
function it_handles_a_single_short_option
expect (__getopts "a" -a) --to-equal a
end
function it_handles_a_single_long_option
expect (__getopts "aa" --aa) --to-equal aa
end
function it_handles_short_options
set -l options "a b c x y z"
set -l expected abcxyz
expect (__getopts $options -abc -xyz) --to-equal $expected
and expect (__getopts $options -a -b -c -x -y -z) --to-equal $expected
end
function it_handles_long_options
expect (__getopts "x-rays ye zombie" --ye --zombie --x-rays) \
--to-equal yezombiex-rays
end
function it_handles_long_options_only
expect (__getopts "all blink crunch" --all --blink --crunch) \
--to-equal allblinkcrunch
end
function it_handles_all_options
expect (__getopts "g:get p:put u:update d:delete t:post" -tud --put --get) \
--to-equal tudpg
end
function it_handles_operands
expect (__getopts "" 1 2 3 4 5) --to-equal "1 2 3 4 5"
end
function it_handles_options_and_operands
expect (__getopts "g:get p:put u:update d:delete t:post" \
-ud --put --get --post url1 url2 user:password) \
--to-equal "udpgturl1 url2 user:password"
end
function it_handles_end_of_args_separator
expect (__getopts "a b c d" -abcd -- -x -y -z) --to-equal "abcd-x -y -z"
end
function it_detects_end_of_args
expect (__getopts "a b c" -abc 1 2 3 -x) --to-equal "abc1 2 3 -x"
end
function it_returns_unknown_option_error_on_strict_mode
while set option (getopts ":a b c d e f" -abcXdef 1 2 3)
set result "$result$option"
end
expect $status --to-equal 3
end
function it_handles_valid_options_while_on_strict_mode
while set option (getopts ":a b c d e f" -fed -cba 1 2 3)
set result "$result$option"
end
expect "$result" --to-equal fedcba
and expect "$option" --to-equal "1 2 3"
end
function it_returns_argument_missing_error_if_argument_is_missing
while set option (getopts "x y z:" -xy -z)
end
expect $status --to-equal 2
end
function it_handles_options_normally_until_an_error_is_found
while set option (getopts ":a b c d e f" -abcXdef 1 2 3)
set result "$result$option"
end
expect "$result" --to-equal abc
expect "$option" --to-equal X
end
function it_handles_short_options_separated_by_blanks
expect (__getopts "a: b: c:" -a '1st arg' -b '2nd arg' -c '3rd arg') \
--to-equal "a(1st arg)b(2nd arg)c(3rd arg)"
end
function it_handles_a_short_option_w_required_argument_without_space_separation
expect (__getopts "a: b:" -adarwin -blinux) --to-equal "a(darwin)b(linux)"
end
function it_handles_a_short_option_w_optional_argument_with_space_separation
expect (__getopts "a: b:^" -a darwin -b linux) --to-equal "a(darwin)b()linux"
end
function it_handles_options_w_argument
set -l url "http://github.com"
set -l expected "pg($url)"
set -l options "g:get: p:put"
expect (__getopts $options -pg$url) --to-equal $expected
and expect (__getopts $options -p --get $url) --to-equal $expected
end
function it_handles_options_w_optional_argument
set -l url "http://github.com"
set -l expected "pg($url)"
set -l options "g:get:^ p:put"
expect (__getopts $options -pg$url) --to-equal $expected
and expect (__getopts $options -p --get=$url) --to-equal $expected
and expect (__getopts $options -pg) --to-equal "pg()"
end
function it_handles_a_contrived_example
set -l options "A b c: x y z: long long-req: long-opt:^"
set -l args -xyz777 -Abc100 --long --long-req 32 --long-opt=!!! D O N E
expect (__getopts $options $args) --to-equal \
"xyz(777)Abc(100)longlong-req(32)long-opt(!!!)D O N E"
end
end
spec.run $argv