From cc565fc16c69affeda4a7e8a1c8d7e419fc5c519 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Wed, 9 Jul 2014 18:21:06 -0700 Subject: [PATCH 01/12] Teach `command` builtin a -p/--path flag Give the `command` builtin a single flag, -p/--path, that causes it to print the full path that would be executed for the given command. --- builtin.cpp | 82 ++++++++++++++++++++++++++++++++++++++++++++- doc_src/command.txt | 12 +++++-- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/builtin.cpp b/builtin.cpp index a7b173bb2..2d3eb749d 100644 --- a/builtin.cpp +++ b/builtin.cpp @@ -1053,6 +1053,86 @@ static int builtin_emit(parser_t &parser, wchar_t **argv) } +/** + Implementation of the builtin 'command'. Actual command running is handled by + the parser, this just processes the flags. +*/ +static int builtin_command(parser_t &parser, wchar_t **argv) +{ + int argc=builtin_count_args(argv); + int print_path=0; + + woptind=0; + + static const struct woption + long_options[] = + { + { L"path", no_argument, 0, 'p' }, + { L"help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + while (1) + { + int opt_index = 0; + + int opt = wgetopt_long(argc, + argv, + L"ph", + long_options, + &opt_index); + if (opt == -1) + break; + + switch (opt) + { + case 0: + if (long_options[opt_index].flag != 0) + break; + append_format(stderr_buffer, + BUILTIN_ERR_UNKNOWN, + argv[0], + long_options[opt_index].name); + builtin_print_help(parser, argv[0], stderr_buffer); + return STATUS_BUILTIN_ERROR; + + case 'h': + builtin_print_help(parser, argv[0], stdout_buffer); + return STATUS_BUILTIN_OK; + + case 'p': + print_path=1; + break; + + case '?': + builtin_unknown_option(parser, argv[0], argv[woptind-1]); + return STATUS_BUILTIN_ERROR; + + } + + } + + if (!print_path) + { + builtin_print_help(parser, argv[0], stdout_buffer); + return STATUS_BUILTIN_ERROR; + } + + int found=0; + + for (int idx = woptind; argv[idx]; ++idx) + { + const wchar_t *command_name = argv[idx]; + wcstring path; + if (path_get_path(command_name, &path)) + { + append_format(stdout_buffer, L"%ls\n", path.c_str()); + ++found; + } + } + return found ? STATUS_BUILTIN_OK : STATUS_BUILTIN_ERROR; +} + /** A generic bultin that only supports showing a help message. This is only a placeholder that prints the help message. Useful for @@ -3703,7 +3783,7 @@ static const builtin_data_t builtin_datas[]= { L"builtin", &builtin_builtin, N_(L"Run a builtin command instead of a function") }, { L"case", &builtin_generic, N_(L"Conditionally execute a block of commands") }, { L"cd", &builtin_cd, N_(L"Change working directory") }, - { L"command", &builtin_generic, N_(L"Run a program instead of a function or builtin") }, + { L"command", &builtin_command, N_(L"Run a program instead of a function or builtin") }, { L"commandline", &builtin_commandline, N_(L"Set or get the commandline") }, { L"complete", &builtin_complete, N_(L"Edit command specific completions") }, { L"contains", &builtin_contains, N_(L"Search for a specified string in a list") }, diff --git a/doc_src/command.txt b/doc_src/command.txt index 19baccd46..e93f34b8e 100644 --- a/doc_src/command.txt +++ b/doc_src/command.txt @@ -1,12 +1,20 @@ \section command command - run a program \subsection command-synopsis Synopsis -command COMMANDNAME [OPTIONS...] +command [OPTIONS] COMMANDNAME [ARGS...] \subsection command-description Description \c command forces the shell to execute the program \c COMMANDNAME and ignore any functions or builtins with the same name. -\subsection command-example Example +The following options are available: +- \c -h or \c --help prints help and then exits. +- \c -p or \c --path returns the name of the disk file that would be executed, or nothing if no file with the specified name could be found in the $PATH. + +With the \c -p option, \c command treats every argument as a separate command to look up and sets the exit status to 0 if any of the specified commands were found, or 1 if no commands could be found. + +\subsection command-example Examples command ls causes fish to execute the \c ls program, even if an 'ls' function exists. + +command -p ls returns the path to the \c ls program. From 0933e5cab470125fc3757dc146173551a4cad177 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Wed, 9 Jul 2014 19:45:24 -0700 Subject: [PATCH 02/12] Fix typo in documentation for `type` builtin --- doc_src/type.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc_src/type.txt b/doc_src/type.txt index 9b5699f35..25bac5289 100644 --- a/doc_src/type.txt +++ b/doc_src/type.txt @@ -14,7 +14,7 @@ The following options are available: - \c -f or \c --no-functions suppresses function and builtin lookup. - \c -t or \c --type prints keyword, function, builtin, or file if \c NAME is a shell reserved word, function, builtin, or disk file, respectively. - \c -p or \c --path returns the name of the disk file that would be executed, or nothing if 'type -t name' would not return 'file'. -- \c -P or \c --force-path returns the name of the disk file that would be executed, or nothing no file with the specified name could be found in the $PATH. +- \c -P or \c --force-path returns the name of the disk file that would be executed, or nothing if no file with the specified name could be found in the $PATH. \c type sets the exit status to 0 if the specified command was found, and 1 if it could not be found. From bfd3a47380f65e24ff568efd1570338298d56200 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Wed, 9 Jul 2014 23:38:33 -0700 Subject: [PATCH 03/12] Fix `type` function to work better Stop using getopt to parse flags. It's far more expensive than necessary, and results in long flags not being parsed on OS X. This also allows args starting with - after the options list to be properly interpreted as a value to test. Print the error message to stderr as is appropriate. Use the new `command -p` functionality when the -a flag has not been provided (`command` does not have any equivalent to the -a flag), instead of using `which`. This is faster and also avoids any possible disagreement between `which` and what fish thinks is valid. Stop testing every path to see if it's executable, that test has already been done by `which` or `command -p`. The end result is `type -P ls` is roughly 250% faster, according to profiling, on my OS X machine. --- share/functions/type.fish | 114 ++++++++++++++------------------------ 1 file changed, 43 insertions(+), 71 deletions(-) diff --git a/share/functions/type.fish b/share/functions/type.fish index 4a4c77031..2ddcc622e 100644 --- a/share/functions/type.fish +++ b/share/functions/type.fish @@ -5,74 +5,48 @@ function type --description "Print the type of a command" set -l res 1 set -l mode normal set -l selection all - - # - # Get options - # - set -l options - set -l shortopt tpPafh - if not getopt -T > /dev/null - # GNU getopt - set -l longopt type,path,force-path,all,no-functions,help - set options -o $shortopt -l $longopt -- - # Verify options - if not getopt -n type $options $argv >/dev/null - return 1 - end - else - # Old getopt, used on OS X - set options $shortopt - # Verify options - if not getopt $options $argv >/dev/null - return 1 - end - end - # Do the real getopt invocation - set -l tmp (getopt $options $argv) + # Parse options + set -l names + if test (count $argv) -gt 0 + for i in (seq (count $argv)) + switch $argv[$i] + case -t --type + set mode type - # Break tmp up into an array - set -l opt - eval set opt $tmp - - for i in $opt - switch $i - case -t --type - set mode type + case -p --path + set mode path - case -p --path - set mode path + case -P --force-path + set mode path + set selection files - case -P --force-path - set mode path - set selection files + case -a --all + set selection multi - case -a --all - set selection multi + case -f --no-functions + set selection files - case -f --no-functions - set selection files + case -h --help + __fish_print_help type + return 0 - case -h --help - __fish_print_help type - return 0 - - case -- - break + case -- + set names $argv[$i..-1] + set -e names[1] + break + case '*' + set names $argv[$i..-1] + break + end end end # Check all possible types for the remaining arguments - for i in $argv - - switch $i - case '-*' - continue - end - + for i in $names # Found will be set to 1 if a match is found - set found 0 + set -l found 0 if test $selection != files @@ -119,32 +93,30 @@ function type --description "Print the type of a command" set -l paths if test $selection != multi - set paths (which $i ^/dev/null) + set paths (command -p $i) else set paths (which -a $i ^/dev/null) end for path in $paths - if test -x (echo $path) - set res 0 - set found 1 - switch $mode - case normal - printf (_ '%s is %s\n') $i $path + set res 0 + set found 1 + switch $mode + case normal + printf (_ '%s is %s\n') $i $path - case type - echo (_ 'file') + case type + echo (_ 'file') - case path - echo $path - end - if test $selection != multi - continue - end + case path + echo $path + end + if test $selection != multi + continue end end if test $found = 0 - printf (_ "%s: Could not find '%s'\n") type $i + printf (_ "%s: Could not find '%s'\n") type $i >&2 end end From 6b062b07b4758883c3d2b7bb28791a86b267ba0a Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Thu, 10 Jul 2014 00:07:58 -0700 Subject: [PATCH 04/12] type: Separate the notion of multi and paths Track whether -a and -f have been supplied separately. That way both `type -a -f command` and `type -f -a command` behaves correctly, as does `type -a -f foo` where there are multiple executables named `foo` in the $PATH. --- share/functions/type.fish | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/share/functions/type.fish b/share/functions/type.fish index 2ddcc622e..9cadcabc3 100644 --- a/share/functions/type.fish +++ b/share/functions/type.fish @@ -4,6 +4,7 @@ function type --description "Print the type of a command" # Initialize set -l res 1 set -l mode normal + set -l multi no set -l selection all # Parse options @@ -22,7 +23,7 @@ function type --description "Print the type of a command" set selection files case -a --all - set selection multi + set multi yes case -f --no-functions set selection files @@ -65,7 +66,7 @@ function type --description "Print the type of a command" echo end - if test $selection != multi + if test $multi != yes continue end end @@ -84,7 +85,7 @@ function type --description "Print the type of a command" case path echo end - if test $selection != multi + if test $multi != yes continue end end @@ -92,7 +93,7 @@ function type --description "Print the type of a command" end set -l paths - if test $selection != multi + if test $multi != yes set paths (command -p $i) else set paths (which -a $i ^/dev/null) @@ -110,7 +111,7 @@ function type --description "Print the type of a command" case path echo $path end - if test $selection != multi + if test $multi != yes continue end end From 6f7a7459c1dadc88e793b558d5ce2668359e7bea Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Thu, 10 Jul 2014 00:18:50 -0700 Subject: [PATCH 05/12] test: Add a new --quiet flag to suppress output The --quiet flag is useful when only the exit status matters. Fix the documentation for the -t flag to no longer claim that `type` can print "keyword", as it never does that. Stop printing a blank line for functions/builtins when the -p flag has been passed. It's just not useful. --- doc_src/type.txt | 3 ++- share/functions/type.fish | 24 +++++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/doc_src/type.txt b/doc_src/type.txt index 25bac5289..8945102ca 100644 --- a/doc_src/type.txt +++ b/doc_src/type.txt @@ -12,9 +12,10 @@ The following options are available: - \c -h or \c --help prints help and then exits. - \c -a or \c --all prints all of possible definitions of the specified names. - \c -f or \c --no-functions suppresses function and builtin lookup. -- \c -t or \c --type prints keyword, function, builtin, or file if \c NAME is a shell reserved word, function, builtin, or disk file, respectively. +- \c -t or \c --type prints function, builtin, or file if \c NAME is a shell function, builtin, or disk file, respectively. - \c -p or \c --path returns the name of the disk file that would be executed, or nothing if 'type -t name' would not return 'file'. - \c -P or \c --force-path returns the name of the disk file that would be executed, or nothing if no file with the specified name could be found in the $PATH. +- \c -q or \c --quiet suppresses all output; this is useful when testing the exit status. \c type sets the exit status to 0 if the specified command was found, and 1 if it could not be found. diff --git a/share/functions/type.fish b/share/functions/type.fish index 9cadcabc3..c54997dd2 100644 --- a/share/functions/type.fish +++ b/share/functions/type.fish @@ -13,13 +13,19 @@ function type --description "Print the type of a command" for i in (seq (count $argv)) switch $argv[$i] case -t --type - set mode type + if test $mode != quiet + set mode type + end case -p --path - set mode path + if test $mode != quiet + set mode path + end case -P --force-path - set mode path + if test $mode != quiet + set mode path + end set selection files case -a --all @@ -28,6 +34,9 @@ function type --description "Print the type of a command" case -f --no-functions set selection files + case -q --quiet + set mode quiet + case -h --help __fish_print_help type return 0 @@ -61,10 +70,6 @@ function type --description "Print the type of a command" case type echo (_ 'function') - - case path - echo - end if test $multi != yes continue @@ -81,9 +86,6 @@ function type --description "Print the type of a command" case type echo (_ 'builtin') - - case path - echo end if test $multi != yes continue @@ -116,7 +118,7 @@ function type --description "Print the type of a command" end end - if test $found = 0 + if begin; test $found = 0; and test $mode != quiet; end printf (_ "%s: Could not find '%s'\n") type $i >&2 end From 533496e43ac4de68537b66412d54e880e03b6561 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Thu, 10 Jul 2014 00:26:58 -0700 Subject: [PATCH 06/12] Adopt the new type -q flag in the other functions --- share/functions/__fish_complete_cabal.fish | 2 +- share/functions/__fish_complete_vi.fish | 2 +- share/functions/__fish_config_interactive.fish | 4 ++-- share/functions/__fish_print_packages.fish | 14 +++++++------- share/functions/funced.fish | 2 +- share/functions/help.fish | 14 +++++++------- share/functions/ls.fish | 2 +- share/functions/open.fish | 4 ++-- share/functions/seq.fish | 2 +- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/share/functions/__fish_complete_cabal.fish b/share/functions/__fish_complete_cabal.fish index b5b8c0c00..484710a84 100644 --- a/share/functions/__fish_complete_cabal.fish +++ b/share/functions/__fish_complete_cabal.fish @@ -1,5 +1,5 @@ function __fish_complete_cabal - if type -f cabal >/dev/null + if type -q -f cabal set cmd (commandline -poc) if test (count $cmd) -gt 1 cabal $cmd[2..-1] --list-options diff --git a/share/functions/__fish_complete_vi.fish b/share/functions/__fish_complete_vi.fish index 8a07ffd2e..51009d8b8 100644 --- a/share/functions/__fish_complete_vi.fish +++ b/share/functions/__fish_complete_vi.fish @@ -2,7 +2,7 @@ function __fish_complete_vi -d "Compleletions for vi and its aliases" --argument-names cmd set -l is_vim - if type $cmd > /dev/null + if type -q $cmd eval command $cmd --version >/dev/null ^/dev/null; and set -l is_vim vim end diff --git a/share/functions/__fish_config_interactive.fish b/share/functions/__fish_config_interactive.fish index 46b6ca248..1d3a1bea2 100644 --- a/share/functions/__fish_config_interactive.fish +++ b/share/functions/__fish_config_interactive.fish @@ -248,7 +248,7 @@ function __fish_config_interactive -d "Initializations that should be performed # First check if we are on OpenSUSE since SUSE's handler has no options # and expects first argument to be a command and second database # also check if there is command-not-found command. - if begin; test -f /etc/SuSE-release; and type -p command-not-found > /dev/null 2> /dev/null; end + if begin; test -f /etc/SuSE-release; and type -q -p command-not-found; end function __fish_command_not_found_handler --on-event fish_command_not_found /usr/bin/command-not-found $argv end @@ -263,7 +263,7 @@ function __fish_config_interactive -d "Initializations that should be performed /usr/lib/command-not-found -- $argv end # Ubuntu Feisty places this command in the regular path instead - else if type -p command-not-found > /dev/null 2> /dev/null + else if type -q -p command-not-found function __fish_command_not_found_handler --on-event fish_command_not_found command-not-found -- $argv end diff --git a/share/functions/__fish_print_packages.fish b/share/functions/__fish_print_packages.fish index 6755dd6ae..050fcac28 100644 --- a/share/functions/__fish_print_packages.fish +++ b/share/functions/__fish_print_packages.fish @@ -18,7 +18,7 @@ function __fish_print_packages end mkdir -m 700 -p $XDG_CACHE_HOME - if type -f apt-cache >/dev/null + if type -q -f apt-cache # Do not generate the cache as apparently sometimes this is slow. # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=547550 apt-cache --no-generate pkgnames (commandline -tc) ^/dev/null | sed -e 's/$/'\t$package'/' @@ -28,7 +28,7 @@ function __fish_print_packages # Pkg is fast on FreeBSD and provides versioning info which we want for # installed packages if begin - type -f pkg > /dev/null + type -q -f pkg and test (uname) = "FreeBSD" end pkg query "%n-%v" @@ -36,7 +36,7 @@ function __fish_print_packages end # Caches for 5 minutes - if type -f pacman >/dev/null + if type -q -f pacman set cache_file $XDG_CACHE_HOME/.pac-cache.$USER if test -f $cache_file cat $cache_file @@ -53,7 +53,7 @@ function __fish_print_packages end # yum is slow, just like rpm, so go to the background - if type -f /usr/share/yum-cli/completion-helper.py >/dev/null + if type -q -f /usr/share/yum-cli/completion-helper.py # If the cache is less than six hours old, we do not recalculate it @@ -75,7 +75,7 @@ function __fish_print_packages # Rpm is too slow for this job, so we set it up to do completions # as a background job and cache the results. - if type -f rpm >/dev/null + if type -q -f rpm # If the cache is less than five minutes old, we do not recalculate it @@ -99,12 +99,12 @@ function __fish_print_packages # installed on the system packages is in completions/emerge.fish # eix is MUCH faster than emerge so use it if it is available - if type -f eix > /dev/null + if type -q -f eix eix --only-names "^"(commandline -tc) | cut -d/ -f2 return else # FIXME? Seems to be broken - if type -f emerge >/dev/null + if type -q -f emerge emerge -s \^(commandline -tc) |sgrep "^*" |cut -d\ -f3 |cut -d/ -f2 return end diff --git a/share/functions/funced.fish b/share/functions/funced.fish index ca2e27721..074e3c435 100644 --- a/share/functions/funced.fish +++ b/share/functions/funced.fish @@ -51,7 +51,7 @@ function funced --description 'Edit function definition' if test -n "$editor" set -l editor_cmd eval set editor_cmd $editor - if not type -f "$editor_cmd[1]" >/dev/null + if not type -q -f "$editor_cmd[1]" _ "funced: The value for \$EDITOR '$editor' could not be used because the command '$editor_cmd[1]' could not be found " set editor fish diff --git a/share/functions/help.fish b/share/functions/help.fish index 04992e35d..a04add53f 100644 --- a/share/functions/help.fish +++ b/share/functions/help.fish @@ -25,7 +25,7 @@ function help --description 'Show help for the fish shell' set -l graphical_browsers htmlview x-www-browser firefox galeon mozilla konqueror epiphany opera netscape rekonq google-chrome chromium-browser set -l text_browsers htmlview www-browser links elinks lynx w3m - if type "$BROWSER" >/dev/null + if type -q "$BROWSER" # User has manually set a preferred browser, so we respect that set fish_browser $BROWSER @@ -36,7 +36,7 @@ function help --description 'Show help for the fish shell' else # Check for a text-based browser. for i in $text_browsers - if type -f $i >/dev/null + if type -q -f $i set fish_browser $i break end @@ -46,7 +46,7 @@ function help --description 'Show help for the fish shell' # browser to use instead. if test "$DISPLAY" -a \( "$XAUTHORITY" = "$HOME/.Xauthority" -o "$XAUTHORITY" = "" \) for i in $graphical_browsers - if type -f $i >/dev/null + if type -q -f $i set fish_browser $i set fish_browser_bg 1 break @@ -55,17 +55,17 @@ function help --description 'Show help for the fish shell' end # If the OS appears to be Windows (graphical), try to use cygstart - if type cygstart > /dev/null + if type -q cygstart set fish_browser cygstart # If xdg-open is available, just use that - else if type xdg-open > /dev/null + else if type -q xdg-open set fish_browser xdg-open end # On OS X, we go through osascript by default if test (uname) = Darwin - if type osascript >/dev/null + if type -q osascript set fish_browser osascript end end @@ -92,7 +92,7 @@ function help --description 'Show help for the fish shell' case $help_topics set fish_help_page "index.html\#$fish_help_item" case "*" - if type -f $fish_help_item >/dev/null + if type -q -f $fish_help_item # Prefer to use fish's man pages, to avoid # the annoying useless "builtin" man page bash # installs on OS X diff --git a/share/functions/ls.fish b/share/functions/ls.fish index dd4fb3a67..4824e1c1d 100644 --- a/share/functions/ls.fish +++ b/share/functions/ls.fish @@ -13,7 +13,7 @@ if command ls --version 1>/dev/null 2>/dev/null end if not set -q LS_COLORS - if type -f dircolors >/dev/null + if type -q -f dircolors eval (dircolors -c | sed 's/>&\/dev\/null$//') end end diff --git a/share/functions/open.fish b/share/functions/open.fish index 63d562371..7a1e7bcec 100644 --- a/share/functions/open.fish +++ b/share/functions/open.fish @@ -14,11 +14,11 @@ if not test (uname) = Darwin end end - if type -f cygstart >/dev/null + if type -q -f cygstart for i in $argv cygstart $i end - else if type -f xdg-open >/dev/null + else if type -q -f xdg-open for i in $argv xdg-open $i end diff --git a/share/functions/seq.fish b/share/functions/seq.fish index 0f5f8534c..4229f0c5a 100644 --- a/share/functions/seq.fish +++ b/share/functions/seq.fish @@ -2,7 +2,7 @@ # test -x in /usr/bin/seq because that's where it usually is and # that's substantially cheaper than the type function -if begin ; not test -x /usr/bin/seq ; and not type -f seq > /dev/null; end +if begin ; not test -x /usr/bin/seq ; and not type -q -f seq; end # No seq command function seq --description "Print sequences of numbers" __fish_fallback_seq $argv From 29b3b6b31e3acbe2b5474db3b7c3841ba1ed17f9 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Thu, 10 Jul 2014 00:37:54 -0700 Subject: [PATCH 07/12] type: Stop claiming `grep` is a function Use `functions -q` instead of searching the `functiosn -na` list for the provided word. This may result in an automatically-loaded function being sourced, but that happens anyway with the default output. This change means the results of `test -q foo` can be relied upon to indicate whether `foo` can actually be invoked. Previosly, if `foo` was the name of an automatically-loaded function file but did not actually define a function `foo`, and there was no execuable `foo`, then `type -q foo` would lie and say `foo` can be invoked when it can't. --- share/functions/type.fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/functions/type.fish b/share/functions/type.fish index c54997dd2..06e94abbe 100644 --- a/share/functions/type.fish +++ b/share/functions/type.fish @@ -60,7 +60,7 @@ function type --description "Print the type of a command" if test $selection != files - if contains -- $i (functions -na) + if functions -q -- $i set res 0 set found 1 switch $mode From cfa13ed84c32ce3330041a54de392caed83f7ac5 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Thu, 10 Jul 2014 10:37:44 -0700 Subject: [PATCH 08/12] Update tests for new `type` behavior One of the tests was using `>/dev/null` to suppress the `type` output. That needs to be `^/dev/null` now, but instead just go ahead and use the new `-q` flag. --- tests/test7.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test7.in b/tests/test7.in index a3ae8360c..322982756 100644 --- a/tests/test7.in +++ b/tests/test7.in @@ -110,6 +110,6 @@ function fish_test_type_zzz true end # Should succeed -type fish_test_type_zzz >/dev/null ; echo $status +type -q fish_test_type_zzz ; echo $status # Should fail -type -f fish_test_type_zzz >/dev/null ; echo $status +type -q -f fish_test_type_zzz ; echo $status From 72e8489d50d749c86d5b57609bb0c4d83a03b41a Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Thu, 10 Jul 2014 19:16:32 -0700 Subject: [PATCH 09/12] command: Rename -p/--path flag to -s/--search --- builtin.cpp | 7 ++++--- doc_src/command.txt | 8 +++++--- share/functions/type.fish | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/builtin.cpp b/builtin.cpp index 2d3eb749d..6f7cc33c6 100644 --- a/builtin.cpp +++ b/builtin.cpp @@ -1067,7 +1067,7 @@ static int builtin_command(parser_t &parser, wchar_t **argv) static const struct woption long_options[] = { - { L"path", no_argument, 0, 'p' }, + { L"search", no_argument, 0, 's' }, { L"help", no_argument, 0, 'h' }, { 0, 0, 0, 0 } }; @@ -1078,7 +1078,7 @@ static int builtin_command(parser_t &parser, wchar_t **argv) int opt = wgetopt_long(argc, argv, - L"ph", + L"svh", long_options, &opt_index); if (opt == -1) @@ -1100,7 +1100,8 @@ static int builtin_command(parser_t &parser, wchar_t **argv) builtin_print_help(parser, argv[0], stdout_buffer); return STATUS_BUILTIN_OK; - case 'p': + case 's': + case 'v': print_path=1; break; diff --git a/doc_src/command.txt b/doc_src/command.txt index e93f34b8e..cef08fdc5 100644 --- a/doc_src/command.txt +++ b/doc_src/command.txt @@ -9,12 +9,14 @@ The following options are available: - \c -h or \c --help prints help and then exits. -- \c -p or \c --path returns the name of the disk file that would be executed, or nothing if no file with the specified name could be found in the $PATH. +- \c -s or \c --search returns the name of the disk file that would be executed, or nothing if no file with the specified name could be found in the $PATH. -With the \c -p option, \c command treats every argument as a separate command to look up and sets the exit status to 0 if any of the specified commands were found, or 1 if no commands could be found. +With the \c -s option, \c command treats every argument as a separate command to look up and sets the exit status to 0 if any of the specified commands were found, or 1 if no commands could be found. + +For basic compatibility with POSIX command, the \c -v flag is recognized as an alias for -s. \subsection command-example Examples command ls causes fish to execute the \c ls program, even if an 'ls' function exists. -command -p ls returns the path to the \c ls program. +command -s ls returns the path to the \c ls program. diff --git a/share/functions/type.fish b/share/functions/type.fish index 06e94abbe..8992d6b22 100644 --- a/share/functions/type.fish +++ b/share/functions/type.fish @@ -96,7 +96,7 @@ function type --description "Print the type of a command" set -l paths if test $multi != yes - set paths (command -p $i) + set paths (command -s $i) else set paths (which -a $i ^/dev/null) end From 973dd6ffbdc189f22b634de0d684e92a9c160c9d Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Sun, 13 Jul 2014 22:36:26 -0700 Subject: [PATCH 10/12] read: Support arrays, character splitting Enhance the `read` builtin to support creating an array with the --array flag. With --array, only a single variable name is allowed and the entire input is tokenized and placed into that variable as an array. Also add custom behavior if IFS is empty or unset. In that event, split the input on every character, instead of the previous behavior of doing no splitting at all. --- builtin.cpp | 83 +++++++++++++++++++++++++++++++++++++++++------ doc_src/read.txt | 6 ++++ tests/read.err | 0 tests/read.in | 72 ++++++++++++++++++++++++++++++++++++++++ tests/read.out | 27 +++++++++++++++ tests/read.status | 1 + tests/top.out | 1 + 7 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 tests/read.err create mode 100644 tests/read.in create mode 100644 tests/read.out create mode 100644 tests/read.status diff --git a/builtin.cpp b/builtin.cpp index 6f7cc33c6..1420e33c1 100644 --- a/builtin.cpp +++ b/builtin.cpp @@ -2301,6 +2301,7 @@ static int builtin_read(parser_t &parser, wchar_t **argv) int exit_res=STATUS_BUILTIN_OK; const wchar_t *mode_name = READ_MODE_NAME; int shell = 0; + int array = 0; woptind=0; @@ -2345,6 +2346,10 @@ static int builtin_read(parser_t &parser, wchar_t **argv) L"shell", no_argument, 0, 's' } , + { + L"array", no_argument, 0, 'a' + } + , { L"help", no_argument, 0, 'h' } @@ -2359,7 +2364,7 @@ static int builtin_read(parser_t &parser, wchar_t **argv) int opt = wgetopt_long(argc, argv, - L"xglUup:c:hm:s", + L"xglUup:c:hm:sa", long_options, &opt_index); if (opt == -1) @@ -2414,6 +2419,10 @@ static int builtin_read(parser_t &parser, wchar_t **argv) shell = 1; break; + case 'a': + array = 1; + break; + case 'h': builtin_print_help(parser, argv[0], stdout_buffer); return STATUS_BUILTIN_OK; @@ -2446,6 +2455,14 @@ static int builtin_read(parser_t &parser, wchar_t **argv) return STATUS_BUILTIN_ERROR; } + if (array && woptind+1 != argc) + { + append_format(stderr_buffer, _(L"%ls: --array option requires a single variable name.\n"), argv[0]); + builtin_print_help(parser, argv[0], stderr_buffer); + + return STATUS_BUILTIN_ERROR; + } + /* Verify all variable names */ @@ -2580,18 +2597,64 @@ static int builtin_read(parser_t &parser, wchar_t **argv) wchar_t *state; env_var_t ifs = env_get_string(L"IFS"); - if (ifs.missing()) - ifs = L""; - nxt = wcstok(buff, (i 0) + { + wcstring chars(bufflen+(bufflen-1), ARRAY_SEP); + for (size_t j=0; j-u or --unexport prevents the variables from being exported to child processes (default behaviour). - -U or --universal causes the specified shell variable to be made universal. - -x or --export exports the variables to child processes. +- -a or --array stores the result as an array. \c read reads a single line of input from stdin, breaks it into tokens based on the IFS shell variable, and then assigns one token to each variable specified in VARIABLES. If there are more tokens than variables, the complete remainder is assigned to the last variable. +As a special case, if \c IFS is set to the empty string, each character of the +input is considered a separate token. + +If \c -a or \c --array is provided, only one variable name is allowed and the +tokens are stored as an array in this variable. See the documentation for \c set for more details on the scoping rules for variables. diff --git a/tests/read.err b/tests/read.err new file mode 100644 index 000000000..e69de29bb diff --git a/tests/read.in b/tests/read.in new file mode 100644 index 000000000..53f9873b6 --- /dev/null +++ b/tests/read.in @@ -0,0 +1,72 @@ +# +# Test read builtin and IFS +# + +count (echo one\ntwo) +set -l IFS \t +count (echo one\ntwo) +set -l IFS +count (echo one\ntwo) +set -le IFS + +function print_vars --no-scope-shadowing + set -l space + set -l IFS \n # ensure our command substitution works right + for var in $argv + echo -n $space (count $$var) \'$$var\' + set space '' + end + echo +end + +echo +echo 'hello there' | read -l one two +print_vars one two +echo 'hello there' | read -l one +print_vars one +echo '' | read -l one +print_vars one +echo '' | read -l one two +print_vars one two +echo 'test' | read -l one two three +print_vars one two three + +echo +set -l IFS +echo 'hello' | read -l one +print_vars one +echo 'hello' | read -l one two +print_vars one two +echo 'hello' | read -l one two three +print_vars one two three +echo '' | read -l one +print_vars one +echo 't' | read -l one two +print_vars one two +echo 't' | read -l one two three +print_vars one two three +echo ' t' | read -l one two +print_vars one two +set -le IFS + +echo +echo 'hello there' | read -la ary +print_vars ary +echo 'hello' | read -la ary +print_vars ary +echo 'this is a bunch of words' | read -la ary +print_vars ary +echo ' one two three' | read -la ary +print_vars ary +echo '' | read -la ary +print_vars ary + +echo +set -l IFS +echo 'hello' | read -la ary +print_vars ary +echo 'h' | read -la ary +print_vars ary +echo '' | read -la ary +print_vars ary +set -le IFS diff --git a/tests/read.out b/tests/read.out new file mode 100644 index 000000000..6d90fc0e7 --- /dev/null +++ b/tests/read.out @@ -0,0 +1,27 @@ +2 +2 +1 + +1 'hello' 1 'there' +1 'hello there' +1 '' +1 '' 1 '' +1 'test' 1 '' 1 '' + +1 'hello' +1 'h' 1 'ello' +1 'h' 1 'e' 1 'llo' +1 '' +1 't' 1 '' +1 't' 1 '' 1 '' +1 ' ' 1 't' + +2 'hello' 'there' +1 'hello' +6 'this' 'is' 'a' 'bunch' 'of' 'words' +3 'one' 'two' 'three' +0 + +5 'h' 'e' 'l' 'l' 'o' +1 'h' +0 diff --git a/tests/read.status b/tests/read.status new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/tests/read.status @@ -0,0 +1 @@ +0 diff --git a/tests/top.out b/tests/top.out index 768526c66..f2873b153 100644 --- a/tests/top.out +++ b/tests/top.out @@ -1,5 +1,6 @@ Testing high level script functionality File printf.in tested ok +File read.in tested ok File test1.in tested ok File test2.in tested ok File test3.in tested ok From cce4265cef63132f7e398f7cf89a63d8078a9c2e Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Sun, 13 Jul 2014 23:08:38 -0700 Subject: [PATCH 11/12] Fix `make test` to use local functions When running `make test` we want to use the local function definitions, not the ones installed on the system. The system config.fish will still insert the system definitions at the end, but at least ours will take precedence. --- Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 9fe0f8e05..4995dddcc 100644 --- a/Makefile.in +++ b/Makefile.in @@ -284,7 +284,7 @@ doc/refman.pdf: doc test: $(PROGRAMS) fish_tests ./fish_tests - cd tests; ../fish Date: Mon, 14 Jul 2014 11:19:59 -0700 Subject: [PATCH 12/12] type: Restore combined flags behavior Fix the parsing of `type` flags to handle combined short flags as appropriate, e.g. `type -qf ls`. --- share/functions/type.fish | 85 ++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/share/functions/type.fish b/share/functions/type.fish index 8992d6b22..e7bc7b288 100644 --- a/share/functions/type.fish +++ b/share/functions/type.fish @@ -11,44 +11,65 @@ function type --description "Print the type of a command" set -l names if test (count $argv) -gt 0 for i in (seq (count $argv)) - switch $argv[$i] - case -t --type - if test $mode != quiet - set mode type - end + set -l arg $argv[$i] + set -l needbreak 0 + while test -n $arg + set -l flag $arg + set arg '' + switch $flag + case '--*' + # do nothing; this just prevents it matching the next case + case '-??*' + # combined flags + set -l IFS + echo -n $flag | read _ flag arg + set flag -$flag + set arg -$arg + end + switch $flag + case -t --type + if test $mode != quiet + set mode type + end - case -p --path - if test $mode != quiet - set mode path - end + case -p --path + if test $mode != quiet + set mode path + end - case -P --force-path - if test $mode != quiet - set mode path - end - set selection files + case -P --force-path + if test $mode != quiet + set mode path + end + set selection files - case -a --all - set multi yes + case -a --all + set multi yes - case -f --no-functions - set selection files + case -f --no-functions + set selection files - case -q --quiet - set mode quiet + case -q --quiet + set mode quiet - case -h --help - __fish_print_help type - return 0 + case -h --help + __fish_print_help type + return 0 - case -- - set names $argv[$i..-1] - set -e names[1] - break + case -- + set names $argv[$i..-1] + set -e names[1] + set needbreak 1 + break - case '*' - set names $argv[$i..-1] - break + case '*' + set names $argv[$i..-1] + set needbreak 1 + break + end + end + if test $needbreak -eq 1 + break end end end @@ -96,9 +117,9 @@ function type --description "Print the type of a command" set -l paths if test $multi != yes - set paths (command -s $i) + set paths (command -s -- $i) else - set paths (which -a $i ^/dev/null) + set paths (which -a -- $i ^/dev/null) end for path in $paths set res 0