aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar axel <axel@liljencrantz.se>2005-09-20 23:26:39 +1000
committerGravatar axel <axel@liljencrantz.se>2005-09-20 23:26:39 +1000
commit149594f974350bb364a76c73b91b1d5ffddaa1fa (patch)
tree95650e9982d5fabe4bd805d94c5d700cbbc1ca7f
Initial revision
darcs-hash:20050920132639-ac50b-fa3b476891e1f5f67207cf4cc7bf623834cc5edc.gz
-rw-r--r--ChangeLog944
-rw-r--r--Doxyfile1161
-rw-r--r--Doxyfile.user161
-rw-r--r--INSTALL62
-rw-r--r--Makefile.in452
-rw-r--r--README15
-rw-r--r--builtin.c2879
-rw-r--r--builtin.h101
-rw-r--r--builtin_commandline.c461
-rw-r--r--builtin_help.h25
-rw-r--r--builtin_help.hdr36
-rw-r--r--builtin_set.c585
-rw-r--r--common.c1061
-rw-r--r--common.h248
-rw-r--r--complete.c2227
-rw-r--r--complete.h155
-rwxr-xr-xconfig.guess1411
-rw-r--r--config.h.in82
-rwxr-xr-xconfig.sub1500
-rwxr-xr-xconfigure4692
-rw-r--r--configure.ac90
-rw-r--r--count.c22
-rw-r--r--env.c763
-rw-r--r--env.h98
-rw-r--r--env_universal.c316
-rw-r--r--env_universal.h39
-rw-r--r--env_universal_common.c403
-rw-r--r--env_universal_common.h110
-rw-r--r--exec.c1285
-rw-r--r--exec.h43
-rw-r--r--expand.c1518
-rw-r--r--expand.h170
-rw-r--r--fish.spec.in110
-rw-r--r--fish_pager.c992
-rw-r--r--fish_tests.c647
-rw-r--r--fishd.c443
-rw-r--r--function.c144
-rw-r--r--function.h63
-rwxr-xr-xgen_hdr.sh12
-rw-r--r--gen_hdr2.c76
-rw-r--r--highlight.c557
-rw-r--r--highlight.h39
-rw-r--r--history.c597
-rw-r--r--history.h65
-rw-r--r--input.c1210
-rw-r--r--input.h109
-rw-r--r--input_common.c220
-rw-r--r--input_common.h40
-rwxr-xr-xinstall-sh251
-rw-r--r--intern.c119
-rw-r--r--intern.h24
-rw-r--r--key_reader.c92
-rw-r--r--kill.c256
-rw-r--r--kill.h31
-rw-r--r--main.c306
-rw-r--r--mimedb.c1266
-rw-r--r--mimedb.h0
-rw-r--r--output.c367
-rw-r--r--output.h89
-rw-r--r--parser.c2230
-rw-r--r--parser.h261
-rw-r--r--proc.c1268
-rw-r--r--proc.h264
-rw-r--r--reader.c3023
-rw-r--r--reader.h186
-rw-r--r--sanity.c72
-rw-r--r--sanity.h22
-rw-r--r--set_color.c235
-rw-r--r--tokenize.c138
-rw-r--r--tokenizer.c625
-rw-r--r--tokenizer.h146
-rw-r--r--user_doc.head.html25
-rw-r--r--util.c986
-rw-r--r--util.h463
-rw-r--r--wgetopt.c696
-rw-r--r--wgetopt.h145
-rw-r--r--wildcard.c581
-rw-r--r--wildcard.h80
-rw-r--r--wutil.c546
-rw-r--r--wutil.h111
-rw-r--r--xdgmime.c715
-rw-r--r--xdgmime.h93
-rw-r--r--xdgmimealias.c184
-rw-r--r--xdgmimealias.h50
-rw-r--r--xdgmimeglob.c472
-rw-r--r--xdgmimeglob.h65
-rw-r--r--xdgmimeint.c154
-rw-r--r--xdgmimeint.h73
-rw-r--r--xdgmimemagic.c781
-rw-r--r--xdgmimemagic.h54
-rw-r--r--xdgmimeparent.c219
-rw-r--r--xdgmimeparent.h50
-rw-r--r--xsel-0.9.6.tarbin0 -> 307200 bytes
93 files changed, 46253 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 00000000..8e72ad18
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,944 @@
+2005-09-19 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * env_universal.c, env_universal_common.c (barrier, callback, create_message, parse_message): Add support for blocking a client until all current messages from the client and corresponding replies have been transmitted.
+
+ * env_universal.c (env_universal_read_all): Avoid recursive get_socket issues
+
+ * fish_pager.c (main, init, destroy): Return unused keypresses on pager exit
+ * reader.c (run_pager): Return unused keypresses on pager exit
+
+ * fish_pager.c (completion_try_print, main): Use alternate screen for interactive pager when available.
+
+ * reader.c (reader_readline): Quit searching on escape
+
+
+2005-09-17 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * init/fish.in: Revert DISPLAY to non-array
+
+ * doc_src/doc.hdr: Documentation cleanup
+
+
+2005-09-16 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * builtin.c (builtin_status): Added the status builtin, for checking the fishprogram status
+
+ * doc_src/doc.hdr, init/fish, init/fish_function.fish, init/fish_complete.fish, init/fish_interactive.fish: Switch nomencalature: from subshell to command substitution
+
+ * doc_src/doc.hdr: Update documentation on variables
+ * doc_src/doc.hdr: Update documentation on commands and builtins
+ * doc_src/doc.hdr: Update documentation on completions
+
+ * init/fish_function.fish: help command now shows help for non-builtin commands
+ * init/fish_function.fish: help command now searches man-pages
+
+ * env_universal.c, env_universal_common.c, fishd.c, env.c, builtin_set.c: Add support for universal variables
+
+ * env.c: Setting a local variable does not erase a global variable with the same name
+
+
+2005-09-13 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * exec.c (handle_new_child): Only call tcsetpgrp after creating new group
+
+ * exec.c, proc.c (setup_child_process, internal_exec_helper, handle_new_child, job_continue): Do not put block commands into their own groups
+
+
+2005-09-06 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * expand.c (tilde_expand): ~ should expand to $HOME, not getpwuid(getuid())->pw_dir
+
+2005-09-05 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * parser.c, builtin.c, Makefile.in (parse_job, builtin_not, builtin_run, builtin_get_desc): Add new 'not' builtin to negate commands
+
+ * parser.c, builtin.c, Makefile.in (parse_job, builtin_begin, builtin_run, builtin_get_desc): Add new 'begin' builtin to create unconditional block
+
+
+2005-09-01 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * complete.c (complete_load): Reload completions from file if completion file changes
+
+ * init/fish_functions: Added a 'type' shellscript function, with the same functionality as the bash builtin with the same name, and additional support for gnu style long options
+
+
+2005-08-31 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * init/completions/modprobe.fish: Add completions for modprobe command
+ * init/completions/service.fish: Add completions for service command
+ * init/completions/man.fish: Manual page completion now honors section specification
+
+ * builtin.c, exec.c, proc.c (builtin_run, exec, job_continue): Builtins now follow the same rules w.r.t. setting the status variable as all other commands
+
+
+2005-09-12 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.13.4
+
+ * common.c (block, unblock, fgetws2): Smart blocking support to avoid excessive calls to signal blocking functions
+
+ * complete.c (complete): Fix infinite loop when completing string 'command '
+
+
+2005-09-07 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.13.3
+
+ * init/fish.in, init/fish_function.fish: Actually apply Netocrats patch, and not a broken patch causing random error messages
+
+2005-09-06 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.13.2
+
+2005-09-05 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * builtin.c (builtin_source): Fix crash when sourcing non-existant file
+
+ * init/fish.in: Only call unicode_start if in unicode locale
+
+2005-09-05 Netocrat <netocrat@dodo.com.au>
+
+ * init/fish.in: Only set LANG in login shells
+ * init/fish_function (prompt_pwd): Don't use ellipsis character when not in unicode mode
+
+2005-08-28 James Vega <jamessan@jamessan.com>
+
+ * function.c: Add missing #include "intern.h"
+
+ * env.c (env_set): GCC 4.0 workaround
+
+2005-08-31 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * init/completions/function.fish: Some completions for function accidentally targeted the functions builtin. (Note the extra 's')
+
+2005-08-31 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.13.1
+
+2005-08-31 Netocrat <netocrat@dodo.com.au>
+
+ * reader.c (write_prompt): Fix prompt flickering issue
+
+2005-08-31 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * exec.c (internal_exec_helper): Function blocks should not be marked as subshells
+ * exec.c (setup_child_process): Unblock childs signal handlers, fixes script bug
+
+2005-08-28 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.13.0
+
+2005-08-26 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * init/fish_inputrc: Remove the lame replace-commandline feature
+
+ * init/fish_inputrc: Remove the lame explain feature
+
+
+2005-08-25 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * proc.c (job_do_notifications): Do not notify user about status changed in subshell jobs
+
+ * init/completions/rpm.fish: Added package completion functionality
+
+ * init/completions/yum.fish: Added completions for yum command
+
+
+2005-08-24 Horea Haitonic <horeah@gmail.com>
+
+ * init/completions/diff.fish: Updated completions for diff command
+
+
+2005-08-24 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * util.c (sb_printf): Added sb_printf function for formated output into string buffer
+ * util.c (sb_append_in, sb_append_float): Dropped various stringbuffer functions in favour of sb_printf
+
+ * common.c (debug): Added function for making debugging messages
+
+ * builtin.c, exec.c, parser.c: Error reporting cleanups
+
+ * reader.c (reader_write_title): Make console detection slightly less lame
+
+
+2005-08-22 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * parser.c (parse_job): Make implicit cd use wrapper function around cd if available
+
+ * parser.c (parser_push_block): Fixed bug causing some global variables to be invisible inside functions
+
+ * env.c (env_push_block, env_pop_block, env_set, local_Scope_exports): Keep track of exported variables in scope to decrease amount of times we have to reexport variable values
+
+ * init/fish.in: Make sure the installation directory is added to the PATH variable, fixes issues where fish won't find it's various subcommands when installed in uncommon locations
+
+2005-08-22 Horea Haitonic <horeah@gmail.com>
+
+ * builtin.c (builtin_cd): Remove support for 'cd -' from cd builtin
+
+ * init/fish_function.fish(cd): Add support for 'cd -' to cd shellfunction wrapper
+ * init/fish_function.fish(cd): Add support for directory history
+ * init/fish_function.fish(pushd,popd): Add support for directory stack
+
+
+2005-08-20 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * proc.c, reader.c (job_continue,set_signal_handlers): Make non-interactive mode use signals for handling children
+ * proc.c (job_continue): Use waitpid when there are no IO_BUFFER redirections
+
+2005-08-19 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * exec.c (exec, launch_process, handle_child_process, setup_child_process): Clean up exec.c, add buffering for functions and blocks.
+ * parser.c (parser_push_block): Fix bug causing while loops in functions to be evaluated when the function was defined
+
+ * reader.c (handle_winch, check_winch): window size environment variables where erroneously set by signal handlers, causing potential crash.
+
+ * init/fish (contains): Made 'contains' much, much faster
+
+ * exec.c (exec): Fix bug causing exec builtin to crash the shell
+
+
+2005-08-18 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * wildcard.c (get_desc): Re-add stat-call to command completion, but make sure only files that match the completion are stated.
+
+ * proc.c (io_add): io_add can now add together two chains of io redirections, which is needed when merging block-level and job-level redirections. Lack of this functionality caused a bug when doing complex io redirections.
+
+
+2005-08-18 Jan Fader <jan.fader@web.de>
+
+ * INSTALL: Changed the Chapter over "Local Install Procedure"
+
+ * configure.ac: Fixed not working installation to $HOME
+
+ * Makefile.in: Make echo at the end of the install target not verbose
+
+
+2005-08-16 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * init/completions/apropos.fish: Improve completions for aprops
+
+ * parser.c: Added profiling support. Set the PROFILE macro in parser.c to non-zero to enable.
+
+2005-08-15 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * init/completions/wget.fish: Add completions for wget command
+
+ * exec.c (exec_subshell): Remove null entries caused by invalid unicode
+
+2005-08-14 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * builtin_set.c (builtin_set): Fixed bug causing bug when changing variable scope without changing value
+
+ * exec.c, proc.c, parser.c: Added support for piping output of blocks
+
+2005-08-12 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * complete.c (complete_cmd_desc): New implementation of command description lookup, much faster!
+
+ * wildcard.c (get_desc): Avoid calling stat during command completion for performance reasons. Command filesizes are no longer printed.
+
+ * wutil.c (wgetcwd, wchdir): Add more wrapper functions
+ * builtin.c (builtin_cd): Use new wrappers in wutil
+
+ * wutil.c (wutil_wcs2str): Make convertions use internal buffer for better performance
+
+ * complete.c (complete_load): Add support for dynamic completion loading
+ * Makefile.in: Add build support for dynamic completion loading
+ * init/fish_complete: Split into multiple dynamically loaded parts
+ * builtin.c (builtin_complete): Add -y switch for requesting the loading of completions for a command
+
+ * env.c: Decrease the amount of exported environment variable recalculations
+
+ * reader.c, builtin.c (do_exit, builtin_exit): Fixed bug causing fish_on_exit function not to be evaluated in very rare cases
+
+2005-08-11 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * builtin_commandline.c (builtin_commandline): Split of to separate file
+ * builtin_commandline.c (builtin_commandline): Make the switches more logical and orthogonal
+
+ * builtin_commandline.c (builtin_commandline): Split of duplicate code to separate functions
+
+ * reader.c (reader_current_token_extent): Fix bug when a short string token follows a pipe or semicolon causing incorrect token identification
+
+ * reader.c (reader_current_job_extent, reader_current_process_extent): Add new extent computation commands
+
+
+2005-08-09 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * builtin.c (builtin_commandline): Add support for inserting readline functions from a command
+
+ * init/fish_inputrc: Change default behaviour of \C-d to __fish_delete_or_exit
+
+ * expand.c (expand_backslash): Add support for C-style character escapes for unicode, as well as octal and hexadecimal ascii, i.e. \uxxxx, \Uxxxxxxxx, \xxx and \oooo.
+
+ * init/fish_complete.fish: Added skin-specific completions for Valgrind
+
+ * complete.c (complete_param): Fixed bug causing some completions to be omitted
+
+ * init/fish_complete.fish: Added completions for the commandline builtin
+
+2005-08-09 Horea Haitonic <horeah@gmail.com>
+
+ * builtin_set.c (update_values): Improve behavior in assigning array-slices with improper number of arguments
+
+
+2005-08-08 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * exec.c (launch_process): Fix bug on redirection to self, i.e. >&1.
+
+ * env.c (env_set): Fix scoping misfeature causing improper scoping for exported variables
+
+ * env.c (env_set): Make it possible to explicitly unexport variables
+ * builtin_set.c (builtin_set): Add unexport switch to set builtin
+
+ * main.c (main): Fix broken login shell detection
+
+
+2005-08-08 Horea Haitonic <horeah@gmail.com>
+
+ * builtin_set.c: Completely new implementation of the 'set' builtin, supporting erasing sets of array elements.
+
+
+2005-08-05 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * intern.c (intern, intern_free_all): Add string pooling library, decreases memory usage by ~40kB
+
+ * exec.c proc.c (exec_subshell, ss_create, ss_wait, ss_clear, read_subshell): Remove old (forking) subshell implementation
+
+ * exec.c (exec): Make sure builtins don't fork when buffering output (slight performance increase, larger when using Valgrind)
+
+ * init/fish_complete.fish (__fish_complete_cd): Make sure we don't actually change the directory
+
+ * builtin.c (builtin_cd): Make sure OLDPWD is not changed when using cd in subshells
+
+ * builtin.c (builtin_read): Fixed bug causing random breakage
+
+
+2005-08-04 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * complete.c (complete_add, complete_param): Add support for conditional command completions
+
+ * builtin.c (builtin_complete): Add switches to the complete builtin to support conditional command completion
+
+ * util.c (al_set): Make sure unused array_list_t elements are zeroed
+
+
+2005-08-04 Horea Haitonic <horeah@gmail.com>
+
+ * util.c (al_push): Set new list size on resize
+
+
+2005-07-30 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * exec.c, proc.c (exec_subshell): Execute subshells in main process
+
+
+2005-07-28 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.12.1
+
+ * expand.c (expand_file_completion): Removed unneeded function, the program should call expand_string directly
+ * expand.c (expand_string): Remove unneeded wrapper, expand_string is now the main function
+
+ * complete.c (complete_param_expand): Renamed complete_param_file to complete_param_expand, since it does completions for more than just filenames
+
+ * complete.c (complete): Make sure process completion is always performed
+
+
+2005-07-27 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * init/fish_complete.fish: A common group completion function, used by chgrp and a few others
+ * init/fish_complete.fish: Tweak kill completion to work on non-Debian systems
+
+2005-07-26 Horea Haitonic <horeah@gmail.com>
+
+ * init/fish_complete.fish: Add completions for kill, zip and diff commands.
+ * init/fish_complete.fish: A common process completion function, used by ps and top
+
+2005-07-17 James Vega <jamessan@jamessan.com>
+
+ * doc_src/set_color.c, doc_src/tokenize.c, doc_src/mimedb.c: Add missing #includes
+
+2005-07-17 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * parser.c (parser_is_block, parser_is_subcommand, parsert_is_reserved): Changed name of functions parser_block_add, parser_subcommand and parser_reserved
+
+ * tokenizer.c, tokenizer.h (tok_init, tok_next): Changed return type to void
+
+ * doc_src/complete.txt: Added documentation for dynamic completion descriptions
+
+ * parser.c (parse_job, eval_job, eval, parse_job_main_loop): Remove unused tokenizer stacking support
+
+ * reader.c: Code cleanup, API documentation additions.
+
+2005-07-16 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * fish_tests.c: Add Library test functions
+
+ * configure.ac: Handle installation to subdirectories of $HOME properly
+
+ * tokenizer.c (tok_init): Add checks for invalid input
+
+ * parser.c (parser_test): Add checks for invalid input
+
+2005-07-15 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.12.0
+
+2005-07-15 Horea Haitonic <horeah@gmail.com>
+
+ * init/fish_complete.fish: Updated make completions to make them more robust
+
+2005-07-14 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * complete.c (complete_init, complete_destroy): Remove table with descriptions for executables
+
+ * complete.c (complete): Fixed bug where fish failed to find the command to complete
+
+ * complete.c (complete_add): Fixed missing initialization
+
+ * env.c (env_init): Removed remnant of $random variable
+
+ * init/fish_complete.fish: Fixed a few typos, misspelled words, etc
+ * init/fish_complete.fish: Make sure -x flag is set properly
+ * init/fish_complete.fish(__fish_print_users): Added generic username completion
+
+ * reader.c (completion_print): Fixed width miscalculation bug in completion listing code
+
+2005-07-13 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * common.c (str2wcs): Make the maximum error report count runtime configurable
+ * complete.c (complete): Do not report string convertion errors in completion mode
+
+ * builtin.c, builtin.h, exec.c (builtin_exec, builtin_run): Renamed builtin_exec to builtin_run to avoid nameclash when adding the builtin 'exec'
+ * builtin.c, parser.c, exec.c (builtin_exec, builtin_run, exec, parse_job): Add builtin 'exec'
+
+2005-07-08 James Vega <jamessan@jamessan.com>
+
+ * reader.c (reader_sanity_check): Remove useless sanity checks
+
+2005-07-08 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * autoconf.ac: Fixed docdir bug
+
+ * init/fish_funcion.fish (help): Fixed bug causing fish not to show right section for builtins
+
+2005-07-07 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * wildcard.c(wildcard_expand): File completion now includes file size
+
+ * init/fish_complete.fish: Tweaked completions
+
+2005-07-07 Yongjian Xu <i3dmaster@gmail.com>
+
+ * init/fish_complete.fish: Added loads of new completions
+
+2005-07-05 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * parser.c (parser_reserved): Added reserved words 'end', 'else', 'case', 'builtin' and 'command'
+
+2005-07-04 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * builtin.c (builtin_commandline): Add token handling functionality to commandline builtin
+ * parser.c, complete.c (parser_check_command, complete): Remove token handling functionality through environment variables - replaced by commandline builtin
+
+ * init/fish_functions.fish (contains,_contains_help): Added a contains function, for checking if a key is in a set
+
+2005-07-01 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * reader.c (reader_current_token_extent, reader_current_subshell_extent): Factor out token handling code
+
+ * reader.c (reader_readline, handle_token_history): Add token search functionality
+
+2005-06-27 James Vega <jamessan@jamessan.com>
+
+ * input.c (add_emacs_bindings): Fix spelling error causing broken keybindings
+
+2005-06-27 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * complete.c (complete_add,complete_parm): Add support for old and gnu style long option completion for the same command
+
+ * gen_hdr2.c (main): Added missing return statement
+
+
+2005-06-24 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * init/fish_complete.fish (__fish_complete_suffix): Add general purpose file completion function
+ * init/fish_complete.fish (__fish_complete_directory): Add general purpose directory completion function
+ * init/fish_complete.fish (__fish_rpm_complete): Add rpm completions
+ * init/fish_complete.fish (__fish_cd_complete): Make cd completion it's own function
+
+ * function.c (function_get_names,get_names_internal): Add support for hidden functions
+ * builtin.c (builtin_function,builtin_functions): Add support for hidden functions
+ * complete.c (complete_cmd): Add support for hidden functions
+
+ * init/fish_complete.fish: Update completions for set_color
+
+2005-06-19 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * input.c(input_init,add_emacs_bindings,add_common_bindings,add_vi_bindings): Add initial vi-mode implementation
+
+2005-06-17 James Vega <jamessan@jamessan.com>
+
+ * Makefile.in: Add -fno-strict-aliasing compiler flag
+
+2005-06-16 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.11.1!
+
+ * doc_src/doc.hdr: Small documentation tweak to work around a bug in Doxygen 1.4.3
+
+ * env.c (env_init): Make HOME variable writeable by root
+
+ * reader.c, configure.ac (writembs): Ugly kludge to make powerpc work
+
+ * parser.c (parser_reserved): Add function for checking if a word is reserved
+ * parser.c (parser_test): Made validator use parser_reserved function
+ * builtin.c(builtin_function): Made function definitions respect reserved words
+
+2005-06-14 Tom Arnett <tarnett@mailshell.com>
+
+ * init/fish_function.fish (prompt_pwd): Corrected the escape for the ellipsis character
+
+2005-06-14 James Vega <jamessan@jamessan.com>
+
+ * builtin.c (builtin_cd): Add support for '-' argument, which performs a cd to the previous working directory.
+ * builtin.c (set_pwd): Extract out common functionality for setting pwd.
+
+2005-06-15 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * proc.c (handle_child_status): Remove debug message
+
+ * init/fish_interactive.fish, highlight.h: Changed the color environment variables to lower case, since they are not global variables
+
+ * doc_src/doc.hdr: Updated sections on fish_prompt and fish_title to reflect that they are now functions, and not environment variables
+
+2005-06-14 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.11.0!
+
+ * common.c (str2wcs, error_reset): Add an upper limit to the number of minor errors to report for a single command.
+
+2005-06-13 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * configure.ac: Make default configuration directory to always be /etc, never $PREFIX/etc
+
+ * reader.c(write_prompt): Make fish_prompt a function
+ * init/fish_interactive.fish(fish_prompt): Make fish_prompt a function
+ * reader.c(write_title): Make fish_title a function
+
+ * init/fish: Make sure the PATH is sane
+
+ * builtin.c(builtin_builtin):Add support for -n option
+ * builtin.c(builtin_set):Add support for -n option
+ * builtin.c(builtin_functions):Add support for -n option
+
+ * init/fish_complete.fish: Add completions for scp command
+
+
+2005-06-11 Michael Wardle <michael@endbracket.net>
+
+ * input.c (input_parse_inputrc_line): tilde expand parameter to 'include'
+
+
+2005-06-11 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * expand.c (expand_brackets): Improve tab completion in bracket expantion context
+ * expand.c (expand_brackets): Fix handling of the comma character in bracket expantion
+
+
+2005-06-10 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * init/fish_complete.fish: Additional completions for mplayer
+ * init/fish_complete.fish: Completion information for the date command
+
+ * expand.h, wildcard.h: Moved various constants from wildcard.h to expand.h
+
+
+2005-06-10 Michael Wardle <michael@endbracket.net>
+
+ * doc_src/doc.hdr: Spelling correction
+ * doc_src/doc.hdr: Fixed description of \b escape
+
+ * init/fish_complete.fish: Drop error messages on missing files when completing ssh hosts
+
+
+2005-06-08 Bram Senders <bram@luon.net>
+
+ * init/fish_function.fish (help): Added www-browser and x-www-browser as one of the possible help browsers.
+
+
+2005-06-08 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * main.c (main): Event hooks are now functions and not environment variables
+ * reader.c (read_i): Event hooks are now functions and not environment variables
+ * reader.c (read_i): Added fish_on_exec hook
+
+ * builtin.c (builtin_functions): functions builtin can now be used to redefine the description of functions
+
+ * doc_src/doc.hdr: Changelog now lives in it's own file
+ * ChangeLog: Changelog now lives in it's own file
+
+
+2005-06-08 James Vega <jamessan@jamessan.com>
+
+ * main.c (main): Add support for the -i option (force interactive mode)
+
+ * Various files: A large number of code cleanups, removing warnings
+
+ * Various files: Insurmountable number of spelling fixes
+
+
+2005-06-08 Jan Fader <jan.fader@web.de>
+
+ * Makefile.in: Remove install_local option and replace it with correct handling of configuration files
+ * configure.ac: Remove install_local option and replace it with correct handling of configuration files
+
+
+2005-06-07 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * parser.c (skipped_exec): Fixed crash bug when using a switch statement in a function
+
+ * parser.c (parser_test): Fixed unhelpful error messages when evaluating a script
+
+ * init/fish_function.fish (help): Fixed text-based browsers going to the background even if they are console based
+ * init/fish_function.fish (vared): Added support for a help option
+
+
+2005-06-01 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.10.1!
+
+ * expand.c: %self now expands to the shells own pid
+
+ * complete.c: Fixed bug with completion descriptions for completion of command names containing spaces
+
+ * init/fish_function.fish: The help command was broken in the previous release
+
+ * init/fish_complete.fish: Fixed bug in tab completions for cd command. The 'new and improved' completion script in 1.10 was a bit buggy, it seems...
+
+ * expand.c: Using double dollars for creating map variables was accidentally broken. This meant among other things that the vared command didn't work. This bug was reported by Jan Fader
+
+ * init/fish_functions.fish: Improved error handling in the vared function
+ * init/fish_functions.fish: The vared command did not work when given the name of an undefined variable
+
+ * configure.ac: There where multiple issues with fish when compiled witout the xsel command. Jan Fader pointed these bugs out and provided patches for some of them.
+
+ * Makefile.in: Fixed build script bug causing the integrated help message for the commandline builtin to dissapear
+
+ * reader.c: Fixed rendering bug causing incorrect prompt after executing some keyboard shortcuts
+
+
+2005-05-28 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.10!
+
+ * builtin.c: Added a return builtin.
+
+ * builtin.c, function.c, complete.c: Functions can now have meaningful completion descriptions.
+ * builtin.c, complete.c: Builtin commands now have meaningful completion descriptions.
+ * A more robust method for finding the help documentation is used.
+ * If the current directory is very long, the prompt is ellipsised.
+ * Long commands are ellipsised instead of overflowing to the next line.
+ * Improved completions for cd, uniq, gcc and mplayer.
+ * Much improved performance in the apparently common case of holding down the enter key for a longer stretch of time.
+ * Fixed bug where the PWD variable was set incorrect on startup.
+ * Fixed bug where appending output to a non-existing file would fail.
+
+2005-05-24 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.9.2
+
+ * Error message from the cd command was missing a newline, which caused it to be erased by the prompt.
+ * Help browser is now put to the background if it is known to be graphical. Mikael Rasmussen suggested this.
+ * Added hook fish_on_return, specifying command to be called when fish returns from evaluating a command.
+ * Changed names of variables that are not exported to lower case.
+ * make uninstall missed a few files. This is now fixed, thanks to Jan Fader.
+ * Specifying a prefix to configure now works. Many people sent in patches for this, including Valient Gough, James Vega and Jan Fader.
+ * Valient Gough sent in patches to fixes multiple issues regarding building rpms.
+ * Fixed a few documentation issues. Several people, including Michael Rasmussen pointed these out.
+ * Fixed bug where files that can't be stat:ed cause fish to perform a double free when doing wildcard expantion.
+ * Fixed bug in manual page tab completion where completing a non-existing manual page results in bogus output.
+
+2005-05-18 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.9.1
+
+ * Fixed bug causing fish to hang on manual page tab completion if the whatis database can't be found. This bug has been reported by Gerben Versluis, Yongjian Xu and Ross Logan, but I couldn't pinpoint the source of the error before.
+ * Fixed bug causing fish to hang on command tab completion if the whatis database can't be found. This bug has been reported by Gerben Versluis, Yongjian Xu and Ross Logan, but I couldn't pinpoint the source of the error before.
+ * Fixed bug causing subshell output to be expanded
+ * Various minor bugs in gen_hdr2.c fixed, making documentation generation more robust. This change was sent in by Pierre Houston.
+ * The file gen_hdr.sh was simplified, and now works on OS X. This change was sent in by Pierre Houston.
+
+2005-05-14 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.9
+
+ * Changed quoting rules so that quoted and unquoted text can be mingled in one token
+ * Command names must may no longer contain variables. This is needed to correctly determine block scope.
+ * Functions definitions are now a block. The new syntax is much more suitable for complex functions.
+ * A new builtin, functions, for erasing and listing functions.
+ * Lots of minor cleanups, typedefed structs now end with '_t', a new string hashing function should decrease collisions in hash tables, etc.
+ * Variables can now be used as array indices.
+ * The set_color command can now be used to set the background color of the terminal.
+ * Added highlighting of grep matches and some additional colors for the ls command. These changes where submitted by Philip Ganchev.
+ * Minor performance increases.
+ * Moving a word left or right moves to the last space after the word.
+ * Completion pager is now much better at handling long completions and descriptions.
+ * Fish now uses the unicode ellipsis character instead of tripple dots when it can't fit a completion
+ * Line number for errors is now correctly calculated
+ * Error reports are more specific
+ * The commandline builtin can now be used to print the current commandline or append a string to the current commandline
+ * Fixed infinite loop when tab-completing inside curly brackets
+ * Fixed some of the IO redirection issues for functions
+ * Fixed bug that caused the $history variable to randomly misbehave.
+ * Fixed bug in file desctiption generation for strange filetypes.
+ * Fixed bug in completion description for variables where variable array descriptions could contain illegal characters
+
+2005-04-29 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.8
+
+ * Fixed bug that made 'make uninstall' miss some files
+ * Fixed bug where subshells with quoted parentesis in them would result in errors
+ * Fixed crash bug when manually erasing a completion with 'complete -e'
+ * Fixed bug causing completion list to have larger column width than needed in some cases
+ * Fixed broken header causing wide characters to not work correctly. Both Adam Rice and Yongjian Xu have been a great help in making this work.
+ * Vastly improved startup time
+ * A large number of smaller updates and additions to the documentation
+ * Completion description for manual page completion is now the whatis information
+ * Completion description for commands now search manual sections 8, n, o and l in addition to section 1
+ * Matching parenthesis and quotes are now highlighted as they are typed, not just when moving the cursor over them
+ * Remove debug message on tab-completing directory from CDPATH
+ * Tab completion displays list of completions on single tap if completions have no common prefix
+ * Fish does not redraw the completion list on multiple tab taps
+ * It is now possible to specify an initial string for the read builtin
+ * New vared function for editing the value of a variable
+ * New commandline builtin for changing the command buffer
+ * History search now searches anywhere in the command
+ * History search highlights the matching string
+ * Changed output of set command when called with no arguments to be more readable
+
+2005-04-24 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.7.1
+
+ * Remove annoying debug message. Pointed out by Claudio Fontana.
+
+2005-04-23 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.7
+
+ * Support for environment variable arrays. $PATH[3] is the third element of the PATH variable, etc.
+ * You can set individual variable elements using set, i.e. 'set PATH[2] foo' replaces the third element of PATH with 'foo'
+ * You can create an array using set, i.e. 'set foo bar baz' creates an array variable 'foo' with the elements 'bar' and 'baz'
+ * aliases have been replaced by functions. The Use 'help function' for an introduction to functions.
+ * Arguments to scripts/functions are now contained in the environment variable array $args.
+ * functions can now be used in pipes
+ * complete.c: tab completion of directories in CDPATH for implicit cd now works.
+ * builtin.c: AThe read builtin now uses the internal line reading library, which means it supports all the advanced line reading capabilities that fish supports.
+ * : Debian packages are now available. Thank you to Dave Tait for making this happen. (This was really added to 1.6.1, but I forgot to mention it in the changelog)
+ * env.c: You can now access previous commands through the environment variable array named history
+ * init/fish_function.fish: Added keyboard shortcut Control-r for replacing text in the current commandline
+ * input.c: Changed a few keyboard shortcuts
+ * doc_src/doc.hdr: Documentation updates
+ * : Fixed bug that broke several keyboard shortcuts
+ * reader.c: Check for double with characters through wcwidth
+
+2005-06-15 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.6.1
+
+ * util.c: Fixed buffer overflow with string_buffer
+
+ * complete.c: Fixed bug where tab-completion would not work on executable scripts
+
+ * reader.c: Fixed crash on terminals that do not provide some capabilities
+
+ * input.c: Alt-left and Alt-right keymappings where accidentaly reversed
+
+
+2005-04-09 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.6
+
+ * init/fish_complete.fish: Updated completions for set_color. Thanks to Phil Ganchev for the bug report.
+
+ * reader.c: Added several new readline functions, suggested by Doug Dimick and Phil Ganchev.
+
+ * : ^C breaks all loops, conditional blocks, etc when waiting for a job to finish.
+
+ * builtin.c: Minor tweaks of completion syntax after suggestions from Tijmen Baarda.
+
+ * parser.c: If you just write in the name of a directory to fish, fish will implicitly assume this is a cd command and change to that directory. Thanks to Phil Ganchev for the suggestion.
+
+ * highlight.c: Highlighting of matching parentheses and quotes.
+
+ * doc_src/doc.hdr: The usual batch of documentation updates.
+
+ * parser.c: fish now has an upper limit on the number of recursive alias calls. This prevents trashing/crashing on bad aliases.
+
+ * parser.c: fish makes sure that an alias does not unconditionally call itself. This prevents the most trivial types of accidental infinite recursion.
+
+
+2005-04-02 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.5.2
+
+ * : Fixed broken header that caused compilation issues
+
+
+2005-04-02 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.5.1
+
+ * reader.c: Fixed issue in completion display, where long completions might spill to the next line and cause uglyness
+
+ * highlight.c, reader.c: Fixed a bug in error explanation (^X) where gibberish would be printed in some cases
+
+ * reader.c: Fixed various minor issues with repainting the screen after status updates
+
+ * configure.ac, Makefile.in: fish can now be compiled without xsel by using './configure --without-xsel'. This means fish can now be compiled on systems without X headers.
+
+ * input.c: Initial support for reading escape sequences for special keys from an inputrc file.
+ * input.c: Initial support for binding a command to a key. Meta-w runs whatis on the current command. Read the /etc/fish_inputrc file for more information.
+
+ * reader.c: The LINES and COLUMNS variables are set to the correct terminal size
+
+
+2005-03-20 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.5
+
+ * complete.c: Tab completion of files now displays file descriptions now from the mimetype database
+ * mimedb.c: New mimedb command, can be used to look up mimetype, description and default action for a file, as well as launch the default handler of a file.
+
+ * builtin.c: Jobs command prints CPU activity for jobs (Linux only)
+
+ * highlight.c: Added syntax highligting for subshells
+
+ * complete.c: Added tab completion inside of subshells
+
+ * reader.c: ^X prints comments on possible problems
+
+ * main.c: 'fish -h' starts the help browser
+
+ * : Non-builtin commands shipped with fish have much improved help
+
+ * doc_src/doc.hdr: Various cleanups and rewrites of the documentation
+
+ * : Fixed accidental mixing of stream and fd based output which caused some status messages to become garbled.
+
+ * : Fixed bug hangup on commands such as 'ls --color|less', caused by process group weirdness. Thank you to Doug Dimick for reporting this issue.
+
+ * init/fish: Fixed problems with the linux frambuffer and unicode. Drew Ferguson reported this problem and went out of his way to help me solve it. Thanks!
+
+ * reader.c: Fixed Konsole Home and End keys not working. Thank you to Jason L. Buberel for reporting this issue.
+
+2005-03-11 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.4
+
+ * highlight.c: Unknown/misspelled options are now flaged red by syntax highlighting if tab completions have been specified for the
+command.
+
+ * builtin.c: Added 'break' and 'continue' loop control builtins.
+ * builtin.c: Added 'switch' and 'case' builtins for conditionally executing
+a block.
+
+ * doc_src/doc.hdr: Lots of smaller updates to the documentation.
+
+ * configure.ac: fish now uses autoconf to improve platform independance.
+
+ * kill.c: Fixed a bug in X clipboard support which could hang the shell.
+
+ * expand.c: Fixed a bug where environment variables were escaped when they shouldn't be. This bug caused som tab-completions for the screen command to break in 1.3
+
+ * complete.c: Fixed bug causing problems for tab-completion if PATH elements end with a slash
+
+ * fish_tests.c: Took the first steps towards a test suite. So far only some simple syntax tests are performed.
+
+ * init/fish: If LANG is unspecified, a default value of en_US.UTF-8 is set
+
+
+2005-03-07 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.3
+
+ * builtin.c: New syntax for if, while and for builtins.
+ * : Added support for X copy and paste inside of fish. Thank you to Stéphane Chazelas for pointers on how to do this.
+ * : Included the Xsel program, written by Conrad Parker, with fish. This is the wonderful program that allows fish to use X copy and paste.
+ * complete.c: Fixed bug causing slowdown in tab completion of wildcarded strings.
+ * reader.c: Avoid calling setupterm() more than once, since the NCurses version leaks memory and sometimes calls exit, possibly without an error message.
+ * set_color.c: Fixed bug in the set_color command that could cause accidental underlining of text. Oh no!
+ * init/fish_complete.fish: Added tab completions for w, xsel, xprop, btdownloadcurses.py and btdownloadheadless.py commands.
+ * doc_src/coplete.txt: Rewrote documentation for the complete command to make it easier to understand how command specific completions work.
+ * complete.c: Scripts with extensions are now described with language, i.e. Perl scripts are discribed as "Perl script", not "Executable"
+
+2005-02-27 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.2.1
+
+ * : Fixed obvious crash bug. I should REALLY try to put together a test suite for fish.
+ * env.c: Added support for changing locale while fish is running.
+
+2005-02-26 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.2
+
+ * INSTALL: The INSTALL file contained a dangerAous mistake, where users were advised to use a command which could cause an invalid /etc/shells. Thanks to Klaus Alexander Seistrup for spotting this.
+
+ * builtin.c: Fixed bug where sourcing a file when in interactive mode would reset signal handlers.
+
+ * builtin_help.c: Builtin help pages are now stored as narrow character strings. This avoids escape sequences being improperly displayed on some platforms. It also decreases memory usage by a few kB.
+
+ * util.c: The comparison function used for sorting completions has been changed so it won't cause duplicate entries.
+
+ * wildcard.c: Avoid returning excessive number of completions when tab-completing a parameter ending with a wildcard. Thanks to Klaus Alexander Seistrup for suggesting this.
+
+ * doc_src/fish.1: Added a man-page pointing to the main fish documentation. Thanks to Chris F.A. Johnson for suggesting this.
+
+ * doc_src/doc.hdr: Minor changes to the documentation.
+
+ * expand.c: Added tab completion for job id, with the job command as the description.
+
+ * init/fish_complete.fish: Added tab completions for ps, top, test, cvs, pine, chown and chgrp commands.
+
+ * builtin.c: Added CDPATH support for the cd builtin.
+
+
+2005-02-16 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.1.1
+
+ * init/fish: Fixed typo that caused spurious error messages on startup
+
+
+2005-02-16 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.1
+
+ * exec.c: Fixed possible segfault when using builtins in subshells (Subshells would sometimes read uninitialized data, no actual crashes encountered, but that might just be luck)
+
+ * expand.c: Completion descriptions for environment variables and for home directories (~USER).
+
+ * util.c: String sorting functions now sort strings that are a prefix to another string first (I.e. 'foo' comes before 'foobar')
+
+ * reader.c: Tab completion pager colors can be set by user
+
+ * init/fish_complete.fish: Updated tab completion command description lookup to only use section 1 manuals
+
+ * doc_src/doc.hdr: Added documentation chapter on environment variables
+ * doc_src/doc.hdr: Added search targets for help sections 'color', 'prompt', 'title', 'variables' and 'expansion'
+ * doc_src/doc.hdr: Added BSD license info to documentation (About 100 lines of fish code comes from OpenBSD and is BSD licensed)
+ * doc_src/doc.hdr: Minor additions and edits to documentation and initialization files
+
+ * init/fish_complete.fish: Added tab completions for bc, mv, uniq, wc and who
+
+
+2005-02-13 Axel Liljencrantz <axel@liljencrantz.se>
+
+ * Version 1.0
+
+ * Initial release, everything is new
+
diff --git a/Doxyfile b/Doxyfile
new file mode 100644
index 00000000..e416b665
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,1161 @@
+# Doxyfile 1.3.9.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = fish
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = 1
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of source
+# files, where putting all generated files in the same directory would otherwise
+# cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish,
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese,
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish,
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# This tag can be used to specify the encoding used in the generated output.
+# The encoding is not always determined by the language that is chosen,
+# but also whether or not the output is meant for Windows or non-Windows users.
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES
+# forces the Windows encoding (this is the default for the Windows binary),
+# whereas setting the tag to NO uses a Unix-style encoding (the default for
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is used
+# as the annotated text. Otherwise, the brief description is used as-is. If left
+# blank, the following values are used ("$name" is automatically replaced with the
+# name of the entity): "The $name class" "The $name widget" "The $name file"
+# "is" "provides" "specifies" "contains" "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF = YES
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited
+# members of a class in the documentation of that class as if those members were
+# ordinary class members. Constructors, destructors and assignment operators of
+# the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like the Qt-style comments (thus requiring an
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF = YES
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member
+# documentation.
+
+DETAILS_AT_TOP = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources
+# only. Doxygen will then generate output that is more tailored for Java.
+# For instance, namespaces will be presented as packages, qualified scopes
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation.
+
+SHOW_DIRECTORIES = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT =
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp
+# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
+
+FILE_PATTERNS = *.h *.c
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories
+# that are symbolic links (a Unix filesystem feature) are excluded from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+
+EXCLUDE_PATTERNS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output. If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default)
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default)
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse the
+# parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or
+# super classes. Setting the tag to NO turns the diagrams off. Note that this
+# option is superseded by the HAVE_DOT option below. This is only a fallback. It is
+# recommended to install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = YES
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = NO
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will
+# generate a call dependency graph for every global function or class method.
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found on the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_WIDTH = 750
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes that
+# lay further from the root node will be omitted. Note that setting this option to
+# 1 or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that a graph may be further truncated if the graph's image dimensions are
+# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT).
+# If 0 is used for the depth value (the default), the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO
diff --git a/Doxyfile.user b/Doxyfile.user
new file mode 100644
index 00000000..cd3c87cc
--- /dev/null
+++ b/Doxyfile.user
@@ -0,0 +1,161 @@
+PROJECT_NAME = fish
+PROJECT_NUMBER = 1
+OUTPUT_DIRECTORY = user_doc
+CREATE_SUBDIRS = NO
+OUTPUT_LANGUAGE = English
+USE_WINDOWS_ENCODING = NO
+BRIEF_MEMBER_DESC = YES
+REPEAT_BRIEF = YES
+ABBREVIATE_BRIEF = YES
+ALWAYS_DETAILED_SEC = NO
+INLINE_INHERITED_MEMB = NO
+FULL_PATH_NAMES = YES
+STRIP_FROM_PATH =
+STRIP_FROM_INC_PATH =
+SHORT_NAMES = NO
+JAVADOC_AUTOBRIEF = YES
+MULTILINE_CPP_IS_BRIEF = NO
+DETAILS_AT_TOP = NO
+INHERIT_DOCS = YES
+DISTRIBUTE_GROUP_DOC = NO
+TAB_SIZE = 8
+ALIASES =
+OPTIMIZE_OUTPUT_FOR_C = YES
+OPTIMIZE_OUTPUT_JAVA = NO
+SUBGROUPING = YES
+EXTRACT_ALL = NO
+EXTRACT_PRIVATE = NO
+EXTRACT_STATIC = YES
+EXTRACT_LOCAL_CLASSES = YES
+EXTRACT_LOCAL_METHODS = NO
+HIDE_UNDOC_MEMBERS = NO
+HIDE_UNDOC_CLASSES = NO
+HIDE_FRIEND_COMPOUNDS = NO
+HIDE_IN_BODY_DOCS = NO
+INTERNAL_DOCS = NO
+CASE_SENSE_NAMES = YES
+HIDE_SCOPE_NAMES = NO
+SHOW_INCLUDE_FILES = YES
+INLINE_INFO = YES
+SORT_MEMBER_DOCS = YES
+SORT_BRIEF_DOCS = NO
+SORT_BY_SCOPE_NAME = NO
+GENERATE_TODOLIST = YES
+GENERATE_TESTLIST = YES
+GENERATE_BUGLIST = YES
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS =
+MAX_INITIALIZER_LINES = 30
+SHOW_USED_FILES = YES
+SHOW_DIRECTORIES = YES
+QUIET = NO
+WARNINGS = YES
+WARN_IF_UNDOCUMENTED = YES
+WARN_IF_DOC_ERROR = YES
+WARN_FORMAT = "$file:$line: $text"
+WARN_LOGFILE =
+INPUT =
+FILE_PATTERNS = doc.h
+RECURSIVE = NO
+EXCLUDE =
+EXCLUDE_SYMLINKS = NO
+EXCLUDE_PATTERNS =
+EXAMPLE_PATH =
+EXAMPLE_PATTERNS =
+EXAMPLE_RECURSIVE = NO
+IMAGE_PATH =
+INPUT_FILTER =
+FILTER_PATTERNS =
+FILTER_SOURCE_FILES = NO
+SOURCE_BROWSER = NO
+INLINE_SOURCES = NO
+STRIP_CODE_COMMENTS = YES
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION = YES
+VERBATIM_HEADERS = YES
+ALPHABETICAL_INDEX = NO
+COLS_IN_ALPHA_INDEX = 5
+IGNORE_PREFIX =
+GENERATE_HTML = YES
+HTML_OUTPUT = html
+HTML_FILE_EXTENSION = .html
+HTML_HEADER = user_doc.head.html
+HTML_FOOTER =
+HTML_STYLESHEET =
+HTML_ALIGN_MEMBERS = YES
+GENERATE_HTMLHELP = NO
+CHM_FILE =
+HHC_LOCATION =
+GENERATE_CHI = NO
+BINARY_TOC = NO
+TOC_EXPAND = NO
+DISABLE_INDEX = YES
+ENUM_VALUES_PER_LINE = 4
+GENERATE_TREEVIEW = NO
+TREEVIEW_WIDTH = 250
+GENERATE_LATEX = YES
+LATEX_OUTPUT = latex
+LATEX_CMD_NAME = latex
+MAKEINDEX_CMD_NAME = makeindex
+COMPACT_LATEX = NO
+PAPER_TYPE = a4wide
+EXTRA_PACKAGES =
+LATEX_HEADER =
+PDF_HYPERLINKS = YES
+USE_PDFLATEX = YES
+LATEX_BATCHMODE = NO
+LATEX_HIDE_INDICES = NO
+GENERATE_RTF = NO
+RTF_OUTPUT = rtf
+COMPACT_RTF = NO
+RTF_HYPERLINKS = NO
+RTF_STYLESHEET_FILE =
+RTF_EXTENSIONS_FILE =
+GENERATE_MAN = NO
+MAN_OUTPUT = man
+MAN_EXTENSION = .3
+MAN_LINKS = NO
+GENERATE_XML = NO
+XML_OUTPUT = xml
+XML_SCHEMA =
+XML_DTD =
+XML_PROGRAMLISTING = YES
+GENERATE_AUTOGEN_DEF = NO
+GENERATE_PERLMOD = NO
+PERLMOD_LATEX = NO
+PERLMOD_PRETTY = YES
+PERLMOD_MAKEVAR_PREFIX =
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = NO
+EXPAND_ONLY_PREDEF = NO
+SEARCH_INCLUDES = YES
+INCLUDE_PATH =
+INCLUDE_FILE_PATTERNS =
+PREDEFINED =
+EXPAND_AS_DEFINED =
+SKIP_FUNCTION_MACROS = YES
+TAGFILES =
+GENERATE_TAGFILE =
+ALLEXTERNALS = NO
+EXTERNAL_GROUPS = YES
+PERL_PATH = /usr/bin/perl
+CLASS_DIAGRAMS = YES
+HIDE_UNDOC_RELATIONS = YES
+HAVE_DOT = NO
+CLASS_GRAPH = NO
+COLLABORATION_GRAPH = YES
+UML_LOOK = NO
+TEMPLATE_RELATIONS = NO
+INCLUDE_GRAPH = NO
+INCLUDED_BY_GRAPH = YES
+CALL_GRAPH = YES
+GRAPHICAL_HIERARCHY = YES
+DOT_IMAGE_FORMAT = png
+DOT_PATH =
+DOTFILE_DIRS =
+MAX_DOT_GRAPH_WIDTH = 750
+MAX_DOT_GRAPH_HEIGHT = 1024
+MAX_DOT_GRAPH_DEPTH = 0
+GENERATE_LEGEND = YES
+DOT_CLEANUP = YES
+SEARCHENGINE = NO
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 00000000..078aae32
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,62 @@
+
+Known issues
+============
+
+Older versions of Doxygen has bugs in the man-page generation which
+cause the builtin help to render incorrectly. Version 1.2.14 is known
+to have this problem.
+
+In version 1.9.2, the installation prefix for fish rpms and debs has
+changed from /usr/local to /usr. The package should automatically
+change any instances of /usr/local/bin/fish in /etc/passwd to
+/usr/bin/fish, but some programs, like screen, may need to be
+restarted to notice the changes. You may also run into problems when
+switching between using a package and personal builds.
+
+
+Prerequisites
+=============
+
+Fish requires the following packages to build:
+
+ - Doxygen
+ - Curses or Ncurses
+
+fish also relies on standard unix tools such as cat, cut, grep, sed,
+whoami and echo. Fish does not support cross-compilation, separate
+build directories or any other fancy configure options. Use a recent
+version of Doxygen, since older versions have bugs that make the
+builtin help pages render incorrectly. Version 1.2.14 is known to be
+broken.
+
+
+Simple install procedure
+========================
+
+ % ./configure
+ % make #Compile fish
+ % make install #Install fish
+ % echo /usr/local/bin/fish >>/etc/shells #Add fish to list of shells
+
+If you wish to use fish as your default shell, use the following
+command:
+
+ % chsh -s /usr/local/bin/fish
+
+chsh will prompt you for your password, and change your default shell.
+
+
+Local install procedure
+=======================
+
+To install fish in your own home directory (typically as non-root),
+type:
+
+ % ./configure --prefix=$HOME
+ % make # Compile fish
+ % make install # Install fish
+
+You will not be able to use fish as the default shell unless you also
+add the corresponding line to /etc/shells, which kind of defeats the
+point of a local install. But you can at least build and run fish.
+
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 00000000..99ccd2a0
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,452 @@
+#
+# Copyright (C) 2005 Axel Liljencrantz
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+
+#
+# Makefile for the fish shell. Can build fish and associated
+# applications, install them, recalculate dependencies and also create
+# binary distributions in tar.bz2 tar.gz and rpm formats.
+#
+
+#
+# The fish buildprocess is quite complex. Do not stare directly into
+# the Makefile. Doing so may cause nausea, dizziness and
+# hallucinations.
+#
+
+# Compiler flags
+
+CC := @CC@
+CFLAGS := @CFLAGS@ @INCLUDEDIR@ -Wall -std=gnu99 -fno-strict-aliasing
+CPPFLAGS=@CPPFLAGS@
+LDFLAGS:= -l@CURSESLIB@ @LDFLAGS@ @LIBDIR@
+INSTALL:=@INSTALL@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+mandir = @mandir@
+sysconfdir = @sysconfdir@
+fishdir = @fishdir@
+fishfile = @fishfile@
+fishinputfile = @fishinputfile@
+docdir = @docdir@
+
+# All objects used by fish, that are compiled from an ordinary .c file
+# using an ordinary .h file.
+COMMON_OBJS := function.o builtin.o common.o complete.o env.o exec.o \
+ expand.o highlight.o history.o kill.o parser.o proc.o reader.o \
+ sanity.o tokenizer.o util.o wildcard.o wgetopt.o wutil.o input.o \
+ output.o intern.o env_universal.o env_universal_common.o input_common.o
+
+# builtin_help.h exists, but builtin_help.c is autogenerated
+COMMON_OBJS_WITH_HEADER := builtin_help.o
+
+# main.c exists, but main.h does not, etc.
+COMMON_OBJS_WITH_CODE := builtin_set.o builtin_commandline.o
+
+# All objects that the system needs to build fish
+FISH_OBJS := $(COMMON_OBJS) $(COMMON_OBJS_WITH_CODE) $(COMMON_OBJS_WITH_HEADER) main.o
+FISH_PAGER_OBJS := fish_pager.o common.o output.o util.o wutil.o tokenizer.o input_common.o env_universal.o env_universal_common.o
+FISH_TESTS_OBJS := $(COMMON_OBJS) $(COMMON_OBJS_WITH_CODE) $(COMMON_OBJS_WITH_HEADER) fish_tests.o
+FISHD_OBJS := fishd.o env_universal_common.o common.o util.o wutil.o \
+
+
+#All objects that the system needs to build mimedb
+MIME_OBJS := mimedb.o xdgmimealias.o xdgmime.o xdgmimeglob.o \
+ xdgmimeint.o xdgmimemagic.o xdgmimeparent.o
+
+#
+# Files containing documentation for builtins. Should be listed
+# alphabetically, since this is the order in which they will be written
+# in the help file.
+#
+
+BUILTIN_DOC_SRC := doc_src/source.txt doc_src/and.txt \
+ doc_src/begin.txt doc_src/bg.txt doc_src/bind.txt \
+ doc_src/break.txt doc_src/builtin.txt doc_src/case.txt \
+ doc_src/cd.txt doc_src/command.txt doc_src/commandline.txt \
+ doc_src/complete.txt doc_src/continue.txt doc_src/else.txt \
+ doc_src/end.txt doc_src/eval.txt doc_src/exec.txt doc_src/exit.txt \
+ doc_src/fg.txt doc_src/for.txt doc_src/function.txt \
+ doc_src/functions.txt doc_src/if.txt doc_src/jobs.txt \
+ doc_src/not.txt doc_src/or.txt doc_src/random.txt \
+ doc_src/return.txt doc_src/read.txt doc_src/set.txt \
+ doc_src/switch.txt doc_src/while.txt
+
+#
+# Files generated by running doxygen on the files in $(BUILTIN_DOC_SRC)
+#
+BUILTIN_DOC_HDR := $(BUILTIN_DOC_SRC:.txt=.doxygen)
+
+#
+# Files containing documentation for external commands. Should be listed
+# alphabetically, since this is the order in which they will be written
+# in the help file.
+#
+
+CMD_DOC_SRC := doc_src/count.txt doc_src/dirh.txt doc_src/dirs.txt \
+ doc_src/help.txt doc_src/mimedb.txt doc_src/nextd.txt \
+ doc_src/open.txt doc_src/popd.txt doc_src/prevd.txt \
+ doc_src/pushd.txt doc_src/set_color.txt doc_src/tokenize.txt
+
+#
+# Files generated by running doxygen on the files in $(CMD_DOC_SRC)
+#
+CMD_DOC_HDR := $(CMD_DOC_SRC:.txt=.doxygen)
+
+TEST_IN := $(wildcard tests/test*.in)
+
+#
+# Files that should be added to the tar archives
+#
+
+# Files in ./doc_src/
+DOC_SRC_DIR_FILES := doc_src/Doxyfile.in doc_src/doc.hdr \
+ $(BUILTIN_DOC_SRC) $(CMD_DOC_SRC) doc_src/fish.1.in
+
+# Files in ./
+MAIN_DIR_FILES := Doxyfile Doxyfile.user Makefile.in configure \
+ configure.ac config.h.in install-sh set_color.c count.c \
+ key_reader.c tokenize.c gen_hdr.sh gen_hdr2.c $(MIME_OBJS:.o=.h) \
+ $(MIME_OBJS:.o=.c) $(COMMON_OBJS_WITH_HEADER:.o=.h) \
+ $(COMMON_OBJS:.o=.h) $(COMMON_OBJS_WITH_CODE:.o=.c) \
+ $(COMMON_OBJS:.o=.c) builtin_help.hdr fish.spec.in INSTALL README \
+ user_doc.head.html xsel-0.9.6.tar ChangeLog config.sub \
+ config.guess fish_tests.c main.c fish_pager.c fishd.c
+
+# Files in ./init/
+INIT_DIR_FILES :=init/fish.in init/fish_complete.fish \
+ init/fish_function.fish init/fish_inputrc \
+ init/fish_interactive.fish
+
+# Files in ./tests/
+TESTS_DIR_FILES := $(TEST_IN) $(TEST_IN:.in=.out) $(TEST_IN:.in=.err) \
+ $(TEST_IN:.in=.status) tests/test.fish tests/gen_output.fish
+
+COMPLETIONS_DIR_FILES := $(wildcard init/completions/*.fish)
+
+# Programs to build
+PROGRAMS:=fish set_color tokenize @XSEL@ mimedb count fish_pager fishd
+
+# Manuals to install
+MANUALS:=doc_src/fish.1 @XSEL_MAN_PATH@ \
+ doc_src/builtin_doc/man/man1/mimedb.1 \
+ doc_src/builtin_doc/man/man1/set_color.1 \
+ doc_src/builtin_doc/man/man1/tokenize.1 \
+ doc_src/builtin_doc/man/man1/count.1
+
+#Make everything needed for installing fish
+all: $(PROGRAMS) user_doc
+
+# User documentation, describing the features of the fish shell.
+user_doc: doc.h Doxyfile.user user_doc.head.html
+ doxygen Doxyfile.user
+
+#Source code documentation. Also includes user documentation.
+doc: *.h *.c doc.h Doxyfile builtin_help.c
+ doxygen;
+
+# PDF version of the source code documentation.
+doc/refman.pdf: doc
+ cd doc/latex;
+ make;
+ mv refman.pdf ..;
+ cd ../..;
+ rm -r doc/latex;
+
+test: $(PROGRAMS) fish_tests
+ ./fish_tests; cd tests; ../fish <test.fish;
+
+xsel-0.9.6:
+ tar -xf xsel-0.9.6.tar
+
+xsel-0.9.6/xsel: xsel-0.9.6
+ cd xsel-0.9.6; ./configure; make
+
+# doc.h is a compilation of the various snipptes of text used both for
+# the user documentation and for internal help functions into a single
+# file that can be parsed dy Doxygen to generate the user
+# documentation.
+doc.h:$(BUILTIN_DOC_SRC) $(CMD_DOC_SRC) doc_src/doc.hdr
+ cat doc_src/doc.hdr >doc.h;
+ echo "/** \page builtins Builtin commands" >>doc.h;
+ cat $(BUILTIN_DOC_SRC) >>doc.h;
+ echo "*/" >>doc.h
+ echo "/** \page commands External commands" >>doc.h;
+ echo "\c fish is shipped with commands which do not use any internal parts of the shell, and are therefore not written as builtins, but separate commands." >>doc.h
+ cat $(CMD_DOC_SRC) >>doc.h;
+ echo "*/" >>doc.h
+
+# This rule creates complete doxygen headers from each of the various
+# snipptes of text used both for the user documentation and for
+# internal help functions, that can be parsed to Doxygen to generate
+# the internal help function text.
+%.doxygen:%.txt
+ echo "/** \page " `basename $*` >$@;
+ cat $*.txt >>$@;
+ echo "*/" >>$@
+
+# Generate the internal help functions by making doxygen create
+# man-pages which are then converted into C code. The convertion path
+# looks like this:
+#
+# .txt file
+# ||
+# (make)
+# ||
+# \/
+# .doxygen file
+# ||
+# (doxygen)
+# ||
+# \/
+# man file
+# ||
+# (man)
+# ||
+# \/
+# formated text
+# with escape
+# sequences
+# ||
+# \/
+# (gen_hdr2)
+# ||
+# \/
+# .c file
+#
+# Which is an awful, clunky and ugly way of producing
+# documentation. There ought to be something simpler.
+
+builtin_help.c: $(BUILTIN_DOC_HDR) doc_src/count.doxygen gen_hdr2 gen_hdr.sh builtin_help.hdr $(CMD_DOC_HDR)
+ cd doc_src; doxygen; cd ..;
+ cp builtin_help.hdr builtin_help.c;
+ for i in $(BUILTIN_DOC_HDR) doc_src/count.doxygen ; do \
+ echo ' hash_put( &tbl, L"'`basename $$i .doxygen`'",' >>$@; \
+ ./gen_hdr.sh $$i >>$@; \
+ echo " );" >>$@; \
+ echo >>$@; \
+ done;
+ echo "}" >>builtin_help.c
+
+#
+# Generate help texts for external fish commands, like set_color and
+# mimedb. Depends on builtin_help.c to make sure doxygen gets run to
+# generate the man files.
+#
+%.c : %.doxygen gen_hdr2 builtin_help.c
+ echo "// This file was automatically generated, do not edit" >$@
+ echo "#include <stdlib.h>" >>$@
+ echo "#include <stdio.h>" >>$@
+ echo >>$@
+ echo "void print_help()" >>$@
+ echo "{" >>$@
+ echo ' printf( "%s",' >>$@
+ ./gen_hdr.sh $*.doxygen >>$@
+ echo ");" >>$@
+ echo "}" >>$@
+#man -- doc_src/builtin_doc/man/man1/`basename $@ .c`.1 | cat -s | ./gen_hdr2 >>$@
+
+install: all
+ $(INSTALL) -m 755 -d $(DESTDIR)$(bindir)
+ for i in $(PROGRAMS); do\
+ $(INSTALL) -m 755 $$i $(DESTDIR)$(bindir) ; \
+ done;
+ $(INSTALL) -m 755 -d $(DESTDIR)$(sysconfdir)$(fishdir)
+ $(INSTALL) -m 755 -d $(DESTDIR)$(sysconfdir)$(fishdir)/completions
+ $(INSTALL) -m 644 init/fish $(DESTDIR)$(sysconfdir)$(fishfile)
+ for i in init/fish_interactive.fish init/fish_function.fish init/fish_complete.fish ; do \
+ $(INSTALL) -m 644 $$i $(DESTDIR)$(sysconfdir)$(fishdir); \
+ done;
+ for i in $(COMPLETIONS_DIR_FILES); do \
+ $(INSTALL) -m 644 $$i $(DESTDIR)$(sysconfdir)$(fishdir)/completions/; \
+ done;
+ $(INSTALL) -m 644 init/fish_inputrc $(DESTDIR)$(sysconfdir)$(fishinputfile);
+ $(INSTALL) -m 755 -d $(DESTDIR)$(docdir)
+ for i in user_doc/html/* ChangeLog; do \
+ $(INSTALL) -m 644 $$i $(DESTDIR)$(docdir); \
+ done;
+ $(INSTALL) -m 755 -d $(DESTDIR)$(mandir)/man1
+ for i in $(MANUALS); do \
+ $(INSTALL) -m 644 $$i $(DESTDIR)$(mandir)/man1/; \
+ done;
+ @echo If you want to use fish as the default shell, remember to first
+ @echo add the line \'$(DESTDIR)$(bindir)/fish\' to the file \'/etc/shells\'.
+
+uninstall:
+ for i in $(PROGRAMS); do \
+ rm -f $(DESTDIR)$(bindir)/$$i; \
+ done;
+ rm -f $(DESTDIR)$(bindir)/xsel
+ rm -f $(DESTDIR)$(sysconfdir)$(fishfile)
+ rm -f $(DESTDIR)$(sysconfdir)$(fishinputfile)
+ rm -r $(DESTDIR)$(sysconfdir)$(fishdir)
+ rm -r $(DESTDIR)$(docdir)
+ for i in fish.1* @XSEL_MAN@ mimedb.1* set_color.1* tokenize.1* count.1*; do \
+ rm $(DESTDIR)$(mandir)/man1/$$i; \
+ done;
+
+# The fish shell
+fish: $(FISH_OBJS)
+ $(CC) $(FISH_OBJS) $(LDFLAGS) -o $@
+
+fish_pager: $(FISH_PAGER_OBJS)
+ $(CC) $(FISH_PAGER_OBJS) $(LDFLAGS) -o $@
+
+fishd: $(FISHD_OBJS)
+ $(CC) $(FISHD_OBJS) $(LDFLAGS) -o $@
+
+fish_tests: $(FISH_TESTS_OBJS)
+ $(CC) $(FISH_TESTS_OBJS) $(LDFLAGS) -o $@
+
+
+mimedb: $(MIME_OBJS) util.o common.o doc_src/mimedb.c
+ $(CC) ${MIME_OBJS} util.o common.o doc_src/mimedb.c $(LDFLAGS) -o $@
+
+set_color: set_color.o doc_src/set_color.c
+ $(CC) set_color.o doc_src/set_color.c $(LDFLAGS) -o $@
+
+tokenize: tokenize.o doc_src/tokenize.c
+ $(CC) tokenize.o doc_src/tokenize.c $(LDFLAGS) -o $@
+
+# Test program for the tokenizer library
+tokenizer_test: tokenizer.c tokenizer.h util.o wutil.o common.o
+ $(CC) ${CFLAGS} tokenizer.c util.o wutil.o common.o -D TOKENIZER_TEST $(LDFLAGS) -o $@
+
+depend:
+ makedepend -fMakefile.in -Y *.c
+
+# Copy all the source files into a new directory and use tar to create
+# an archive from it. Simplest way I could think of to make an archive
+# witout backups, autogenerated files, etc.
+#
+# Uses install instead of mkdir so build won't fail if the directory
+# exists
+fish-@PACKAGE_VERSION@.tar: $(DOC_SRC_DIR_FILES) $(MAIN_DIR_FILES) $(INIT_DIR_FILES) $(TEST_DIR_FILES) $(COMPLETIONS_DIR_FILES) ChangeLog
+ rm -rf fish-@PACKAGE_VERSION@
+ $(INSTALL) -d fish-@PACKAGE_VERSION@
+ $(INSTALL) -d fish-@PACKAGE_VERSION@/doc_src
+ $(INSTALL) -d fish-@PACKAGE_VERSION@/init
+ $(INSTALL) -d fish-@PACKAGE_VERSION@/init/completions
+ $(INSTALL) -d fish-@PACKAGE_VERSION@/tests
+ cp -f $(DOC_SRC_DIR_FILES) fish-@PACKAGE_VERSION@/doc_src
+ cp -f $(MAIN_DIR_FILES) fish-@PACKAGE_VERSION@/
+ cp -f $(INIT_DIR_FILES) fish-@PACKAGE_VERSION@/init/
+ cp -f $(COMPLETIONS_DIR_FILES) fish-@PACKAGE_VERSION@/init/completions/
+ cp -f $(TESTS_DIR_FILES) fish-@PACKAGE_VERSION@/tests/
+ tar -c fish-@PACKAGE_VERSION@ >fish-@PACKAGE_VERSION@.tar
+ rm -rf fish-@PACKAGE_VERSION@
+
+fish-@PACKAGE_VERSION@.tar.gz: fish-@PACKAGE_VERSION@.tar
+ gzip -f --best -c fish-@PACKAGE_VERSION@.tar >fish-@PACKAGE_VERSION@.tar.gz
+
+fish-@PACKAGE_VERSION@.tar.bz2: fish-@PACKAGE_VERSION@.tar
+ bzip2 -f --best -k fish-@PACKAGE_VERSION@.tar
+
+# Create .rpm file for the current systems architecture and an
+# .src.rpm file.
+rpm: fish-@PACKAGE_VERSION@.tar.bz2
+ cp fish.spec /usr/src/redhat/SPECS/
+ cp fish-@PACKAGE_VERSION@.tar.bz2 /usr/src/redhat/SOURCES/
+ rpmbuild -ba --clean /usr/src/redhat/SPECS/fish.spec
+ mv /usr/src/redhat/RPMS/*/fish*@PACKAGE_VERSION@*.rpm .
+ mv /usr/src/redhat/SRPMS/fish*@PACKAGE_VERSION@*.src.rpm .
+
+clean:
+ rm -f *.o doc.h doc_src/*.doxygen doc_src/*.c builtin_help.c
+ rm -f config.status config.log config.h Makefile
+ rm -f tokenizer_test fish key_reader set_color tokenize gen_hdr2 mimedb
+ rm -f fish-@PACKAGE_VERSION@.tar
+ rm -f fish-@PACKAGE_VERSION@.tar.gz
+ rm -f fish-@PACKAGE_VERSION@.tar.bz2
+ rm -rf doc;
+ rm -rf user_doc;
+ rm -rf doc_src/builtin_doc
+ rm -rf fish-@PACKAGE_VERSION@
+ rm -rf xsel-0.9.6/
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+
+builtin.o: config.h util.h wutil.h builtin.h function.h complete.h proc.h
+builtin.o: parser.h reader.h env.h expand.h common.h wgetopt.h sanity.h
+builtin.o: tokenizer.h builtin_help.h wildcard.h input_common.h input.h
+builtin.o: intern.h
+builtin_commandline.o: config.h util.h builtin.h common.h wgetopt.h reader.h
+builtin_commandline.o: proc.h parser.h tokenizer.h input_common.h input.h
+builtin_help.o: config.h util.h common.h builtin_help.h
+builtin_set.o: config.h util.h builtin.h env.h expand.h common.h wgetopt.h
+builtin_set.o: proc.h parser.h
+common.o: config.h util.h wutil.h common.h expand.h proc.h wildcard.h
+common.o: parser.h
+complete.o: config.h util.h tokenizer.h wildcard.h proc.h parser.h function.h
+complete.o: complete.h builtin.h env.h exec.h expand.h common.h reader.h
+complete.o: history.h intern.h wutil.h
+env.o: config.h util.h wutil.h proc.h common.h env.h sanity.h expand.h
+env.o: history.h reader.h parser.h env_universal.h env_universal_common.h
+env_universal.o: util.h common.h wutil.h env_universal_common.h
+env_universal_common.o: util.h common.h wutil.h env_universal_common.h
+exec.o: config.h util.h common.h wutil.h proc.h exec.h parser.h builtin.h
+exec.o: function.h env.h wildcard.h sanity.h expand.h env_universal.h
+exec.o: env_universal_common.h
+expand.o: config.h util.h common.h wutil.h env.h proc.h parser.h expand.h
+expand.o: wildcard.h exec.h tokenizer.h complete.h
+fishd.o: util.h common.h wutil.h env_universal_common.h
+fish_pager.o: config.h util.h wutil.h common.h complete.h output.h
+fish_pager.o: input_common.h env_universal.h env_universal_common.h
+fish_tests.o: config.h util.h common.h proc.h reader.h builtin.h function.h
+fish_tests.o: complete.h wutil.h env.h expand.h parser.h tokenizer.h
+function.o: config.h util.h function.h proc.h parser.h common.h intern.h
+highlight.o: config.h util.h wutil.h highlight.h tokenizer.h proc.h parser.h
+highlight.o: builtin.h function.h env.h expand.h sanity.h common.h complete.h
+highlight.o: output.h
+history.o: config.h util.h wutil.h history.h common.h reader.h env.h sanity.h
+input.o: config.h util.h wutil.h reader.h proc.h common.h sanity.h
+input.o: input_common.h input.h parser.h env.h expand.h
+input_common.o: config.h util.h common.h wutil.h input_common.h
+input_common.o: env_universal.h env_universal_common.h
+intern.o: config.h util.h common.h intern.h
+kill.o: config.h util.h wutil.h kill.h proc.h sanity.h common.h env.h
+kill.o: expand.h exec.h parser.h
+main.o: config.h util.h common.h reader.h builtin.h function.h complete.h
+main.o: wutil.h env.h sanity.h proc.h parser.h expand.h intern.h
+mimedb.o: config.h xdgmime.h util.h
+output.o: config.h util.h wutil.h expand.h common.h output.h highlight.h
+parser.o: config.h util.h common.h wutil.h proc.h parser.h tokenizer.h exec.h
+parser.o: wildcard.h function.h builtin.h builtin_help.h env.h expand.h
+parser.o: reader.h sanity.h
+proc.o: config.h util.h wutil.h proc.h common.h reader.h sanity.h env.h
+reader.o: config.h util.h wutil.h highlight.h reader.h proc.h parser.h
+reader.o: complete.h history.h common.h sanity.h env.h exec.h expand.h
+reader.o: tokenizer.h kill.h input_common.h input.h function.h output.h
+sanity.o: config.h util.h common.h sanity.h proc.h history.h reader.h kill.h
+sanity.o: wutil.h
+set_color.o: config.h
+tokenize.o: config.h
+tokenizer.o: config.h util.h wutil.h tokenizer.h common.h wildcard.h
+util.o: config.h util.h common.h wutil.h
+wgetopt.o: config.h wgetopt.h wutil.h
+wildcard.o: config.h util.h wutil.h complete.h common.h wildcard.h reader.h
+wildcard.o: expand.h
+wutil.o: config.h util.h common.h wutil.h
+xdgmimealias.o: xdgmimealias.h xdgmime.h xdgmimeint.h
+xdgmime.o: xdgmime.h xdgmimeint.h xdgmimeglob.h xdgmimemagic.h xdgmimealias.h
+xdgmime.o: xdgmimeparent.h
+xdgmimeglob.o: xdgmimeglob.h xdgmime.h xdgmimeint.h
+xdgmimeint.o: xdgmimeint.h xdgmime.h
+xdgmimemagic.o: xdgmimemagic.h xdgmime.h xdgmimeint.h
+xdgmimeparent.o: xdgmimeparent.h xdgmime.h xdgmimeint.h
diff --git a/README b/README
new file mode 100644
index 00000000..5b6ff982
--- /dev/null
+++ b/README
@@ -0,0 +1,15 @@
+How to find documentation for fish
+==================================
+
+The fish documentation is distributed in an intermediate format. To
+view it, you have to type:
+
+ % make user_doc
+
+Which will create the directory user_doc, containing html
+documentation for fish. If you build and install fish, the
+documentation will be available through the 'help' builtin.
+
+After installation, you can start fish by typing fish in the
+terminal. After fish has started, try using the help command for more
+information.
diff --git a/builtin.c b/builtin.c
new file mode 100644
index 00000000..92e4671b
--- /dev/null
+++ b/builtin.c
@@ -0,0 +1,2879 @@
+/** \file builtin.c
+ Functions for executing builtin functions.
+
+ How to add a new builtin function:
+
+ 1). Create a function in builtin.c with the following signature:
+
+ <tt>static int builtin_NAME( wchar_t ** args )</tt>
+
+ where NAME is the name of the builtin, and args is a zero-terminated list of arguments.
+
+ 2). Add a line like hash_put( &builtin, L"NAME", &builtin_NAME ); to builtin_init. This will enable the parser to find the builtin function.
+
+ 3). Add a line like hash_put( desc, L"NAME", L"Frobble the bloogle" ); to the proper part of builtin_get_desc, containing a short description of what the builtin does. This description is used by the completion system.
+
+ 4). Create a file names doc_src/NAME.txt, contining the manual for the builtin in Doxygen-format. Check the other builtin manuals for proper syntax.
+
+ 5). Add an entry to the BUILTIN_DOC_SRC variable of Makefile.in. Note that the entries should be sorted alpabetically!
+
+ 6). Add an entry to the manual at the builtin-overview subsection
+
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <unistd.h>
+#include <termios.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <signal.h>
+#include <wctype.h>
+#include <sys/time.h>
+
+#include "config.h"
+#include "util.h"
+#include "wutil.h"
+#include "builtin.h"
+#include "function.h"
+#include "complete.h"
+#include "proc.h"
+#include "parser.h"
+#include "reader.h"
+#include "env.h"
+#include "expand.h"
+#include "common.h"
+#include "wgetopt.h"
+#include "sanity.h"
+#include "tokenizer.h"
+#include "builtin_help.h"
+#include "wildcard.h"
+#include "input_common.h"
+#include "input.h"
+#include "intern.h"
+
+/**
+ The default prompt for the read command
+*/
+#define DEFAULT_READ_PROMPT L"set_color green; echo read; set_color normal; echo \"> \""
+
+/**
+ The mode name to pass to history and input
+*/
+
+#define READ_MODE_NAME L"fish_read"
+/**
+ Table of all builtins
+*/
+static hash_table_t builtin;
+
+int builtin_out_redirect;
+int builtin_err_redirect;
+
+/**
+ Buffers for storing the output of builtin functions
+*/
+string_buffer_t *sb_out=0, *sb_err=0;
+/**
+ Stack containing builtin I/O for recursive builtin calls.
+*/
+static array_list_t io_stack;
+
+/**
+ The file from which builtin functions should attempt to read, use
+ instead of stdin.
+*/
+static int builtin_stdin;
+
+/**
+ Table containing descriptions for all builtins
+*/
+static hash_table_t *desc=0;
+
+int builtin_count_args( wchar_t **argv )
+{
+ int argc = 1;
+ while( argv[argc] != 0 )
+ {
+ argc++;
+ }
+ return argc;
+}
+
+/**
+ This function works like wperror, but it prints its result into
+ the sb_err string_buffer_t instead of to stderr. Used by the builtin
+ commands.
+*/
+static void builtin_wperror( const wchar_t *s)
+{
+ if( s != 0 )
+ {
+ sb_append2( sb_err, s, L": ", 0 );
+ }
+ char *err = strerror( errno );
+ wchar_t *werr = str2wcs( err );
+ if( werr )
+ {
+ sb_append2( sb_err, werr, L"\n", 0 );
+ free( werr );
+ }
+}
+
+
+/*
+ Here follows the definition of all builtin commands. The function
+ names are all on the form builtin_NAME where NAME is the name of the
+ builtin. so the function name for the builtin 'jobs' is
+ 'builtin_jobs'.
+
+ Two builtins, 'command' and 'builtin' are not defined here as they
+ are part of the parser. (They are not parsed as commands, instead
+ they only slightly alter the parser state)
+
+*/
+
+
+/**
+ Noop function. A fake function which successfully does nothing, for
+ builtins which are handled by the parser, such as command and
+ while.
+*/
+static int builtin_ignore( wchar_t **argv )
+{
+ return 0;
+}
+
+void builtin_print_help( wchar_t *cmd, string_buffer_t *b )
+{
+ const char *h;
+
+ if( b == sb_err )
+ {
+ sb_append( sb_err,
+ parser_current_line() );
+ }
+
+ h = builtin_help_get( cmd );
+
+ if( !h )
+ return;
+
+
+
+ wchar_t *str = str2wcs(builtin_help_get( cmd ));
+ if( str )
+ {
+ sb_append( b, str );
+ free( str );
+ }
+}
+/**
+ The bind builtin, used for setting character sequences
+*/
+static int builtin_bind( wchar_t **argv )
+{
+ int i;
+ int argc=builtin_count_args( argv );
+
+ woptind=0;
+
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"set-mode", required_argument, 0, 'M'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while( 1 )
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"M:",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_printf( sb_err,
+ L"%ls%ls %ls\n",
+ argv[0],
+ BUILTIN_ERR_UNKNOWN,
+ long_options[opt_index].name );
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ case 'M':
+ input_set_mode( woptarg );
+ break;
+
+ case '?':
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ }
+
+ }
+
+ for( i=woptind; i<argc; i++ )
+ {
+// fwprintf( stderr, L"Parse binding '%ls'\n", argv[i] );
+
+ input_parse_inputrc_line( argv[i] );
+ }
+
+ return 0;
+}
+
+
+/**
+ The builtin builtin, used for given builtins precedence over functions. Mostly handled by the parser. All this code does is some additional operational modes, such as printing a list of all builtins.
+*/
+static int builtin_builtin( wchar_t **argv )
+{
+ int argc=builtin_count_args( argv );
+ int list=0;
+
+ woptind=0;
+
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"names", no_argument, 0, 'n'
+ }
+ ,
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while( 1 )
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"nh",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_UNKNOWN,
+ L" ",
+ long_options[opt_index].name,
+ L"\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+
+
+ return 1;
+ case 'h':
+ builtin_print_help( argv[0], sb_err );
+ return 0;
+
+ case 'n':
+ list=1;
+ break;
+
+ case '?':
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ }
+
+ }
+
+ if( list )
+ {
+ array_list_t names;
+ wchar_t **names_arr;
+ int i;
+
+ al_init( &names );
+ builtin_get_names( &names );
+ names_arr = list_to_char_arr( &names );
+ qsort( names_arr,
+ al_get_count( &names ),
+ sizeof(wchar_t *),
+ (int (*)(const void *, const void *))&wcsfilecmp );
+ for( i=0; i<al_get_count( &names ); i++ )
+ {
+ if( wcscmp( names_arr[i], L"count" ) == 0 )
+ continue;
+
+ sb_append2( sb_out,
+ names_arr[i],
+ L"\n",
+ 0 );
+ }
+ free( names_arr );
+ al_destroy( &names );
+ }
+ return 0;
+}
+
+/**
+ A generic bultin that only supports showing a help message. This is
+ only a placeholder that prints the help message. Useful for
+ commands that live in hte parser.
+*/
+
+static int builtin_generic( wchar_t **argv )
+{
+ int argc=builtin_count_args( argv );
+ woptind=0;
+
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while( 1 )
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"h",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_UNKNOWN,
+ L" ",
+ long_options[opt_index].name,
+ L"\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+
+ case 'h':
+ builtin_print_help( argv[0], sb_out );
+ return 0;
+
+ case '?':
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ }
+
+ }
+ return 1;
+}
+
+/**
+ The exec bultin. This is only a placeholder that prints the help message. Ther actual implementation lives in exec.c.
+*/
+
+static int builtin_exec( wchar_t **argv )
+{
+ int argc=builtin_count_args( argv );
+ woptind=0;
+
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while( 1 )
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"h",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_UNKNOWN,
+ L" ",
+ long_options[opt_index].name,
+ L"\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+
+ case 'h':
+ builtin_print_help( argv[0], sb_out );
+ return 0;
+
+ case '?':
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ }
+
+ }
+ return 1;
+}
+
+
+/**
+ The functions builtin, used for listing and erasing functions.
+*/
+static int builtin_functions( wchar_t **argv )
+{
+ int i;
+ int erase=0;
+ wchar_t *desc=0;
+
+ array_list_t names;
+ wchar_t **names_arr;
+
+ int argc=builtin_count_args( argv );
+ int list=0;
+ int show_hidden=0;
+
+ woptind=0;
+
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"erase", no_argument, 0, 'e'
+ }
+ ,
+ {
+ L"description", required_argument, 0, 'd'
+ }
+ ,
+ {
+ L"names", no_argument, 0, 'n'
+ }
+ ,
+ {
+ L"all", no_argument, 0, 'a'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while( 1 )
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"ed:na",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_UNKNOWN,
+ L" ",
+ long_options[opt_index].name,
+ L"\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+
+
+ return 1;
+
+ case 'e':
+ erase=1;
+ break;
+
+ case 'd':
+ desc=woptarg;
+ break;
+
+ case 'n':
+ list=1;
+ break;
+
+ case 'a':
+ show_hidden=1;
+ break;
+
+ case '?':
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ }
+
+ }
+
+
+
+ /*
+ Erase, desc and list are mutually exclusive
+ */
+ if( (erase + (desc!=0) + list) > 1 )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Invalid combination of options\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+ }
+
+
+ if( erase )
+ {
+ int i;
+ for( i=woptind; i<argc; i++ )
+ function_remove( argv[i] );
+ return 0;
+ }
+ else if( desc )
+ {
+ wchar_t *func;
+
+ if( argc-woptind != 1 )
+ {
+ sb_append2( sb_err,
+ L"functions: Expected exactly one function name\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+ }
+ func = argv[woptind];
+ if( !function_exists( func ) )
+ {
+ sb_append2( sb_err,
+ L"functions: Function ",
+ func,
+ L" does not exist\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+ }
+
+ function_set_desc( func, desc );
+
+ return 0;
+ }
+ else if( list )
+ {
+ al_init( &names );
+ function_get_names( &names, show_hidden );
+ names_arr = list_to_char_arr( &names );
+ qsort( names_arr,
+ al_get_count( &names ),
+ sizeof(wchar_t *),
+ (int (*)(const void *, const void *))&wcsfilecmp );
+ for( i=0; i<al_get_count( &names ); i++ )
+ {
+ sb_append2( sb_out,
+ names_arr[i],
+ L"\n",
+ 0 );
+ }
+ free( names_arr );
+ al_destroy( &names );
+ return 0;
+ }
+
+
+ switch( argc - woptind )
+ {
+ case 0:
+ {
+ sb_append( sb_out, L"Current function definitions are:\n\n" );
+ al_init( &names );
+ function_get_names( &names, show_hidden );
+ names_arr = list_to_char_arr( &names );
+ qsort( names_arr,
+ al_get_count( &names ),
+ sizeof(wchar_t *),
+ (int (*)(const void *, const void *))&wcsfilecmp );
+ for( i=0; i<al_get_count( &names ); i++ )
+ {
+ sb_append2( sb_out,
+ L"function ",
+ names_arr[i],
+ L"\n\t",
+ function_get_definition(names_arr[i]),
+ L"\nend\n\n",
+ 0);
+ }
+ free( names_arr );
+ al_destroy( &names );
+ break;
+ }
+
+ default:
+ {
+ for( i=woptind; i<argc; i++ )
+ sb_append2( sb_out,
+ L"function ",
+ argv[i],
+ L"\n\t",
+ function_get_definition(argv[i]),
+ L"\nend\n\n",
+ 0);
+
+ break;
+ }
+ }
+ return 0;
+
+
+}
+
+
+/**
+ The function builtin, used for providing subroutines.
+ It calls various functions from function.c to perform any heavy lifting.
+*/
+static int builtin_function( wchar_t **argv )
+{
+ int argc = builtin_count_args( argv );
+ int res=0;
+ wchar_t *desc=0;
+
+ woptind=0;
+
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"description", required_argument, 0, 'd'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while( 1 )
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"d:",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_UNKNOWN,
+ L" ",
+ long_options[opt_index].name,
+ L"\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ case 'd':
+ desc=woptarg;
+ break;
+
+ case '?':
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ }
+
+ }
+
+ if( argc-woptind != 1 )
+ {
+ sb_printf( sb_err,
+ L"%ls: Expected one argument, got %d\n",
+ argv[0],
+ argc-woptind );
+ res=1;
+ }
+ else if( !wcsvarname( argv[woptind] ) )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": illegal function name \'",
+ argv[woptind],
+ L"\'\n",
+ 0 );
+ res=1;
+ }
+ else if( parser_is_reserved(argv[woptind] ) )
+ {
+
+ sb_append2( sb_err,
+ argv[0],
+ L": the name \'",
+ argv[woptind],
+ L"\' is reserved,\nand can not be used as a function name\n",
+ 0 );
+
+ res=1;
+
+ }
+
+
+
+ if( res )
+ {
+ int i;
+ array_list_t names;
+ wchar_t **names_arr;
+ int chars=0;
+
+ builtin_print_help( argv[0], sb_err );
+
+ sb_append( sb_err, L"Current functions are: " );
+ chars += wcslen( L"Current functions are: " );
+ al_init( &names );
+ function_get_names( &names, 0 );
+ names_arr = list_to_char_arr( &names );
+ qsort( names_arr,
+ al_get_count( &names ),
+ sizeof(wchar_t *),
+ (int (*)(const void *, const void *))&wcsfilecmp );
+ for( i=0; i<al_get_count( &names ); i++ )
+ {
+ wchar_t *nxt = names_arr[i];
+ int l = wcslen( nxt + 2 );
+ if( chars+l > reader_get_width() )
+ {
+ chars = 0;
+ sb_append(sb_err, L"\n" );
+ }
+
+ sb_append2( sb_err,
+ nxt, L" ", 0 );
+ }
+ free( names_arr );
+ al_destroy( &names );
+ sb_append( sb_err, L"\n" );
+
+ parser_push_block( FAKE );
+ }
+ else
+ {
+ parser_push_block( FUNCTION_DEF );
+ current_block->function_name=wcsdup(argv[woptind]);
+ current_block->function_description=desc?wcsdup(desc):0;
+ }
+
+ current_block->tok_pos = parser_get_pos();
+ current_block->skip = 1;
+
+ return 0;
+
+}
+
+/**
+ The random builtin. For generating random numbers.
+*/
+
+static int builtin_random( wchar_t **argv )
+{
+ static int seeded=0;
+ int argc = builtin_count_args( argv );
+
+ woptind=0;
+
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while( 1 )
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"h",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_UNKNOWN,
+ L" ",
+ long_options[opt_index].name,
+ L"\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ case 'h':
+ builtin_print_help( argv[0], sb_err );
+ break;
+
+ case '?':
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ }
+
+ }
+
+ switch( argc-woptind )
+ {
+
+ case 0:
+ {
+ if( !seeded )
+ {
+ seeded=1;
+ srand( time( 0 ) );
+ }
+ sb_printf( sb_out, L"%d\n", rand()%32767 );
+ break;
+ }
+
+ case 1:
+ {
+ int foo;
+ wchar_t *end=0;
+
+ errno=0;
+ foo = wcstol( argv[woptind], &end, 10 );
+ if( errno || *end )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Seed value '" , argv[woptind], L"' is not a valid number\n", 0);
+
+ return 1;
+ }
+ seeded=1;
+ srand( foo );
+ break;
+ }
+
+ default:
+ {
+ sb_printf( sb_err,
+ L"%ls: Expected zero or one argument, got %d\n",
+ argc-woptind );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ The read builtin. Reads from stdin and stores the values in environment variables.
+*/
+static int builtin_read( wchar_t **argv )
+{
+ wchar_t *buff=0;
+ int i, argc = builtin_count_args( argv );
+ wchar_t *ifs;
+ int place = ENV_USER;
+ wchar_t *nxt;
+ wchar_t *prompt = DEFAULT_READ_PROMPT;
+ wchar_t *commandline = L"";
+
+ woptind=0;
+
+ while( 1 )
+ {
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"export", no_argument, 0, 'x'
+ }
+ ,
+ {
+ L"global", no_argument, 0, 'g'
+ }
+ ,
+ {
+ L"local", no_argument, 0, 'l'
+ }
+ ,
+ {
+ L"unexport", no_argument, 0, 'u'
+ }
+ ,
+ {
+ L"prompt", required_argument, 0, 'p'
+ }
+ ,
+ {
+ L"command", required_argument, 0, 'c'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"xglup:c:",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_UNKNOWN,
+ L" ",
+ long_options[opt_index].name,
+ L"\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ case L'x':
+ place |= ENV_EXPORT;
+ break;
+ case L'g':
+ place |= ENV_GLOBAL;
+ break;
+ case L'l':
+ place |= ENV_LOCAL;
+ break;
+ case L'u':
+ place |= ENV_UNEXPORT;
+ break;
+ case L'p':
+ prompt = woptarg;
+ break;
+ case L'c':
+ commandline = woptarg;
+ break;
+
+ case L'?':
+ builtin_print_help( argv[0], sb_err );
+
+
+ return 1;
+ }
+
+ }
+
+ if( ( place & ENV_UNEXPORT ) && ( place & ENV_EXPORT ) )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_EXPUNEXP,
+ L"\n",
+ parser_current_line(),
+ L"\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ if( (place&ENV_LOCAL) && (place & ENV_GLOBAL) )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_GLOCAL,
+ L"\n",
+ parser_current_line(),
+ L"\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+ }
+
+ if( woptind == argc )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_MISSING,
+ L"\n",
+ parser_current_line(),
+ L"\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ /*
+ The call to reader_readline may change woptind, so we save it away here
+ */
+ i=woptind;
+
+ ifs = env_get( L"IFS" );
+ if( ifs == 0 )
+ ifs = L"";
+
+ /*
+ Check if we should read interactively using \c reader_readline()
+ */
+ if( isatty(0) && builtin_stdin == 0 )
+ {
+ reader_push( READ_MODE_NAME );
+ reader_set_prompt( prompt );
+
+ reader_set_buffer( commandline, wcslen( commandline ) );
+ buff = wcsdup(reader_readline( ));
+ reader_pop();
+ }
+ else
+ {
+ string_buffer_t sb;
+ sb_init( &sb );
+ while( 1 )
+ {
+ int eof=0;
+ int finished=0;
+
+ wchar_t res=0;
+ static mbstate_t state;
+ memset (&state, '\0', sizeof (state));
+
+ while( !finished )
+ {
+ char b;
+ int read_res = read_blocked( builtin_stdin, &b, 1 );
+ if( read_res <= 0 )
+ {
+ eof=1;
+ break;
+ }
+
+ int sz = mbrtowc( &res, &b, 1, &state );
+
+ switch( sz )
+ {
+ case -1:
+ memset (&state, '\0', sizeof (state));
+ break;
+
+ case -2:
+ break;
+ case 0:
+ eof=1;
+ finished = 1;
+ break;
+
+ default:
+ finished=1;
+ break;
+
+ }
+ }
+
+ if( eof )
+ break;
+ if( res == L'\n' )
+ break;
+
+ sb_append_char( &sb, res );
+ }
+ buff = wcsdup( (wchar_t *)sb.buff );
+ sb_destroy( &sb );
+ }
+
+ wchar_t *state;
+
+ nxt = wcstok( buff, (i<argc-1)?ifs:L"", &state );
+// fwprintf( stderr, L"first token %ls, %d args, start at %d\n", nxt, argc, i );
+
+ while( i<argc )
+ {
+ env_set( argv[i], nxt != 0 ? nxt: L"", place );
+
+ i++;
+ if( nxt != 0 )
+ nxt = wcstok( 0, (i<argc-1)?ifs:L"", &state);
+ }
+
+ free( buff );
+ return 0;
+}
+
+static int builtin_status( wchar_t **argv )
+{
+ enum
+ {
+ NORMAL,
+ SUBST,
+ BLOCK,
+ INTERACTIVE,
+ LOGIN
+ }
+ ;
+
+ int mode = NORMAL;
+
+ int argc = builtin_count_args( argv );
+ woptind=0;
+
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ L"is-command-substitution", no_argument, 0, 'c'
+ }
+ ,
+ {
+ L"is-block", no_argument, 0, 'b'
+ }
+ ,
+ {
+ L"is-interactive", no_argument, 0, 'i'
+ }
+ ,
+ {
+ L"is-login", no_argument, 0, 'l'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while( 1 )
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"hcbil",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_UNKNOWN,
+ L" ",
+ long_options[opt_index].name,
+ L"\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ case 'h':
+ builtin_print_help( argv[0], sb_err );
+ break;
+
+ case 'i':
+ mode = INTERACTIVE;
+ break;
+
+ case 'c':
+ mode = SUBST;
+ break;
+
+ case 'b':
+ mode = BLOCK;
+ break;
+
+ case 'l':
+ mode = LOGIN;
+ break;
+
+ case '?':
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ }
+
+ }
+
+ switch( mode )
+ {
+ case INTERACTIVE:
+ return !is_interactive_session;
+
+ case SUBST:
+ return !is_subshell;
+
+ case BLOCK:
+ return !is_block;
+
+ case LOGIN:
+ return !is_login;
+
+ }
+
+ return 0;
+}
+
+
+/**
+ The eval builtin. Concatenates the arguments and calls eval on the
+ result.
+*/
+static int builtin_eval( wchar_t **argv )
+{
+ wchar_t *tot, **ptr, *next;
+ int totlen=0;
+
+ for( ptr = argv+1; *ptr; ptr++ )
+ {
+ totlen += wcslen( *ptr) + 1;
+ }
+ tot = malloc( sizeof(wchar_t)*totlen );
+ if( !tot )
+ {
+ die_mem();
+ }
+ for( ptr = argv+1, next=tot; *ptr; ptr++ )
+ {
+ int len = wcslen( *ptr );
+ wcscpy( next, *ptr );
+ next+=len;
+ *next++=L' ';
+ }
+ *(next-1)=L'\0';
+ eval( tot, block_io, TOP );
+ free( tot );
+ return proc_get_last_status();
+}
+
+/**
+ The exit builtin. Calls reader_exit to exit and returns the value specified.
+*/
+static int builtin_exit( wchar_t **argv )
+{
+ int argc = builtin_count_args( argv );
+
+ int ec=0;
+ switch( argc )
+ {
+ case 1:
+ break;
+ case 2:
+ {
+ wchar_t *end;
+ errno = 0;
+ ec = wcstol(argv[1],&end,10);
+ if( errno || *end != 0)
+ {
+ sb_append2( sb_err, argv[0], L": Argument must be an integer '", argv[1], L"'\n", 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+ break;
+ }
+
+ default:
+ sb_append2( sb_err, argv[0], L": Too many arguments\n", 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+
+ }
+ reader_exit( 1 );
+ return ec;
+}
+
+/**
+ Helper function for builtin_cd, used for seting the current working directory
+*/
+static int set_pwd(wchar_t *env)
+{
+ wchar_t dir_path[4096];
+ wchar_t *res = wgetcwd( dir_path, 4096 );
+ if( !res )
+ {
+ builtin_wperror( L"wgetcwd" );
+ return 0;
+ }
+ env_set( env, dir_path, ENV_EXPORT | ENV_GLOBAL );
+ return 1;
+}
+
+/**
+ The cd builtin. Changes the current directory to the one specified
+ or to $HOME if none is specified. If '-' is the directory specified,
+ the directory is changed to the previous working directory. The
+ directory can be relative to any directory in the CDPATH variable.
+*/
+static int builtin_cd( wchar_t **argv )
+{
+ wchar_t *dir_in;
+ wchar_t *dir;
+ int res=0;
+
+ if( argv[1] == 0 )
+ {
+ dir_in = env_get( L"HOME" );
+ if( !dir_in )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Could not find home directory\n",
+ 0 );
+
+ }
+ }
+ else
+ dir_in = argv[1];
+
+ dir = parser_cdpath_get( dir_in );
+
+ if( !dir )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": ",
+ dir_in,
+ L" is not a directory or you do not have permission to enter it\n",
+ 0 );
+ sb_append2( sb_err,
+ parser_current_line(),
+ 0 );
+ return 1;
+ }
+
+ if( wchdir( dir ) != 0 )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": ",
+ dir,
+ L" is not a directory\n",
+ 0 );
+ sb_append2( sb_err,
+ parser_current_line(),
+ 0 );
+
+ free( dir );
+
+ return 1;
+ }
+
+ if (!set_pwd(L"PWD"))
+ {
+ res=1;
+ sb_append( sb_err, L"Could not set PWD variable\n" );
+ }
+
+// fwprintf( stderr, L"cd '%ls' -> '%ls', set PWD to '%ls'\n", argv[1]?argv[1]:L"-", dir, env_get( L"PWD" ) );
+
+ free( dir );
+
+ return res;
+}
+
+/**
+ The complete builtin. Used for specifying programmable
+ tab-completions. Calls the functions in complete.c for any heavy
+ lifting.
+*/
+static int builtin_complete( wchar_t **argv )
+{
+
+ int argc=0;
+ int result_mode=SHARED, long_mode=0;
+ int cmd_type=-1;
+ int remove = 0;
+ int authorative = 1;
+
+ wchar_t *cmd=0, short_opt=L'\0', *long_opt=L"", *comp=L"", *desc=L"", *condition=L"", *load=0;
+
+ argc = builtin_count_args( argv );
+
+ woptind=0;
+
+ while( 1 )
+ {
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"exclusive", no_argument, 0, 'x'
+ }
+ ,
+ {
+ L"no-files", no_argument, 0, 'f'
+ }
+ ,
+ {
+ L"require-parameter", no_argument, 0, 'r'
+ }
+ ,
+ {
+ L"path", required_argument, 0, 'p'
+ }
+ ,
+ {
+ L"command", required_argument, 0, 'c'
+ }
+ ,
+ {
+ L"short-option", required_argument, 0, 's'
+ }
+ ,
+ {
+ L"long-option", required_argument, 0, 'l' }
+ ,
+ {
+ L"old-option", required_argument, 0, 'o'
+ }
+ ,
+ {
+ L"description", required_argument, 0, 'd'
+ }
+ ,
+ {
+ L"arguments", required_argument, 0, 'a'
+ }
+ ,
+ {
+ L"erase", no_argument, 0, 'e'
+ }
+ ,
+ {
+ L"unauthorative", no_argument, 0, 'u'
+ }
+ ,
+ {
+ L"condition", required_argument, 0, 'n'
+ }
+ ,
+ {
+ L"load", required_argument, 0, 'y'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"a:c:p:s:l:o:d:frxeun:y:",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ L": Unknown option ",
+ long_options[opt_index].name,
+ L"\n",
+ 0 );
+ sb_append( sb_err,
+ parser_current_line() );
+// builtin_print_help( argv[0], sb_err );
+
+
+ return 1;
+
+
+ case 'x':
+ result_mode |= EXCLUSIVE;
+ break;
+
+ case 'f':
+ result_mode |= NO_FILES;
+ break;
+
+ case 'r':
+ result_mode |= NO_COMMON;
+ break;
+
+ case 'p':
+ cmd_type = PATH;
+ cmd = expand_backslash( wcsdup(woptarg), 1);
+ break;
+
+ case 'c':
+ cmd_type = COMMAND;
+ cmd = expand_backslash( wcsdup(woptarg), 1);
+ break;
+
+ case 'd':
+ desc = woptarg;
+ break;
+
+ case 'u':
+ authorative=0;
+ break;
+
+ case 's':
+ if( wcslen( woptarg ) > 1 )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Parameter too long ",
+ woptarg,
+ L"\n",
+ 0);
+ sb_append( sb_err,
+ parser_current_line() );
+// builtin_print_help( argv[0], sb_err );
+
+ return 1;
+ }
+
+ short_opt = woptarg[0];
+ break;
+
+ case 'l':
+ long_opt = woptarg;
+ break;
+
+ case 'o':
+ long_mode=1;
+ long_opt = woptarg;
+ break;
+
+ case 'a':
+ comp = woptarg;
+ break;
+
+
+ case 'e':
+ remove = 1;
+
+ break;
+
+ case 'n':
+ condition = woptarg;
+ break;
+
+ case 'y':
+ load = woptarg;
+ break;
+
+
+ case '?':
+ // builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ }
+
+ }
+
+ if( woptind != argc )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Too many arguments\n",
+ 0);
+ sb_append( sb_err,
+ parser_current_line() );
+ // builtin_print_help( argv[0], sb_err );
+
+ return 1;
+ }
+
+ if( load )
+ {
+ complete_load( load, 1 );
+ return 0;
+ }
+
+
+ if( cmd == 0 )
+ {
+ /* No arguments specified, meaning we print the definitions of
+ * all specified completions to stdout.*/
+ complete_print( sb_out );
+ }
+ else
+ {
+ if( remove )
+ {
+ /* Remove the specified completion */
+ complete_remove( cmd,
+ cmd_type,
+ short_opt,
+ long_opt );
+ }
+ else
+ {
+ /* Add the specified completion */
+ complete_add( cmd,
+ cmd_type,
+ short_opt,
+ long_opt,
+ long_mode,
+ result_mode,
+ authorative,
+ condition,
+ comp,
+ desc );
+ }
+ free( cmd );
+
+ }
+ return 0;
+}
+
+/**
+ The source builtin. Can be called through either 'source' or
+ '.'. Evaluates the contents of a file.
+*/
+static int builtin_source( wchar_t ** argv )
+{
+ int stdin_org;
+ int res;
+
+/*
+ if( wcsstr( argv[1], L"fish_complete" ) )
+ {
+ fwprintf( stderr, L"Woot\n" );
+ return 0;
+ }
+*/
+
+ if( (argv[1] == 0) || (argv[2]!=0) )
+ {
+
+ sb_append2( sb_err, argv[0], L": Expected exactly one argument\n", 0 );
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+ }
+
+ if( (stdin_org=dup( 0 )) == -1)
+ {
+ builtin_wperror(L"dup");
+ return 1;
+ }
+
+ if( close( 0 ) )
+ {
+ builtin_wperror(L"close");
+ return 1;
+ }
+
+ if( wopen( argv[1], O_RDONLY ) == -1 )
+ {
+ builtin_wperror( L"open" );
+ res = 1;
+ }
+ else
+ {
+ reader_push_current_filename( argv[1] );
+ res = reader_read();
+ if( res )
+ {
+ sb_printf( sb_err,
+ L"%ls : Error while reading file '%ls'\n",
+ argv[0],
+ argv[1]
+ );
+
+ }
+
+ if( close( 0 ) )
+ {
+ builtin_wperror(L"close");
+ res = errno;
+ }
+ reader_pop_current_filename();
+ }
+
+ if( dup( stdin_org ) == -1)
+ {
+ builtin_wperror(L"dup");
+ res = errno;
+ fwprintf( stderr, L"Could not restore stdout\n" );
+ sanity_lose();
+ }
+
+ if( close( stdin_org ) )
+ {
+ builtin_wperror(L"close");
+ res = errno;
+ fwprintf( stderr, L"Could not restore stdout\n" );
+ sanity_lose();
+ }
+
+ return res;
+}
+
+
+/**
+ Make the specified job the first job of the job list. Moving jobs
+ around in the list makes the list reflect the order in which the
+ jobs where used.
+*/
+static void make_first( job_t *j )
+{
+ job_t *prev=0;
+ job_t *curr;
+ for( curr = first_job; curr != j; curr = curr->next )
+ {
+ prev=curr;
+ }
+ if( curr == j )
+ {
+ if( prev == 0 )
+ return;
+ else
+ {
+ prev->next = curr->next;
+ curr->next = first_job;
+ first_job = curr;
+ }
+ }
+}
+
+
+/**
+ Builtin for putting a job in the foreground
+*/
+static int builtin_fg( wchar_t **argv )
+{
+ job_t *j;
+
+ if( argv[1] == 0 )
+ {
+ /*
+ Last constructed job in the job que by default
+ */
+ for( j=first_job; ((j!=0) && (!j->constructed)); j=j->next )
+ ;
+ }
+ else if( argv[2] != 0 )
+ {
+ /*
+ Specifying what more than one job to put to the foreground
+ is a syntax error, we still try to locate the job argv[1],
+ since we want to know if this is an ambigous job
+ specification or if this is an malformed job id
+ */
+ int pid = wcstol( argv[1], 0, 10 );
+ j = job_get_from_pid( pid );
+ if( j != 0 )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Ambiguous job\n",
+ 0);
+ }
+ else
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Not a job (",
+ argv[1],
+ L")\n", 0 );
+ }
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+ }
+ else
+ {
+ int pid = abs(wcstol( argv[1], 0, 10 ));
+ j = job_get_from_pid( pid );
+ }
+
+ if( j == 0 )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": No suitable job\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+ else
+ {
+ if( builtin_err_redirect )
+ {
+ sb_printf( sb_err,
+ L"Send job %d, '%ls' to foreground\n",
+ j->job_id,
+ j->command );
+ }
+ else
+ {
+ fwprintf( stderr,
+ L"Send job %d, '%ls' to foreground\n",
+ j->job_id,
+ j->command );
+ }
+ }
+
+ wchar_t *ft = tok_first( j->command );
+ if( ft != 0 )
+ env_set( L"_", ft, ENV_EXPORT );
+ free(ft);
+ reader_write_title();
+/*
+ fwprintf( stderr, L"Send job %d, \'%ls\' to foreground\n",
+ j->job_id,
+ j->command );
+*/
+ make_first( j );
+ j->fg=1;
+
+
+ job_continue( j, job_is_stopped(j) );
+ return 0;
+}
+
+/**
+ Helper function for builtin_bg()
+*/
+static void send_to_bg( job_t *j, wchar_t *name )
+{
+ if( j == 0 )
+ {
+ sb_append2( sb_err, L"bg", L": Unknown job ", name, L"\n", 0 );
+ builtin_print_help( L"bg", sb_err );
+ return;
+ }
+ else
+ {
+ sb_printf( sb_err,
+ L"Send job %d '%ls' to background\n",
+ j->job_id,
+ j->command );
+ }
+ make_first( j );
+ j->fg=0;
+ job_continue( j, job_is_stopped(j) );
+}
+
+
+/**
+ Builtin for putting a job in the background
+*/
+static int builtin_bg( wchar_t **argv )
+{
+ if( argv[1] == 0 )
+ {
+ job_t *j;
+ for( j=first_job; ((j!=0) && (!j->constructed) && (!job_is_stopped(j))); j=j->next )
+ ;
+ send_to_bg( j, L"(default)");
+ return 0;
+ }
+ for( argv++; *argv != 0; argv++ )
+ {
+ int pid = wcstol( *argv, 0, 10 );
+ send_to_bg( job_get_from_pid( pid ), *argv);
+ }
+ return 0;
+}
+
+
+#ifdef HAVE__PROC_SELF_STAT
+/**
+ Calculates the cpu usage (in percent) of the specified job.
+*/
+static int cpu_use( job_t *j )
+{
+ double u=0;
+ process_t *p;
+
+ for( p=j->first_process; p; p=p->next )
+ {
+ struct timeval t;
+ int jiffies;
+ gettimeofday( &t, 0 );
+ jiffies = proc_get_jiffies( p );
+
+ double t1 = 1000000.0*p->last_time.tv_sec+p->last_time.tv_usec;
+ double t2 = 1000000.0*t.tv_sec+t.tv_usec;
+
+/* fwprintf( stderr, L"t1 %f t2 %f p1 %d p2 %d\n",
+ t1, t2, jiffies, p->last_jiffies );
+*/
+
+ u += ((double)(jiffies-p->last_jiffies))/(t2-t1);
+ }
+ return u*1000000;
+}
+#endif
+
+/**
+ Builtin for printing running jobs
+*/
+static int builtin_jobs( wchar_t **argv )
+{
+
+ enum
+ {
+ DEFAULT,
+ PRINT_PID,
+ PRINT_COMMAND
+ }
+ ;
+
+
+ int argc=0;
+ job_t *j;
+ int found=0;
+ int mode=DEFAULT;
+ argc = builtin_count_args( argv );
+
+ woptind=0;
+
+ while( 1 )
+ {
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"pid", no_argument, 0, 'p'
+ }
+ ,
+ {
+ L"command", no_argument, 0, 'c'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"pc",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ L": Unknown option ",
+ long_options[opt_index].name,
+ L"\n",
+ 0 );
+ sb_append( sb_err,
+ parser_current_line() );
+// builtin_print_help( argv[0], sb_err );
+
+
+ return 1;
+
+
+ case 'p':
+ mode=PRINT_PID;
+ break;
+
+ case 'c':
+ mode=PRINT_COMMAND;
+ break;
+
+ case '?':
+ // builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ }
+ }
+
+ if( mode==DEFAULT )
+ {
+
+ for( j= first_job; j; j=j->next )
+ {
+ /*
+ Ignore unconstructed jobs, i.e. ourself.
+ */
+ if( j->constructed )
+ {
+ if( !found )
+ {
+ /*
+ Print table header before first job
+ */
+ sb_append( sb_out, L"Job\tGroup\t");
+#ifdef HAVE__PROC_SELF_STAT
+ sb_append( sb_out, L"CPU\t" );
+#endif
+ sb_append( sb_out, L"State\tCommand\n" );
+ }
+
+ found = 1;
+
+ sb_printf( sb_out, L"%d\t%d\t", j->job_id, j->pgid );
+
+
+#ifdef HAVE__PROC_SELF_STAT
+ sb_printf( sb_out, L"%d\t", cpu_use(j) );
+#endif
+ sb_append2( sb_out, job_is_stopped(j)?L"stopped\t":L"running\t",
+ j->command, L"\n", 0 );
+
+ }
+ }
+ if( !found )
+ {
+ sb_append2( sb_out, argv[0], L": There are no running jobs\n", 0 );
+ }
+ }
+ else
+ {
+ long pid;
+ wchar_t *end;
+ job_t *j;
+
+ if( woptind != argc-1 )
+ {
+ sb_append2( sb_err, argv[0], L": Expected exactly one argument\n", 0 );
+ }
+
+
+ errno=0;
+ pid=wcstol( argv[woptind], &end, 10 );
+ if( errno || *end )
+ {
+ sb_append2( sb_err, argv[0], L": Not a process id: ", argv[woptind], L"\n", 0 );
+ return 1;
+
+ }
+
+ j = job_get_from_pid( pid );
+ if( !j )
+ {
+ sb_printf( sb_err, L"%ls: No suitable job: %d\n", argv[0], pid );
+ return 1;
+ }
+ process_t *p;
+ for( p=j->first_process; p; p=p->next )
+ {
+ switch( mode )
+ {
+ case PRINT_PID:
+ {
+ sb_printf( sb_out, L"%d\n", p->pid );
+ break;
+ }
+
+ case PRINT_COMMAND:
+ {
+ sb_printf( sb_out, L"%ls\n", p->argv[0] );
+ break;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ Builtin for looping over a list
+*/
+static int builtin_for( wchar_t **argv )
+{
+ int argc = builtin_count_args( argv );
+ int res=1;
+
+
+ if( argc < 3)
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Expected at least two arguments\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+ }
+ else if ( !wcsvarname(argv[1]) )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": \'",
+ argv[1],
+ L"\' invalid variable name\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+ }
+ else if (wcscmp( argv[2], L"in") != 0 )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Second argument must be \'in\'\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+ }
+ else
+ {
+ res=0;
+ }
+
+
+ if( res )
+ {
+ parser_push_block( FAKE );
+ }
+ else
+ {
+ parser_push_block( FOR );
+ al_init( &current_block->for_vars);
+
+ int i;
+ current_block->tok_pos = parser_get_pos();
+ current_block->for_variable = wcsdup( argv[1] );
+
+ for( i=argc-1; i>3; i-- )
+ {
+ al_push( &current_block->for_vars, wcsdup(argv[ i ] ));
+ }
+ if( argc > 3 )
+ {
+ env_set( current_block->for_variable, argv[3], 0);
+ }
+ else
+ {
+ current_block->skip=1;
+ }
+ }
+ return res;
+}
+
+static int builtin_begin( wchar_t **argv )
+{
+ parser_push_block( BEGIN );
+ current_block->tok_pos = parser_get_pos();
+ return 0;
+}
+
+
+/**
+ Builtin for ending a block of code, such as a for-loop or an if statement.
+
+ The end command is whare a lot of the block-level magic happens.
+*/
+static int builtin_end( wchar_t **argv )
+{
+ if( !current_block->outer ||
+ current_block->type == OR ||
+ current_block->type == AND )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Not inside of block\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+ else
+ {
+ /**
+ By default, 'end' kills the current block scope. But if we
+ are rewinding a loop, this should be set to false, so that
+ variables in the current loop scope won't die between laps.
+ */
+ int kill_block = 1;
+
+ switch( current_block->type )
+ {
+ case WHILE:
+ {
+ /*
+ If this is a while loop, we rewind the loop unless
+ it's the last lap, in which case we continue.
+ */
+ if( !( current_block->skip && (current_block->loop_status != LOOP_CONTINUE )))
+ {
+ current_block->loop_status = LOOP_NORMAL;
+ current_block->skip = 0;
+ kill_block = 0;
+ parser_set_pos( current_block->tok_pos);
+ current_block->while_state = WHILE_TEST_AGAIN;
+ }
+
+ break;
+ }
+
+ case IF:
+ case SUBST:
+ case BEGIN:
+ /*
+ Nothing special happens at the end of these. The scope just ends.
+ */
+
+ break;
+
+ case FOR:
+ {
+ /*
+ set loop variable to next element, and rewind to the beginning of the block.
+ */
+ if( current_block->loop_status == LOOP_BREAK )
+ {
+ while( al_get_count( &current_block->for_vars ) )
+ {
+ free( (void *)al_pop( &current_block->for_vars ) );
+ }
+ }
+
+ if( al_get_count( &current_block->for_vars ) )
+ {
+ wchar_t *val = (wchar_t *)al_pop( &current_block->for_vars );
+ env_set( current_block->for_variable, val, 0);
+ current_block->loop_status = LOOP_NORMAL;
+ current_block->skip = 0;
+ free(val);
+
+ kill_block = 0;
+ parser_set_pos( current_block->tok_pos );
+/*
+ fwprintf( stderr,
+ L"jump to %d\n",
+ current_block->tok_pos ); */
+ }
+ break;
+ }
+
+ case FUNCTION_DEF:
+ {
+ /**
+ Copy the text from the beginning of the function
+ until the end command and use as the new definition
+ for the specified function
+ */
+ wchar_t *def = wcsndup( parser_get_buffer()+current_block->tok_pos,
+ parser_get_job_pos()-current_block->tok_pos );
+
+ //fwprintf( stderr, L"Function: %ls\n", def );
+ if( !parser_test( def, 1 ) )
+ {
+ function_add( current_block->function_name,
+ def,
+ current_block->function_description);
+ }
+ free(def);
+ }
+ break;
+
+ }
+ if( kill_block )
+ {
+ parser_pop_block();
+ }
+// fwprintf( stderr, L"End with status %d\n", proc_get_last_status() );
+
+
+ /*
+ If everything goes ok, return status of last command to execute.
+ */
+ return proc_get_last_status();
+ }
+}
+
+/**
+ Builtin for executing commands if an if statement is false
+*/
+static int builtin_else( wchar_t **argv )
+{
+ if( current_block == 0 ||
+ current_block->type != IF ||
+ current_block->if_state != 1)
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": not inside of if block\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+ else
+ {
+ current_block->if_state++;
+ current_block->skip = !current_block->skip;
+ env_pop();
+ env_push(0);
+ }
+
+ /*
+ If everything goes ok, return status of last command to execute.
+ */
+ return proc_get_last_status();
+}
+
+/**
+ This function handles both the 'continue' and the 'break' builtins
+ that are used for loop control.
+*/
+static int builtin_break_continue( wchar_t **argv )
+{
+ int is_break = (wcscmp(argv[0],L"break")==0);
+ int argc = builtin_count_args( argv );
+
+ block_t *b = current_block;
+
+ if( argc != 1 )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Unknown option \'", argv[1], L"\'", 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+
+ while( (b != 0) &&
+ ( b->type != WHILE) &&
+ (b->type != FOR ) )
+ {
+ b = b->outer;
+ }
+
+ if( b == 0 )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Not inside of loop\n", 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ b = current_block;
+ while( ( b->type != WHILE) &&
+ (b->type != FOR ) )
+ {
+ b->skip=1;
+ b = b->outer;
+ }
+ b->skip=1;
+ b->loop_status = is_break?LOOP_BREAK:LOOP_CONTINUE;
+ return 0;
+}
+
+/**
+ Function for handling the \c return builtin
+*/
+static int builtin_return( wchar_t **argv )
+{
+ int argc = builtin_count_args( argv );
+ int status = 0;
+
+ block_t *b = current_block;
+
+ switch( argc )
+ {
+ case 1:
+ break;
+ case 2:
+ {
+ wchar_t *end;
+ errno = 0;
+ status = wcstol(argv[1],&end,10);
+ if( errno || *end != 0)
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Argument must be an integer '",
+ argv[1],
+ L"'\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+// fwprintf( stderr, L"Return with status %d\n", status );
+ break;
+ }
+ default:
+ sb_append2( sb_err,
+ argv[0],
+ L": Too many arguments\n", 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+
+ while( (b != 0) &&
+ ( b->type != FUNCTION_CALL) )
+ {
+ b = b->outer;
+ }
+
+ if( b == 0 )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Not inside of function\n", 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ b = current_block;
+ while( ( b->type != FUNCTION_CALL))
+ {
+ b->skip=1;
+ b = b->outer;
+ }
+ b->skip=1;
+// proc_set_last_status( status );
+
+ return status;
+}
+
+/**
+ Builtin for executing one of several blocks of commands depending on the value of an argument.
+*/
+static int builtin_switch( wchar_t **argv )
+{
+ int res=0;
+ int argc = builtin_count_args( argv );
+
+ if( argc != 2 )
+ {
+ sb_printf( sb_err,
+ L"%ls : syntax error, expected exactly one argument, got %d\n",
+ argv[0],
+ argc-1 );
+
+ builtin_print_help( argv[0], sb_err );
+ res=1;
+ parser_push_block( FAKE );
+ }
+ else
+ {
+ parser_push_block( SWITCH );
+ current_block->switch_value = wcsdup( argv[1]);
+ current_block->skip=1;
+ current_block->switch_taken=0;
+ }
+
+ return res;
+}
+
+/**
+ Builtin used together with the switch builtin for conditional execution
+*/
+static int builtin_case( wchar_t **argv )
+{
+ int argc = builtin_count_args( argv );
+ int i;
+ wchar_t *unescaped=0;
+
+ if( current_block->type != SWITCH )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": syntax error, case command while not in switch block\n",
+ 0);
+ builtin_print_help( L"case", sb_err );
+ return 1;
+ }
+
+ current_block->skip = 1;
+
+ if( current_block->switch_taken )
+ {
+ return 0;
+ }
+
+ for( i=1; i<argc; i++ )
+ {
+ free( unescaped );
+ unescaped = expand_backslash( wcsdup( argv[i] ), 1);
+
+ if( wildcard_match( current_block->switch_value, unescaped ) )
+ {
+ current_block->skip = 0;
+ current_block->switch_taken = 1;
+ break;
+ }
+ }
+ free( unescaped );
+
+ return 0;
+}
+
+
+/*
+ END OF BUILTIN COMMANDS
+ Below are functions for handling the builtin commands
+*/
+void builtin_init()
+{
+ al_init( &io_stack );
+ hash_init( &builtin, &hash_wcs_func, &hash_wcs_cmp );
+
+ hash_put( &builtin, L"exit", &builtin_exit );
+ hash_put( &builtin, L"builtin", &builtin_builtin );
+ hash_put( &builtin, L"cd", &builtin_cd );
+ hash_put( &builtin, L"function", &builtin_function );
+ hash_put( &builtin, L"functions", &builtin_functions );
+ hash_put( &builtin, L"complete", &builtin_complete );
+ hash_put( &builtin, L"end", &builtin_end );
+ hash_put( &builtin, L"else", &builtin_else );
+ hash_put( &builtin, L"eval", &builtin_eval );
+ hash_put( &builtin, L"for", &builtin_for );
+ hash_put( &builtin, L".", &builtin_source );
+ hash_put( &builtin, L"set", &builtin_set );
+ hash_put( &builtin, L"fg", &builtin_fg );
+ hash_put( &builtin, L"bg", &builtin_bg );
+ hash_put( &builtin, L"jobs", &builtin_jobs );
+ hash_put( &builtin, L"read", &builtin_read );
+ hash_put( &builtin, L"break", &builtin_break_continue );
+ hash_put( &builtin, L"continue", &builtin_break_continue );
+ hash_put( &builtin, L"return", &builtin_return );
+ hash_put( &builtin, L"commandline", &builtin_commandline );
+ hash_put( &builtin, L"switch", &builtin_switch );
+ hash_put( &builtin, L"case", &builtin_case );
+ hash_put( &builtin, L"bind", &builtin_bind );
+ hash_put( &builtin, L"random", &builtin_random );
+ hash_put( &builtin, L"status", &builtin_status );
+
+ /*
+ Builtins that are handled directly by the parser. They are
+ bound to a noop function only so that they show up in the
+ listings of builtin commands, etc..
+ */
+ hash_put( &builtin, L"command", &builtin_ignore );
+ hash_put( &builtin, L"if", &builtin_ignore );
+ hash_put( &builtin, L"while", &builtin_ignore );
+ hash_put( &builtin, L"not", &builtin_generic );
+ hash_put( &builtin, L"and", &builtin_generic );
+ hash_put( &builtin, L"or", &builtin_generic );
+ hash_put( &builtin, L"exec", &builtin_exec );
+ hash_put( &builtin, L"begin", &builtin_begin );
+
+ /*
+ This is not a builtin, but fish handles it's help display
+ internally, to do some ugly special casing to make sure 'count
+ -h', but 'count (echo -h)' does not.
+ */
+ hash_put( &builtin, L"count", &builtin_ignore );
+
+ intern_static( L"exit" );
+ intern_static( L"builtin" );
+ intern_static( L"cd" );
+ intern_static( L"function" );
+ intern_static( L"functions" );
+ intern_static( L"complete" );
+ intern_static( L"end" );
+ intern_static( L"else" );
+ intern_static( L"eval" );
+ intern_static( L"for" );
+ intern_static( L"." );
+ intern_static( L"set" );
+ intern_static( L"fg" );
+ intern_static( L"bg" );
+ intern_static( L"jobs" );
+ intern_static( L"read" );
+ intern_static( L"break" );
+ intern_static( L"continue" );
+ intern_static( L"return" );
+ intern_static( L"commandline" );
+ intern_static( L"switch" );
+ intern_static( L"case" );
+ intern_static( L"bind" );
+ intern_static( L"random" );
+ intern_static( L"command" );
+ intern_static( L"if" );
+ intern_static( L"while" );
+ intern_static( L"exec" );
+ intern_static( L"count" );
+ intern_static( L"not" );
+ intern_static( L"and" );
+ intern_static( L"or" );
+ intern_static( L"begin" );
+ intern_static( L"status" );
+
+ builtin_help_init();
+}
+
+void builtin_destroy()
+{
+ if( desc )
+ {
+ hash_destroy( desc );
+ free( desc );
+ }
+
+ al_destroy( &io_stack );
+ hash_destroy( &builtin );
+ builtin_help_destroy();
+}
+
+int builtin_exists( wchar_t *cmd )
+{
+ /*
+ Count is not a builtin, but it's help is handled internally by
+ fish, so it is in the hash_table_t.
+ */
+ if( wcscmp( cmd, L"count" )==0)
+ return 0;
+
+ return (hash_get(&builtin, cmd) != 0 );
+}
+
+/**
+ Return true if the specified builtin should handle it's own help,
+ false otherwise.
+*/
+static int internal_help( wchar_t *cmd )
+{
+ if( wcscmp( cmd, L"for" ) == 0 ||
+ wcscmp( cmd, L"while" ) == 0 ||
+ wcscmp( cmd, L"function" ) == 0 ||
+ wcscmp( cmd, L"if" ) == 0 ||
+ wcscmp( cmd, L"end" ) == 0 ||
+ wcscmp( cmd, L"switch" ) == 0 )
+ return 1;
+ return 0;
+}
+
+
+int builtin_run( wchar_t **argv )
+{
+ int (*cmd)(wchar_t **argv)=0;
+ cmd = hash_get( &builtin, argv[0] );
+
+ if( argv[1] != 0 && !internal_help(argv[0]) )
+ {
+ if( argv[2] == 0 && (parser_is_help( argv[1], 0 ) ) )
+ {
+ builtin_print_help( argv[0], sb_out );
+ return 0;
+ }
+ }
+
+ if( cmd != 0 )
+ {
+ int status;
+
+ status = cmd(argv);
+// fwprintf( stderr, L"Builtin: Set status of %ls to %d\n", argv[0], status );
+
+ return status;
+
+ }
+ else
+ {
+ debug( 0, L"Unknown builtin: ", argv[0], 0 );
+ }
+ return 1;
+}
+
+
+void builtin_get_names( array_list_t *list )
+{
+ hash_get_keys( &builtin, list );
+}
+
+const wchar_t *builtin_get_desc( const wchar_t *b )
+{
+
+ if( !desc )
+ {
+ desc = malloc( sizeof( hash_table_t ) );
+ if( !desc)
+ return 0;
+
+ hash_init( desc, &hash_wcs_func, &hash_wcs_cmp );
+
+ hash_put( desc, L"exit", L"Exit the shell" );
+ hash_put( desc, L"cd", L"Change working directory" );
+ hash_put( desc, L"function", L"Define a new function" );
+ hash_put( desc, L"functions", L"List or remove functions" );
+ hash_put( desc, L"complete", L"Edit command specific completions" );
+ hash_put( desc, L"end", L"End a block of commands" );
+ hash_put( desc, L"else", L"Evaluate block if condition is false" );
+ hash_put( desc, L"eval", L"Evaluate parameters as a command" );
+ hash_put( desc, L"for", L"Perform a set of commands multiple times" );
+ hash_put( desc, L".", L"Evaluate contents of file" );
+ hash_put( desc, L"set", L"Handle environment variables" );
+ hash_put( desc, L"fg", L"Send job to foreground" );
+ hash_put( desc, L"bg", L"Send job to background" );
+ hash_put( desc, L"jobs", L"Print currently running jobs" );
+ hash_put( desc, L"read", L"Read a line of input into variables" );
+ hash_put( desc, L"break", L"Stop the innermost loop" );
+ hash_put( desc, L"continue", L"Skip the rest of the current lap of the innermost loop" );
+ hash_put( desc, L"return", L"Stop the innermost currently evaluated function" );
+ hash_put( desc, L"commandline", L"Set the commandline" );
+ hash_put( desc, L"switch", L"Conditionally execute a block of commands" );
+ hash_put( desc, L"case", L"Conditionally execute a block of commands" );
+ hash_put( desc, L"builtin", L"Run a builtin command" );
+ hash_put( desc, L"command", L"Run a program" );
+ hash_put( desc, L"if", L"Conditionally execute a command" );
+ hash_put( desc, L"while", L"Perform a command multiple times" );
+ hash_put( desc, L"bind", L"Handle key bindings");
+ hash_put( desc, L"random", L"Generate random number");
+ hash_put( desc, L"exec", L"Run command in current process");
+ hash_put( desc, L"not", L"Negate exit status of job");
+ hash_put( desc, L"or", L"Execute second command if first fails");
+ hash_put( desc, L"and", L"Execute second command if first suceeds");
+ hash_put( desc, L"begin", L"Create a block of code" );
+ hash_put( desc, L"status", L"Return status information about fish" );
+ }
+
+ return hash_get( desc, b );
+}
+
+
+void builtin_push_io( int in)
+{
+ if( builtin_stdin != -1 )
+ {
+ al_push( &io_stack, (void *)(long)builtin_stdin );
+ al_push( &io_stack, sb_out );
+ al_push( &io_stack, sb_err );
+ }
+ builtin_stdin = in;
+ sb_out = malloc(sizeof(string_buffer_t));
+ sb_err = malloc(sizeof(string_buffer_t));
+ sb_init( sb_out );
+ sb_init( sb_err );
+}
+
+void builtin_pop_io()
+{
+ builtin_stdin = 0;
+ sb_destroy( sb_out );
+ sb_destroy( sb_err );
+ free( sb_out);
+ free(sb_err);
+
+ if( al_get_count( &io_stack ) >0 )
+ {
+ sb_err = (string_buffer_t *)al_pop( &io_stack );
+ sb_out = (string_buffer_t *)al_pop( &io_stack );
+ builtin_stdin = (int)(long)al_pop( &io_stack );
+ }
+ else
+ {
+ sb_out = sb_err = 0;
+ builtin_stdin = 0;
+ }
+}
+
diff --git a/builtin.h b/builtin.h
new file mode 100644
index 00000000..2c1a2d1e
--- /dev/null
+++ b/builtin.h
@@ -0,0 +1,101 @@
+/** \file builtin.h
+ Prototypes for functions for executing builtin functions.
+*/
+
+
+enum
+{
+ COMMAND_NOT_BUILTIN,
+ BUILTIN_REGULAR,
+ BUILTIN_FUNCTION
+}
+;
+
+#define BUILTIN_ERR_MISSING L": Expected argument"
+#define BUILTIN_ERR_COMBO L": Invalid combination of options"
+#define BUILTIN_ERR_GLOCAL L": Variable can only be one of universal, global and local"
+#define BUILTIN_ERR_EXPUNEXP L": Variable can't be both exported and unexported"
+#define BUILTIN_ERR_UNKNOWN L": Unknown option"
+
+/**
+ Stringbuffer used to represent standard output
+*/
+extern string_buffer_t *sb_out;
+
+/**
+ Stringbuffer used to represent standard error
+*/
+extern string_buffer_t *sb_err;
+
+/**
+ Kludge. Tells builtins if output is to screen
+*/
+extern int builtin_out_redirect;
+
+/**
+ Kludge. Tells builtins if error is to screen
+*/
+extern int builtin_err_redirect;
+
+
+/**
+ Initialize builtin data.
+*/
+void builtin_init();
+
+/**
+ Destroy builtin data.
+*/
+void builtin_destroy();
+
+/**
+ Is there a builtin command with the given name?
+*/
+int builtin_exists( wchar_t *cmd );
+
+/**
+ Execute a builtin command
+
+ \param argv Array containing the command and parameters
+ of the builtin. The list is terminated by a
+ null pointer. This syntax resembles the syntax
+ for exec.
+
+ \return the exit status of the builtin command
+*/
+int builtin_run( wchar_t **argv );
+
+/**
+ Insert all builtin names into l. These are not copies of the strings and should not be freed after use.
+*/
+void builtin_get_names( array_list_t *list );
+
+/**
+ Pushes a new set of input/output to the stack. The new stdin is supplied, a new set of output string_buffer_ts is created.
+*/
+void builtin_push_io( int stdin_fd );
+
+/**
+ Pops a set of input/output from the stack. The output string_buffer_ts are destroued, but the input file is not closed.
+*/
+void builtin_pop_io();
+
+
+/**
+ Return a one-line description of the specified builtin
+*/
+const wchar_t *builtin_get_desc( const wchar_t *b );
+
+/**
+ Counts the number of non null pointers in the specified array
+*/
+int builtin_count_args( wchar_t **argv );
+
+/**
+ Print help for the specified builtin. If \c b is sb_err, also print the line information
+*/
+void builtin_print_help( wchar_t *cmd, string_buffer_t *b );
+
+
+int builtin_set(wchar_t **argv);
+int builtin_commandline(wchar_t **argv);
diff --git a/builtin_commandline.c b/builtin_commandline.c
new file mode 100644
index 00000000..8b915868
--- /dev/null
+++ b/builtin_commandline.c
@@ -0,0 +1,461 @@
+/** \file builtin_commandline.c Functions defining the commandline builtin
+
+Functions used for implementing the commandline builtin.
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <signal.h>
+
+#include "config.h"
+#include "util.h"
+#include "builtin.h"
+#include "common.h"
+#include "wgetopt.h"
+#include "reader.h"
+#include "proc.h"
+#include "parser.h"
+#include "tokenizer.h"
+#include "input_common.h"
+#include "input.h"
+
+/**
+ Which part of the comandbuffer are we operating on
+*/
+enum
+{
+ STRING_MODE=1, // Operate on entire buffer
+ JOB_MODE, // Operate on job under cursor
+ PROCESS_MODE, // Operate on process under cursor
+ TOKEN_MODE // Operate on token under cursor
+}
+ ;
+
+/**
+ For text insertion, how should it be done
+*/
+enum
+{
+ REPLACE_MODE=1, // Replace current text
+ INSERT_MODE, // Insert at cursor position
+ APPEND_MODE // Insert at end of current token/command/buffer
+}
+ ;
+
+
+static void replace_part( wchar_t *begin,
+ wchar_t *end,
+ wchar_t *insert,
+ int append_mode )
+{
+ wchar_t *buff = reader_get_buffer();
+ string_buffer_t out;
+ int out_pos=reader_get_cursor_pos();
+
+ sb_init( &out );
+
+// wchar_t *tmp = wcsndup( begin, end-begin );
+
+// fwprintf( stderr, L"Commandline '%ls', current command '%ls'\n", reader_get_buffer(), tmp );
+
+ sb_append_substring( &out, buff, begin-buff );
+
+ switch( append_mode)
+ {
+ case REPLACE_MODE:
+ {
+
+ sb_append( &out, insert );
+ out_pos = wcslen( insert ) + (begin-buff);
+ break;
+
+ }
+ case APPEND_MODE:
+ {
+ sb_append_substring( &out, begin, end-begin );
+ sb_append( &out, insert );
+ break;
+ }
+ case INSERT_MODE:
+ {
+ int cursor = reader_get_cursor_pos() -(begin-buff);
+ sb_append_substring( &out, begin, cursor );
+ sb_append( &out, insert );
+ sb_append_substring( &out, begin+cursor, end-begin-cursor );
+ out_pos += wcslen( insert );
+ break;
+ }
+ }
+ sb_append( &out, end );
+ reader_set_buffer( (wchar_t *)out.buff, out_pos );
+ sb_destroy( &out );
+}
+
+void write_part( wchar_t *begin,
+ wchar_t *end,
+ int cut_at_cursor,
+ int tokenize )
+{
+ tokenizer tok;
+ string_buffer_t out;
+ wchar_t *buff;
+ int pos;
+
+ pos = reader_get_cursor_pos()-(begin-reader_get_buffer());
+
+ if( tokenize )
+ {
+
+ buff = wcsndup( begin, end-begin );
+// fwprintf( stderr, L"Subshell: %ls, end char %lc\n", buff, *end );
+ sb_init( &out );
+
+ for( tok_init( &tok, buff, TOK_ACCEPT_UNFINISHED );
+ tok_has_next( &tok );
+ tok_next( &tok ) )
+ {
+ if( (cut_at_cursor) &&
+ (tok_get_pos( &tok)+wcslen(tok_last( &tok)) >= pos) )
+ break;
+
+// fwprintf( stderr, L"Next token %ls\n", tok_last( &tok ) );
+
+ switch( tok_last_type( &tok ) )
+ {
+ case TOK_STRING:
+ sb_append2( &out, tok_last( &tok), L"\n", 0 );
+ break;
+
+ }
+ }
+
+ if( out.buff )
+ sb_append( sb_out,
+ (wchar_t *)out.buff );
+
+ free( buff );
+ tok_destroy( &tok );
+ sb_destroy( &out );
+ }
+ else
+ {
+ if( cut_at_cursor )
+ {
+ end = begin+pos;
+ }
+ sb_append_substring( sb_out, begin, end-begin );
+ sb_append( sb_out, L"\n" );
+ }
+}
+
+
+/**
+ The commandline builtin. It is used for specifying a new value for
+ the commandline.
+*/
+int builtin_commandline( wchar_t **argv )
+{
+
+ int buffer_part=0;
+ int cut_at_cursor=0;
+
+ int argc = builtin_count_args( argv );
+ int append_mode=0;
+
+ int function_mode = 0;
+
+ int tokenize = 0;
+
+ if( !reader_get_buffer() )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Can not set commandline in non-interactive mode\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ woptind=0;
+
+ while( 1 )
+ {
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"append", no_argument, 0, 'a'
+ }
+ ,
+ {
+ L"insert", no_argument, 0, 'i'
+ }
+ ,
+ {
+ L"replace", no_argument, 0, 'r'
+ }
+ ,
+ {
+ L"current-job", no_argument, 0, 'j'
+ }
+ ,
+ {
+ L"current-process", no_argument, 0, 'p'
+ }
+ ,
+ {
+ L"current-token", no_argument, 0, 't'
+ }
+ ,
+ {
+ L"current-buffer", no_argument, 0, 'b'
+ }
+ ,
+ {
+ L"cut-at-cursor", no_argument, 0, 'c'
+ }
+ ,
+ {
+ L"function", no_argument, 0, 'f'
+ }
+ ,
+ {
+ L"tokenize", no_argument, 0, 'o'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ int opt_index = 0;
+
+ int opt = wgetopt_long( argc,
+ argv,
+ L"aijpctwfo",
+ long_options,
+ &opt_index );
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ if(long_options[opt_index].flag != 0)
+ break;
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_UNKNOWN,
+ L" ",
+ long_options[opt_index].name,
+ L"\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+
+ return 1;
+
+ case L'a':
+ append_mode = APPEND_MODE;
+ break;
+
+ case L'i':
+ append_mode = INSERT_MODE;
+ break;
+
+ case 'c':
+ cut_at_cursor=1;
+ break;
+
+ case 't':
+ buffer_part = TOKEN_MODE;
+ break;
+
+ case 'j':
+ buffer_part = JOB_MODE;
+ break;
+
+ case 'p':
+ buffer_part = PROCESS_MODE;
+ break;
+
+ case 'f':
+ function_mode=1;
+ break;
+
+ case 'o':
+ tokenize=1;
+ break;
+
+ case L'?':
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+ }
+
+ if( function_mode )
+ {
+ int i;
+
+ /*
+ Check for invalid switch combinations
+ */
+ if( buffer_part || cut_at_cursor || append_mode || tokenize )
+ {
+ sb_append2(sb_err,
+ argv[0],
+ BUILTIN_ERR_COMBO,
+ L"\n",
+ parser_current_line(),
+ L"\n",
+ 0);
+ return 1;
+ }
+
+
+ if( argc == woptind )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_MISSING,
+ L"\n",
+ parser_current_line(),
+ L"\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+ for( i=woptind; i<argc; i++ )
+ {
+ wint_t c = input_get_code( argv[i] );
+ if( c != -1 )
+ {
+// fwprintf( stderr, L"Add function %ls (%d)\n", argv[i], c );
+ /*
+ input_unreadch inserts the specified keypress or
+ readline function at the top of the stack of unused
+ keypresses
+ */
+ input_unreadch(c);
+ }
+ else
+ {
+// fwprintf( stderr, L"BLUR %ls %d\n", argv[i], c );
+ sb_append2( sb_err,
+ argv[0],
+ L": Unknown readline function '",
+ argv[i],
+ L"'\n",
+ parser_current_line(),
+ L"\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+ /*
+ Check for invalid switch combinations
+ */
+ if( argc-woptind > 1 )
+ {
+
+ sb_append2( sb_err,
+ argv[0],
+ L": Too many arguments\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ if( (tokenize || cut_at_cursor) && (argc-woptind) )
+ {
+
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_COMBO,
+ L",\n --cut-at-cursor and --tokenize can not be used when setting the commandline",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ if( append_mode && !(argc-woptind) )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ BUILTIN_ERR_COMBO,
+ L",\n insertion mode switches can not be used when not in insertion mode",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ /*
+ Set default modes
+ */
+ if( !append_mode )
+ {
+ append_mode = REPLACE_MODE;
+ }
+
+ if( !buffer_part )
+ {
+ buffer_part = STRING_MODE;
+ }
+
+ wchar_t *begin, *end;
+
+ switch( buffer_part )
+ {
+ case STRING_MODE:
+ {
+ begin = reader_get_buffer();
+ end = begin+wcslen(begin);
+ break;
+ }
+
+ case PROCESS_MODE:
+ {
+ reader_current_process_extent( &begin, &end );
+ break;
+ }
+
+ case JOB_MODE:
+ {
+ reader_current_job_extent( &begin, &end );
+ break;
+ }
+
+ case TOKEN_MODE:
+ {
+ reader_current_token_extent( &begin, &end, 0, 0 );
+ break;
+ }
+
+ }
+
+ switch(argc-woptind)
+ {
+ case 0:
+ {
+ write_part( begin, end, cut_at_cursor, tokenize );
+ break;
+ }
+
+ case 1:
+ {
+ replace_part( begin, end, argv[woptind], append_mode );
+ break;
+ }
+ }
+
+ return 0;
+}
diff --git a/builtin_help.h b/builtin_help.h
new file mode 100644
index 00000000..556d2e6b
--- /dev/null
+++ b/builtin_help.h
@@ -0,0 +1,25 @@
+/** \file builtin_help.h
+
+ Prototypes for functions for printing usage information of builtin commands. The
+ corresponding .c file is automatically generated by combining the
+ builtin_help.hdr file with doxygen output.
+*/
+/**
+ Return the help text for the specified builtin command. Use
+ non-wide characters since wide characters have some issues with
+ string formating escape sequences sometimes.
+
+ \param cmd The command for which to obtain help text
+*/
+char *builtin_help_get( wchar_t *cmd );
+
+/**
+ Initialize builtin help data
+*/
+void builtin_help_init();
+
+/**
+ Destory builtin help data
+*/
+void builtin_help_destroy();
+
diff --git a/builtin_help.hdr b/builtin_help.hdr
new file mode 100644
index 00000000..c2932562
--- /dev/null
+++ b/builtin_help.hdr
@@ -0,0 +1,36 @@
+/** \file builtin_help.c
+
+ Functions for printing usage information of builtin commands. This
+ file is automatically generated from the file builtin_help.hdr and
+ various help files in the doc_src directory.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <strings.h>
+#include <wchar.h>
+
+#include "config.h"
+#include "util.h"
+#include "common.h"
+#include "builtin_help.h"
+
+/**
+ Hashtable storing the help text
+*/
+static hash_table_t tbl;
+
+char *builtin_help_get( wchar_t *cmd )
+{
+ return (char *)hash_get( &tbl, (void *)cmd );
+}
+
+void builtin_help_destroy()
+{
+ hash_destroy( &tbl );
+}
+
+
+void builtin_help_init()
+{
+ hash_init( &tbl, &hash_wcs_func, &hash_wcs_cmp );
diff --git a/builtin_set.c b/builtin_set.c
new file mode 100644
index 00000000..e47ea35d
--- /dev/null
+++ b/builtin_set.c
@@ -0,0 +1,585 @@
+/** \file builtin_set.c Functions defining the set builtin
+
+Functions used for implementing the set builtin.
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <signal.h>
+
+#include "config.h"
+#include "util.h"
+#include "builtin.h"
+#include "env.h"
+#include "expand.h"
+#include "common.h"
+#include "wgetopt.h"
+#include "proc.h"
+#include "parser.h"
+
+/**
+ Extract the name from a destination argument of the form name[index1 index2...]
+*/
+static int parse_fill_name( string_buffer_t *name,
+ const wchar_t *src)
+{
+
+ if (src == 0)
+ {
+ return 0;
+ }
+
+ while (iswalnum(*src) || *src == L'_')
+ {
+ sb_append_char(name, *src++);
+ }
+
+ if (*src != L'[' && *src != L'\0')
+ {
+
+ sb_append(sb_err, L"set: Invalid character in variable name: ");
+ sb_append_char(sb_err, *src);
+ sb_append2(sb_err, L"\n", parser_current_line(), L"\n", 0 );
+// builtin_print_help( L"set", sb_err );
+
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+
+/**
+ Extract indexes from a destination argument of the form name[index1 index2...]
+*/
+static int parse_fill_indexes( array_list_t *indexes,
+ const wchar_t *src)
+{
+ int count = 0;
+
+ if (src == 0)
+ {
+ return 0;
+ }
+
+ while (*src != L'\0' && (iswalnum(*src) || *src == L'_'))
+ {
+ src++;
+ }
+
+ if (*src == L'\0')
+ {
+ return 0;
+ }
+
+ if (*src++ != L'[')
+ {
+ return -1;
+ }
+
+ while (iswblank(*src))
+ {
+ src++;
+ }
+
+ while (*src != L']')
+ {
+ wchar_t *end;
+ long l_ind = wcstol(src, &end, 10);
+ if (end == src)
+ {
+ wchar_t sbuf[256];
+ swprintf(sbuf, 255, L"Invalid index starting at %ls\n", src);
+ sb_append(sb_err, sbuf);
+ return -1;
+ }
+
+ int *ind = (int *) calloc(1, sizeof(int));
+ *ind = (int) l_ind;
+ al_push(indexes, ind);
+ src = end;
+ count++;
+ while (iswblank(*src)) src++;
+ }
+
+ return count;
+}
+
+
+/**
+ Update a list by writing the specified values at the specified indexes
+*/
+static int update_values( array_list_t *list,
+ array_list_t *indexes,
+ array_list_t *values )
+{
+ int i;
+
+ //fwprintf(stderr, L"Scan complete\n");
+ /* Replace values where needed */
+ for( i = 0; i < al_get_count(indexes); i++ )
+ {
+ int ind = *(int *) al_get(indexes, i) - 1;
+ void *new = (void *) al_get(values, i);
+ if (al_get(list, ind) != 0)
+ {
+ free((void *) al_get(list, ind));
+ }
+ al_set(list, ind, new != 0 ? wcsdup(new) : wcsdup(L""));
+ }
+
+ return al_get_count(list);
+}
+
+
+/**
+ Return 1 if an array list of int* pointers contains the specified
+ value, 0 otherwise
+*/
+static int al_contains_int( array_list_t *list,
+ int val)
+{
+ int i;
+
+ for (i = 0; i < al_get_count(list); i++)
+ {
+ int *current = (int *) al_get(list, i);
+ if (current != 0 && *current == val)
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ Erase from a list values at specified indexes
+*/
+static int erase_values(array_list_t *list, array_list_t *indexes)
+{
+ int i;
+ array_list_t result;
+
+ //fwprintf(stderr, L"Starting with %d\n", al_get_count(list));
+
+ al_init(&result);
+
+ for (i = 0; i < al_get_count(list); i++)
+ {
+ if (!al_contains_int(indexes, i + 1))
+ {
+ al_push(&result, al_get(list, i));
+ }
+ else
+ {
+ free((void *) al_get(list, i));
+ }
+ }
+
+ al_truncate(list,0);
+ al_push_all( list, &result );
+ al_destroy(&result);
+
+ //fwprintf(stderr, L"Remaining: %d\n", al_get_count(&result));
+
+ return al_get_count(list);
+}
+
+
+/**
+ Fill a string buffer with values from a list, using ARRAY_SEP_STR to separate them
+*/
+static int fill_buffer_from_list(string_buffer_t *sb, array_list_t *list)
+{
+ int i;
+
+ for (i = 0; i < al_get_count(list); i++)
+ {
+ wchar_t *v = (wchar_t *) al_get(list, i);
+ if (v != 0)
+ {
+ // fwprintf(stderr, L".\n");
+ // fwprintf(stderr, L"Collecting %ls from %d\n", v, i);
+ sb_append(sb, v);
+ }
+ if (i < al_get_count(list) - 1)
+ sb_append(sb, ARRAY_SEP_STR);
+ }
+ return al_get_count(list);
+}
+
+
+/**
+ Print the names of all environment variables in the scope, with or without values,
+ with or without escaping
+*/
+static void print_variables(int include_values, int escape, int scope)
+{
+ array_list_t names;
+ wchar_t **names_arr;
+ int i;
+
+ al_init( &names );
+ env_get_names( &names, scope );
+ names_arr = list_to_char_arr( &names );
+ qsort( names_arr,
+ al_get_count( &names ),
+ sizeof(wchar_t *),
+ (int (*)(const void *, const void *))&wcsfilecmp );
+
+ for( i = 0; i < al_get_count(&names); i++ )
+ {
+ wchar_t *key = names_arr[i];
+ /* Why does expand_escape free its argument ?! */
+ wchar_t *e_key = escape ? expand_escape(wcsdup(key), 1) : wcsdup(key);
+ sb_append(sb_out, e_key);
+
+ if( include_values )
+ {
+ wchar_t *value = env_get(key);
+ wchar_t *e_value = escape ? expand_escape_variable(value) : wcsdup(value);
+ sb_append2(sb_out, L" ", e_value, 0);
+ free(e_value);
+ }
+
+ sb_append(sb_out, L"\n");
+ free(e_key);
+ }
+
+ free(names_arr);
+ al_destroy(&names);
+}
+
+/**
+ The set builtin. Creates, updates and erases environment variables and environemnt variable arrays.
+*/
+
+int builtin_set( wchar_t **argv )
+{
+ const static struct woption
+ long_options[] =
+ {
+ {
+ L"export", no_argument, 0, 'x'
+ },
+ {
+ L"global", no_argument, 0, 'g'
+ },
+ {
+ L"local", no_argument, 0, 'l'
+ },
+ {
+ L"erase", no_argument, 0, 'e'
+ },
+ {
+ L"names", no_argument, 0, 'n'
+ } ,
+ {
+ L"unexport", no_argument, 0, 'u'
+ } ,
+ {
+ L"universal", no_argument, 0, 'U'
+ } ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ wchar_t short_options[] = L"xglenuU";
+
+ int argc = builtin_count_args(argv);
+
+ /*
+ Flags to set the work mode
+ */
+ int local = 0, global = 0, export = 0;
+ int erase = 0, list = 0, unexport=0;
+ int universal = 0;
+
+
+ /*
+ Variables used for performing the actual work
+ */
+ wchar_t *dest = 0;
+ array_list_t values;
+ string_buffer_t name_sb;
+ int retcode=0;
+ wchar_t *name;
+ array_list_t indexes;
+ int retval;
+
+
+ /* Parse options to obtain the requested operation and the modifiers */
+ woptind = 0;
+ while (1)
+ {
+ int c = wgetopt_long(argc, argv, short_options, long_options, 0);
+
+ if (c == -1)
+ {
+ break;
+ }
+
+ switch(c)
+ {
+ case 0:
+ break;
+ case 'e':
+ erase = 1;
+ break;
+ case 'n':
+ list = 1;
+ break;
+ case 'x':
+ export = 1;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'g':
+ global = 1;
+ break;
+ case 'u':
+ unexport = 1;
+ break;
+ case 'U':
+ universal = 1;
+ break;
+ case '?':
+ return 1;
+ default:
+ break;
+ }
+ }
+
+ /* Check operation and modifiers sanity */
+ if( erase && list )
+ {
+ sb_append2(sb_err,
+ argv[0],
+ BUILTIN_ERR_COMBO,
+ L"\n",
+ parser_current_line(),
+ L"\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ if( local + global + universal > 1 )
+ {
+ sb_printf( sb_err,
+ L"%ls%ls\n%ls\n",
+ argv[0],
+ BUILTIN_ERR_GLOCAL,
+ parser_current_line() );
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ if( export && unexport )
+ {
+ sb_append2(sb_err,
+ argv[0],
+ BUILTIN_ERR_EXPUNEXP,
+ L"\n",
+ parser_current_line(),
+ L"\n",
+ 0);
+ builtin_print_help( argv[0], sb_err );
+ return 1;
+ }
+
+ /* Parse destination */
+ if( woptind < argc )
+ {
+ dest = wcsdup(argv[woptind++]);
+ //fwprintf(stderr, L"Dest: %ls\n", dest);
+ }
+
+ /* Parse values */
+ // wchar_t **values = woptind < argc ? (wchar_t **) calloc(argc - woptind, sizeof(wchar_t *)) : 0;
+
+ al_init(&values);
+ while( woptind < argc )
+ {
+ al_push(&values, argv[woptind++]);
+ // fwprintf(stderr, L"Val: %ls\n", argv[woptind - 1]);
+ }
+
+ /* Extract variable name and indexes */
+
+ sb_init(&name_sb);
+ retval = parse_fill_name(&name_sb, dest);
+
+
+ if( retval < 0 )
+ retcode=1;
+
+ if( !retcode )
+ {
+ name = (wchar_t *) name_sb.buff;
+ //fwprintf(stderr, L"Name is %ls\n", name);
+
+ al_init(&indexes);
+ retval = parse_fill_indexes(&indexes, dest);
+ if (retval < 0)
+ retcode = 1;
+ }
+
+ if( !retcode )
+ {
+
+ int i;
+ int finished=0;
+
+ /* Do the actual work */
+ int scope = (local ? ENV_LOCAL : 0) | (global ? ENV_GLOBAL : 0) | (export ? ENV_EXPORT : 0) | (unexport ? ENV_UNEXPORT : 0) | (universal ? ENV_UNIVERSAL:0) | ENV_USER;
+ if( list )
+ {
+ /* Maybe we should issue an error if there are any other arguments */
+ print_variables(0, 0, scope);
+ finished=1;
+ }
+
+ if( (!finished ) &&
+ (name == 0 || wcslen(name) == 0))
+ {
+ /* No arguments -- display name & value for all variables in scope */
+ if( erase )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Erase needs a variable name\n",
+ parser_current_line(),
+ L"\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ retcode = 1;
+ }
+ else
+ {
+ print_variables( 1, 1, scope );
+ }
+
+ finished=1;
+ }
+
+
+ if( !finished )
+ {
+ if( al_get_count( &values ) == 0 &&
+ al_get_count( &indexes ) == 0 &&
+ !erase &&
+ !list )
+ {
+ /*
+ Only update the variable scope
+ */
+ env_set( name, 0, scope );
+ finished = 1;
+ }
+ }
+
+ if( !finished )
+ {
+ /* There are some arguments, we have at least a variable name */
+ if( erase && al_get_count(&values) != 0 )
+ {
+ sb_append2( sb_err,
+ argv[0],
+ L": Values cannot be specfied with erase\n",
+ parser_current_line(),
+ L"\n",
+ 0 );
+ builtin_print_help( argv[0], sb_err );
+ retcode = 1;
+ }
+ else
+ {
+ /* All ok, we can alter the specified variable */
+ array_list_t val_l;
+ al_init(&val_l);
+
+ void *old=0;
+
+ if (al_get_count(&indexes) == 0)
+ {
+ /* We will act upon the entire variable */
+
+ al_push( &val_l, wcsdup(L"") );
+ old = val_l.arr;
+
+ /* Build indexes for all variable or all new values */
+ int end_index = erase ? al_get_count(&val_l) : al_get_count(&values);
+ for (i = 0; i < end_index; i++)
+ {
+ int *ind = (int *) calloc(1, sizeof(int));
+ *ind = i + 1;
+ al_push(&indexes, ind);
+ }
+ }
+ else
+ {
+ /* We will act upon some specific indexes */
+ expand_variable_array( env_get(name), &val_l );
+ }
+
+ string_buffer_t result_sb;
+ sb_init(&result_sb);
+ if (erase)
+ {
+ int rem = erase_values(&val_l, &indexes);
+ if (rem == 0)
+ {
+ env_remove(name, ENV_USER);
+ }
+ else
+ {
+ fill_buffer_from_list(&result_sb, &val_l);
+ env_set(name, (wchar_t *) result_sb.buff, scope);
+ }
+ }
+ else
+ {
+
+ update_values( &val_l,
+ &indexes,
+ &values );
+
+ fill_buffer_from_list( &result_sb,
+ &val_l );
+
+ env_set(name,
+ (wchar_t *) result_sb.buff,
+ scope);
+ }
+
+ al_foreach( &val_l, (void (*)(const void *))&free );
+ al_destroy(&val_l);
+ sb_destroy(&result_sb);
+ }
+ }
+
+ al_foreach( &indexes, (void (*)(const void *))&free );
+ al_destroy(&indexes);
+ }
+
+/* Common cleanup */
+//fwprintf(stderr, L"Cleanup\n");
+ free(dest);
+ sb_destroy(&name_sb);
+ al_destroy( &values );
+
+ return retcode;
+}
+
diff --git a/common.c b/common.c
new file mode 100644
index 00000000..e26dc690
--- /dev/null
+++ b/common.c
@@ -0,0 +1,1061 @@
+/** \file common.c
+
+Various functions, mostly string utilities, that are used by most
+parts of fish.
+*/
+#include <stdlib.h>
+#include <termios.h>
+#include <wchar.h>
+#include <string.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <wctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <locale.h>
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+
+
+#include "config.h"
+#include "util.h"
+#include "wutil.h"
+#include "common.h"
+#include "expand.h"
+#include "proc.h"
+#include "wildcard.h"
+#include "parser.h"
+
+/**
+ Error message to show on string convertion error
+*/
+#define STR2WCS_MSG "fish: Invalid multibyte sequence \'"
+
+/**
+ The maximum number of minor errors to report. Further errors will be omitted.
+*/
+#define ERROR_MAX_COUNT 1
+
+struct termios shell_modes;
+
+/**
+ Number of error encountered. This is reset after each command, and
+ used to limit the number of error messages on commands with many
+ string convertion problems.
+*/
+static int error_count=0;
+
+int error_max=1;
+
+wchar_t ellipsis_char;
+
+static int c1=0, c2=0, c3=0, c4=0, c5;
+
+char *profile=0;
+
+wchar_t *program_name;
+
+static int block_count=0;
+
+void common_destroy()
+{
+ debug( 3, L"Calls: wcsdupcat %d, wcsdupcat2 %d, wcsndup %d, str2wcs %d, wcs2str %d", c1, c2, c3, c4, c5 );
+}
+
+
+
+wchar_t **list_to_char_arr( array_list_t *l )
+{
+ wchar_t ** res = malloc( sizeof(wchar_t *)*(al_get_count( l )+1) );
+ int i;
+ if( res == 0 )
+ {
+ die_mem();
+ }
+ for( i=0; i<al_get_count( l ); i++ )
+ res[i] = (wchar_t *)al_get(l,i);
+ res[i]='\0';
+ return res;
+}
+
+void block()
+{
+ block_count++;
+ if( block_count == 1 )
+ {
+ sigset_t chldset;
+ sigemptyset( &chldset );
+ sigaddset( &chldset, SIGCHLD );
+ sigprocmask(SIG_BLOCK, &chldset, 0);
+ }
+}
+
+
+void unblock()
+{
+ block_count--;
+ if( block_count == 0 )
+ {
+ sigset_t chldset;
+ sigemptyset( &chldset );
+ sigaddset( &chldset, SIGCHLD );
+ sigprocmask(SIG_UNBLOCK, &chldset, 0);
+ }
+}
+
+
+
+int fgetws2( wchar_t **b, int *len, FILE *f )
+{
+ int i=0;
+ wint_t c;
+
+ wchar_t *buff = *b;
+
+ /*
+ This is a kludge: We block SIGCHLD while reading, since I can't
+ get getwc to perform reliably when signals are flying. Even when
+ watching for EINTR errors, bytes are lost.
+ */
+
+ block();
+
+ while( 1 )
+ {
+ /* Reallocate the buffer if necessary */
+ if( i+1 >= *len )
+ {
+ int new_len = maxi( 128, (*len)*2);
+ buff = realloc( buff, sizeof(wchar_t)*new_len );
+ if( buff == 0 )
+ {
+ die_mem();
+ }
+ else
+ {
+ *len = new_len;
+ *b = buff;
+ }
+ }
+
+ errno=0;
+
+ c = getwc( f );
+
+ if( errno == EILSEQ )
+ {
+
+ getc( f );
+ continue;
+ }
+
+//fwprintf( stderr, L"b\n" );
+
+ switch( c )
+ {
+ /* End of line */
+ case WEOF:
+ case L'\n':
+ case L'\0':
+ buff[i]=L'\0';
+ unblock();
+
+ return i;
+ /* Ignore carriage returns */
+ case L'\r':
+ break;
+
+ default:
+ buff[i++]=c;
+ break;
+ }
+
+
+ }
+ unblock();
+
+}
+
+
+/**
+ Wrapper for wcsfilecmp
+*/
+static int completion_cmp( const void *a, const void *b )
+{
+ wchar_t *c= *((wchar_t **)a);
+ wchar_t *d= *((wchar_t **)b);
+ return wcsfilecmp( c, d );
+}
+
+void sort_list( array_list_t *comp )
+{
+
+ qsort( comp->arr,
+ al_get_count( comp ),
+ sizeof( void*),
+ &completion_cmp );
+}
+
+wchar_t *str2wcs( const char *in )
+{
+ c4++;
+
+ wchar_t *res;
+
+ res = malloc( sizeof(wchar_t)*(strlen(in)+1) );
+
+ if( !res )
+ {
+ die_mem();
+
+ }
+
+ if( (size_t)-1 == mbstowcs( res, in, sizeof(wchar_t)*(strlen(in)) +1) )
+ {
+ error_count++;
+ if( error_count <=error_max )
+ {
+ fflush( stderr );
+ write( 2,
+ STR2WCS_MSG,
+ strlen(STR2WCS_MSG) );
+ write( 2,
+ in,
+ strlen(in ));
+ write( 2,
+ "\'\n",
+ 2 );
+ }
+
+ free(res);
+ return 0;
+ }
+
+ return res;
+
+}
+
+void error_reset()
+{
+ error_count=0;
+}
+
+char *wcs2str( const wchar_t *in )
+{
+ c5++;
+
+ char *res = malloc( MAX_UTF8_BYTES*wcslen(in)+1 );
+ if( res == 0 )
+ {
+ die_mem();
+ }
+
+ wcstombs( res,
+ in,
+ MAX_UTF8_BYTES*wcslen(in)+1 );
+
+ res = realloc( res, strlen( res )+1 );
+
+ return res;
+}
+
+char **wcsv2strv( const wchar_t **in )
+{
+ int count =0;
+ int i;
+
+ while( in[count] != 0 )
+ count++;
+ char **res = malloc( sizeof( char *)*(count+1));
+ if( res == 0 )
+ {
+ die_mem();
+
+ }
+
+ for( i=0; i<count; i++ )
+ {
+ res[i]=wcs2str(in[i]);
+ }
+ res[count]=0;
+ return res;
+
+}
+
+wchar_t *wcsdupcat( const wchar_t *a, const wchar_t *b )
+{
+ c1++;
+
+ return wcsdupcat2( a, b, 0 );
+}
+
+wchar_t *wcsdupcat2( const wchar_t *a, ... )
+{
+ c2++;
+
+ int len=wcslen(a);
+ int pos;
+ va_list va, va2;
+ wchar_t *arg;
+
+ va_start( va, a );
+ va_copy( va2, va );
+ while( (arg=va_arg(va, wchar_t *) )!= 0 )
+ {
+ len += wcslen( arg );
+ }
+ va_end( va );
+
+ wchar_t *res = malloc( sizeof(wchar_t)*(len +1 ));
+ if( res == 0 )
+ return 0;
+
+ wcscpy( res, a );
+ pos = wcslen(a);
+ while( (arg=va_arg(va2, wchar_t *) )!= 0 )
+ {
+ wcscpy( res+pos, arg );
+ pos += wcslen(arg);
+ }
+ va_end( va2 );
+ return res;
+
+}
+
+
+wchar_t **strv2wcsv( const char **in )
+{
+ int count =0;
+ int i;
+
+ while( in[count] != 0 )
+ count++;
+ wchar_t **res = malloc( sizeof( wchar_t *)*(count+1));
+ if( res == 0 )
+ {
+ die_mem();
+ }
+
+ for( i=0; i<count; i++ )
+ {
+ res[i]=str2wcs(in[i]);
+ }
+ res[count]=0;
+ return res;
+
+}
+
+
+
+wchar_t *wcsndup( const wchar_t *in, int c )
+{
+ c3++;
+
+ wchar_t *res = malloc( sizeof(wchar_t)*(c+1) );
+ if( res == 0 )
+ {
+ die_mem();
+ }
+ wcsncpy( res, in, c );
+ res[c] = L'\0';
+ return res;
+}
+
+long convert_digit( wchar_t d, int base )
+{
+ long res=-1;
+ if( (d <= L'9') && (d >= L'0') )
+ {
+ res = d - L'0';
+ }
+ else if( (d <= L'z') && (d >= L'a') )
+ {
+ res = d + 10 - L'a';
+ }
+ else if( (d <= L'Z') && (d >= L'A') )
+ {
+ res = d + 10 - L'A';
+ }
+ if( res >= base )
+ {
+ res = -1;
+ }
+
+ return res;
+}
+
+
+long wcstol(const wchar_t *nptr,
+ wchar_t **endptr,
+ int base)
+{
+ long long res=0;
+ int is_set=0;
+ if( base > 36 )
+ {
+ errno = EINVAL;
+ return 0;
+ }
+
+ while( 1 )
+ {
+ long nxt = convert_digit( *nptr, base );
+ if( endptr != 0 )
+ *endptr = (wchar_t *)nptr;
+ if( nxt < 0 )
+ {
+ if( !is_set )
+ {
+ errno = EINVAL;
+ }
+ return res;
+ }
+ res = (res*base)+nxt;
+ is_set = 1;
+ if( res > LONG_MAX )
+ {
+ errno = ERANGE;
+ return LONG_MAX;
+ }
+ if( res < LONG_MIN )
+ {
+ errno = ERANGE;
+ return LONG_MIN;
+ }
+ nptr++;
+ }
+}
+
+/*$OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $*/
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ Appends src to string dst of size siz (unlike wcsncat, siz is the
+ full size of dst, not space left). At most siz-1 characters will be
+ copied. Always NUL terminates (unless siz <= wcslen(dst)). Returns
+ wcslen(src) + MIN(siz, wcslen(initial dst)). If retval >= siz,
+ truncation occurred.
+
+ This is the OpenBSD strlcat function, modified for wide characters,
+ and renamed to reflect this change.
+
+*/
+size_t
+wcslcat(wchar_t *dst, const wchar_t *src, size_t siz)
+{
+
+ register wchar_t *d = dst;
+ register const wchar_t *s = src;
+ register size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + wcslen(s));
+
+ while (*s != '\0')
+ {
+ if (n != 1)
+ {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src));
+ /* count does not include NUL */
+}
+
+/*$OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $*/
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ Copy src to string dst of size siz. At most siz-1 characters will
+ be copied. Always NUL terminates (unless siz == 0). Returns
+ wcslen(src); if retval >= siz, truncation occurred.
+
+ This is the OpenBSD strlcpy function, modified for wide characters,
+ and renamed to reflect this change.
+*/
+size_t
+wcslcpy(wchar_t *dst, const wchar_t *src, size_t siz)
+{
+ register wchar_t *d = dst;
+ register const wchar_t *s = src;
+ register size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0)
+ {
+ do
+ {
+ if ((*d++ = *s++) == 0)
+ break;
+ }
+ while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0)
+ {
+ if (siz != 0)
+ *d = '\0';
+ /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+ return(s - src - 1);
+ /* count does not include NUL */
+}
+
+wchar_t *wcsdup( const wchar_t *in )
+{
+ wchar_t *out = malloc( sizeof( wchar_t)*(wcslen(in)+1));
+ if( out == 0 )
+ {
+ die_mem();
+
+ }
+ wcscpy( out, in );
+ return out;
+
+}
+
+int wcscasecmp( const wchar_t *a, const wchar_t *b )
+{
+ if( *a == 0 )
+ {
+ return (*b==0)?0:-1;
+ }
+ else if( *b == 0 )
+ {
+ return 1;
+ }
+ int diff = towlower(*a)-towlower(*b);
+ if( diff != 0 )
+ return diff;
+ else
+ return wcscasecmp( a+1,b+1);
+}
+
+int wcsvarname( wchar_t *str )
+{
+ while( *str )
+ {
+ if( (!iswalnum(*str)) && (*str != L'_' ) )
+ {
+ return 0;
+ }
+ str++;
+ }
+ return 1;
+
+
+}
+
+#if !HAVE_WCWIDTH
+/**
+ Return the number of columns used by a character.
+
+ In locales without a native wcwidth, Unicode is probably so broken
+ that it isn't worth trying to implement a real wcwidth. This
+ wcwidth assumes any printing character takes up one column.
+*/
+int wcwidth( wchar_t c )
+{
+ if( c < 32 )
+ return 0;
+ if ( c == 127 )
+ return 0;
+ return 1;
+}
+#endif
+
+/**
+ The glibc version of wcswidth seems to hang on some strings. fish uses this replacement.
+*/
+int my_wcswidth( const wchar_t *c )
+{
+ int res=0;
+ while( *c )
+ {
+ int w = wcwidth( *c++ );
+ if( w < 0 )
+ w = 1;
+ if( w > 2 )
+ w=1;
+
+ res += w;
+ }
+ return res;
+}
+
+wchar_t *quote_end( const wchar_t *in )
+{
+ int level=1;
+ int offset = (*in != L'\"');
+
+ in++;
+
+ while(1)
+ {
+/* fwprintf( stderr, L"Check %c\n", *tok->buff );*/
+ switch( *in )
+ {
+ case L'\\':
+ in++;
+ if( *in == L'\0' )
+ {
+ return 0;
+ }
+ break;
+ case L'\"':
+ case L'\'':
+ if( (((level+offset) % 2)?L'\"':L'\'') == *in )
+ {
+ level--;
+ }
+ else
+ {
+ level++;
+ }
+
+ break;
+ }
+ if( (*in == L'\0') ||(level==0))
+ break;
+
+ in++;
+ }
+ return level?0:(wchar_t *)in;
+
+}
+
+
+void fish_setlocale(int category, const wchar_t *locale)
+{
+ char *lang = wcs2str( locale );
+ setlocale(category,lang);
+ free( lang );
+ /*
+ Use ellipsis if on known unicode system, otherwise use $
+ */
+ if( wcslen( locale ) )
+ {
+ ellipsis_char = wcsstr( locale, L".UTF")?L'\u2026':L'$';
+ }
+ else
+ {
+ char *lang = getenv( "LANG" );
+ if( lang )
+ ellipsis_char = strstr( lang, ".UTF")?L'\u2026':L'$';
+ else
+ ellipsis_char = L'$';
+ }
+}
+
+int contains_str( const wchar_t *a, ... )
+{
+ wchar_t *arg;
+ va_list va;
+ int res = 0;
+
+ va_start( va, a );
+ while( (arg=va_arg(va, wchar_t *) )!= 0 )
+ {
+ if( wcscmp( a,arg) == 0 )
+ {
+ res=1;
+ break;
+ }
+
+ }
+ va_end( va );
+ return res;
+}
+
+int read_blocked(int fd, void *buf, size_t count)
+{
+ int res;
+ sigset_t chldset, oldset;
+
+ sigemptyset( &chldset );
+ sigaddset( &chldset, SIGCHLD );
+ sigprocmask(SIG_BLOCK, &chldset, &oldset);
+ res = read( fd, buf, count );
+ sigprocmask( SIG_SETMASK, &oldset, 0 );
+ return res;
+}
+
+int writeb( tputs_arg_t b )
+{
+ write( 1, &b, 1 );
+// putc( b, stdout );
+ return 0;
+}
+
+void die_mem()
+{
+ /*
+ int *foo=0;
+ *foo = 6;
+ */
+ debug( 0, L"Out of memory, shutting down fish." );
+ exit(1);
+}
+
+void debug( int level, wchar_t *msg, ... )
+{
+ va_list va;
+
+ if( level > DEBUG_LEVEL )
+ return;
+
+ va_start( va, msg );
+
+ fwprintf( stderr, L"%ls: ", program_name );
+ vfwprintf( stderr, msg, va );
+ va_end( va );
+ fwprintf( stderr, L"\n" );
+}
+
+wchar_t *escape( wchar_t *in,
+ int escape_all )
+{
+ wchar_t *killme=in;
+ wchar_t *out = malloc( sizeof(wchar_t)*(wcslen(in)*4 + 1));
+ if( out != 0 )
+ {
+ wchar_t *pos=out;
+ while( *in != 0 )
+ {
+ switch( *in )
+ {
+ case L'\t':
+ *(pos++) = L'\\';
+ *(pos++) = L't';
+ break;
+
+ case L'\n':
+ *(pos++) = L'\\';
+ *(pos++) = L'n';
+ break;
+
+ case L'\b':
+ *(pos++) = L'\\';
+ *(pos++) = L'b';
+ break;
+
+ case L'\r':
+ *(pos++) = L'\\';
+ *(pos++) = L'r';
+ break;
+
+ case L'\e':
+ *(pos++) = L'\\';
+ *(pos++) = L'e';
+ break;
+
+ case L'\\':
+ case L'&':
+ case L'$':
+ case L' ':
+ case L'#':
+ case L'^':
+ case L'<':
+ case L'>':
+ case L'@':
+ case L'(':
+ case L')':
+ case L'{':
+ case L'}':
+ case L'?':
+ case L'*':
+ case L'|':
+ case L';':
+ case L':':
+ case L'\'':
+ case L'\"':
+ if( escape_all )
+ *pos++ = L'\\';
+ *pos++ = *in;
+ break;
+
+ default:
+ if( *in < 32 )
+ {
+ int tmp = (*in)%16;
+ *pos++ = L'\\';
+ *pos++ = L'x';
+ *pos++ = ((*in>15)? L'1' : L'0');
+ *pos++ = tmp > 9? L'a'+(tmp-10):L'0'+tmp;
+ }
+ else
+ *pos++ = *in;
+ break;
+ }
+ in++;
+ }
+ *pos = 0;
+ free(killme);
+ }
+
+ return out;
+}
+
+
+wchar_t *unescape( wchar_t * in, int escape_special )
+{
+ int in_pos, out_pos, len = wcslen( in );
+ int c;
+ int bracket_count=0;
+ wchar_t prev=0;
+
+ for( in_pos=0, out_pos=0; in_pos<len; prev=in[out_pos], out_pos++, in_pos++ )
+ {
+ c = in[in_pos];
+ if( c == L'\\' )
+ {
+ switch( in[++in_pos] )
+ {
+ case L'\0':
+ error( SYNTAX_ERROR, L"Unexpected end of string", -1 );
+ return in;
+
+ break;
+
+ case L'n':
+ in[out_pos]=L'\n';
+ break;
+
+ case L'r':
+ in[out_pos]=L'\r';
+ break;
+
+ case L't':
+ in[out_pos]=L'\t';
+ break;
+
+ case L'b':
+ in[out_pos]=L'\b';
+ break;
+
+ case L'e':
+ in[out_pos]=L'\e';
+ break;
+
+ case L'u':
+ case L'U':
+ case L'x':
+ case L'o':
+ {
+ int i;
+ wchar_t res=0;
+ int chars=2;
+ int base=16;
+
+ switch( in[in_pos] )
+ {
+ case L'u':
+ base=16;
+ chars=4;
+ break;
+
+ case L'U':
+ base=16;
+ chars=8;
+ break;
+
+ case L'x':
+ base=16;
+ chars=2;
+ break;
+
+ case L'o':
+ base=8;
+ chars=3;
+ break;
+
+ }
+
+ for( i=0; i<chars; i++ )
+ {
+ int d = convert_digit( in[++in_pos],base);
+ if( d < 0 )
+ {
+ in_pos--;
+ break;
+ }
+
+ res=(res*base)|d;
+
+ }
+
+ in[out_pos] = res;
+
+ break;
+ }
+
+ default:
+ in[out_pos]=in[in_pos];
+ break;
+ }
+ }
+ else
+ {
+ switch( in[in_pos]){
+ case L'~':
+ if( escape_special && (in_pos == 0) )
+ in[out_pos]=HOME_DIRECTORY;
+ else
+ in[out_pos] = L'~';
+ break;
+ case L'%':
+ if( escape_special && (in_pos == 0) )
+ in[out_pos]=PROCESS_EXPAND;
+ else
+ in[out_pos]=in[in_pos];
+ break;
+ case L'*':
+ if( escape_special )
+ {
+ if( out_pos > 0 && in[out_pos-1]==ANY_STRING )
+ {
+ out_pos--;
+ in[out_pos] = ANY_STRING_RECURSIVE;
+ }
+ else
+ in[out_pos]=ANY_STRING;
+ }
+ else
+ in[out_pos]=in[in_pos];
+ break;
+ case L'?':
+ if( escape_special )
+ in[out_pos]=ANY_CHAR;
+ else
+ in[out_pos]=in[in_pos];
+ break;
+ case L'$':
+ if( escape_special )
+ in[out_pos]=VARIABLE_EXPAND;
+ else
+ in[out_pos]=in[in_pos];
+ break;
+ case L'{':
+ if( escape_special )
+ {
+ bracket_count++;
+ in[out_pos]=BRACKET_BEGIN;
+ }
+ else
+ in[out_pos]=in[in_pos];
+ break;
+ case L'}':
+ if( escape_special )
+ {
+ bracket_count--;
+ in[out_pos]=BRACKET_END;
+ }
+ else
+ in[out_pos]=in[in_pos];
+ break;
+
+ case L',':
+ if( escape_special && bracket_count && prev!=BRACKET_SEP)
+ {
+ in[out_pos]=BRACKET_SEP;
+ }
+ else
+ in[out_pos]=in[in_pos];
+ break;
+
+ case L'\'':
+ case L'\"':
+ {
+ wchar_t *end = quote_end( &in[in_pos] );
+ int len;
+
+ if( end == 0 )
+ {
+ error( SYNTAX_ERROR, L"Unexpected end of string", -1 );
+ return in;
+ }
+
+ len = end- &in[in_pos]-1;
+
+ if( escape_special)
+ in[out_pos++]=INTERNAL_SEPARATOR;
+
+ memmove( &in[out_pos], &in[in_pos+1], sizeof(wchar_t)*(len) );
+ in_pos += len+1;
+ out_pos += len-1;
+
+ if( escape_special)
+ in[++out_pos]=INTERNAL_SEPARATOR;
+
+ break;
+ }
+ default:
+ in[out_pos] = in[in_pos];
+ break;
+ }
+ }
+ }
+ in[out_pos]=L'\0';
+ return in;
+}
+
diff --git a/common.h b/common.h
new file mode 100644
index 00000000..cb3d7b00
--- /dev/null
+++ b/common.h
@@ -0,0 +1,248 @@
+/** \file common.h
+ Prototypes for various functions, mostly string utilities, that are used by most parts of fish.
+*/
+
+
+/**
+ Under curses, tputs expects an int (*func)(char) as its last parameter, but in ncurses, tputs expects a int (*func)(int) as its last parameter. tputs_arg_t is defined to always be what tputs expects. Hopefully.
+*/
+
+#ifdef NCURSES_VERSION
+typedef int tputs_arg_t;
+#else
+typedef char tputs_arg_t;
+#endif
+
+/**
+ Maximum number of bytes in a utf-8 character
+*/
+#define MAX_UTF8_BYTES 6
+
+/**
+ Amount of debug info to show. Higher level means more debug info will be displayed
+*/
+#define DEBUG_LEVEL 1
+
+/**
+ Color code for set_color. Does not update the color.
+*/
+
+#define FISH_COLOR_IGNORE -1
+
+/**
+ Color code for set_color. Sets the default color.
+*/
+#define FISH_COLOR_RESET -2
+
+/** Save the shell mode on startup so we can restore them on exit */
+extern struct termios shell_modes;
+
+/**
+ The character to use where the text has been truncated. Is an ellipsis on unicode system and a $ on other systems.
+*/
+extern wchar_t ellipsis_char;
+
+/**
+ The maximum number of charset convertion errors to report
+*/
+extern int error_max;
+
+/**
+ Profiling flag. True if commands should be profiled.
+*/
+extern char *profile;
+
+/**
+ Name of the current program. Should be set at startup. Used by the
+ debug function.
+*/
+extern wchar_t *program_name;
+/**
+ Take an array_list_t containing wide strings and converts them to a wchar_t **.
+*/
+wchar_t **list_to_char_arr( array_list_t *l );
+
+/**
+ Read a line from the stream f into the buffer buff of length len. If
+ buff is to small, it will be reallocated, and both buff and len will
+ be updated to reflect this. Returns the number of bytes read or -1
+ on failiure.
+
+ If the carriage return character is encountered, it is
+ ignored. fgetws() considers the line to end if reading the file
+ results in either a newline (L'\n') character, the null (L'\\0')
+ character or the end of file (WEOF) character.
+*/
+int fgetws2( wchar_t **buff, int *len, FILE *f );
+
+/**
+ Sorts a list of wide strings according to the wcsfilecmp-function from the util library
+*/
+void sort_list( array_list_t *comp );
+
+/**
+ Returns a newly allocated wide character string equivalent of the specified multibyte character string
+*/
+wchar_t *str2wcs( const char *in );
+
+/**
+ Returns a newly allocated multibyte character string equivalent of the specified wide character string
+*/
+char *wcs2str( const wchar_t *in );
+
+/**
+ Returns a newly allocated wide character string array equivalent of the specified multibyte character string array
+*/
+char **wcsv2strv( const wchar_t **in );
+
+/**
+ Returns a newly allocated multibyte character string array equivalent of the specified wide character string array
+*/
+wchar_t **strv2wcsv( const char **in );
+
+/**
+ Returns a newly allocated concatenation of the specified wide character strings
+*/
+wchar_t *wcsdupcat( const wchar_t *a, const wchar_t *b );
+
+/**
+ Returns a newly allocated concatenation of the specified wide character strings. The last argument must be a null pointer.
+*/
+wchar_t *wcsdupcat2( const wchar_t *a, ... );
+
+/**
+ Returns a newly allocated wide character string wich is a copy of the string in, but of length c or shorter. The returned string is always null terminated, and the null is not included in the string length.
+*/
+wchar_t *wcsndup( const wchar_t *in, int c );
+
+/**
+ Converts from wide char to digit in the specified base. If d is not
+ a valid digit in the specified base, return -1.
+*/
+long convert_digit( wchar_t d, int base );
+
+
+/**
+ Convert a wide character string to a number in the specified
+ base. This functions is the wide character string equivalent of
+ strtol. For bases of 10 or lower, 0..9 are used to represent
+ numbers. For bases below 36, a-z and A-Z are used to represent
+ numbers higher than 9. Higher bases than 36 are not supported.
+*/
+long wcstol(const wchar_t *nptr,
+ wchar_t **endptr,
+ int base);
+
+size_t
+wcslcat(wchar_t *dst, const wchar_t *src, size_t siz);
+
+size_t
+wcslcpy(wchar_t *dst, const wchar_t *src, size_t siz);
+
+/**
+ Create a dublicate string. Wide string version of strdup.
+*/
+wchar_t *wcsdup(const wchar_t *in);
+
+/**
+ Case insensitive string compare function. Wide string version of
+ strcasecmp.
+
+ This implementation of wcscasecmp does not take into account
+ esoteric locales where uppercase and lowercase do not cleanly
+ transform between each other. Hopefully this should be fine since
+ fish only uses this function with one of the strings supplied by
+ fish and guaranteed to be a sane, english word.
+*/
+int wcscasecmp( const wchar_t *a, const wchar_t *b );
+
+/**
+ Test if the given string is a valid variable name
+*/
+
+int wcsvarname( wchar_t *str );
+
+/**
+ The prototype for this function is missing in some libc
+ implementations. Fish has a fallback implementation in case the
+ implementation is missing altogether.
+*/
+int wcwidth( wchar_t c );
+
+
+/**
+ A wcswidth workalike. Fish uses this since the regular wcswidth seems flaky.
+*/
+int my_wcswidth( const wchar_t *c );
+
+/**
+ This functions returns the end of a quoted substring. It can handle nested single and double quotes.
+*/
+wchar_t *quote_end( const wchar_t *in );
+
+/**
+ A call to this function will reset the error counter. Some
+ functions print out non-critical error messages. These should check
+ the error_count before, and skip printing the message if
+ MAX_ERROR_COUNT messages have been printed. The error_reset()
+ should be called after each interactive command executes, to allow
+ new messages to be printed.
+*/
+void error_reset();
+
+/**
+ Set the locale, also change the ellipsis character
+*/
+void fish_setlocale(int category, const wchar_t *locale);
+
+/**
+ Checks if \c needle is included in the list of strings specified
+
+ \param needle the string to search for in the list
+*/
+int contains_str( const wchar_t *needle, ... );
+
+/**
+ Call read while blocking the SIGCHLD signal. Should only be called
+ if you _know_ there is data available for reading.
+*/
+int read_blocked(int fd, void *buf, size_t count);
+
+/**
+ This is for writing process notification messages. Has to write to
+ stdout, so clr_eol and such functions will work correctly. Not an
+ issue since this function is only used in interactive mode anyway.
+*/
+int writeb( tputs_arg_t b );
+
+void die_mem();
+
+/**
+ Clean up
+*/
+void common_destroy();
+
+/**
+ Issue a debug message
+
+ \param level the priority of the message. Lower number means higher priority. Messages with too high priority number will be discarded.
+ \param the message.
+*/
+void debug( int level, wchar_t *msg, ... );
+
+/**
+ Replace special characters with escape sequences. Newline is
+ replaced with \n, etc.
+
+ \param in The string to be escaped
+ \param escape_all Whether all characters wich hold special meaning in fish (Pipe, semicolon, etc,) should be escaped, or only unprintable characters
+ \return The escaped string, or 0 if there is not enough memory
+*/
+
+wchar_t *escape( wchar_t *in,
+ int escape_all );
+
+wchar_t *unescape( wchar_t * in, int escape_special );
+
+void block();
+void unblock();
diff --git a/complete.c b/complete.c
new file mode 100644
index 00000000..ea6999cb
--- /dev/null
+++ b/complete.c
@@ -0,0 +1,2227 @@
+/** \file complete.c
+ Functions related to tab-completion.
+
+ These functions are used for storing and retrieving tab-completion data, as well as for performing tab-completion.
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <termios.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <signal.h>
+
+#include "config.h"
+#include "util.h"
+#include "tokenizer.h"
+#include "wildcard.h"
+#include "proc.h"
+#include "parser.h"
+#include "function.h"
+#include "complete.h"
+#include "builtin.h"
+#include "env.h"
+#include "exec.h"
+#include "expand.h"
+#include "common.h"
+#include "reader.h"
+#include "history.h"
+#include "intern.h"
+
+#include "wutil.h"
+
+
+/*
+ Completion description strings for files
+
+ There are a few other completion description strings defined in expand.c
+*/
+
+/**
+ Description for ~USER completion
+*/
+#define COMPLETE_USER_DESC COMPLETE_SEP_STR L"User home"
+
+/**
+ Description for short variables. The value is concatenated to this description
+*/
+#define COMPLETE_VAR_DESC_VAL COMPLETE_SEP_STR L"Variable: "
+
+/**
+ Description for generic executable
+*/
+#define COMPLETE_EXEC_DESC COMPLETE_SEP_STR L"Executable"
+/**
+ Description for link to executable
+*/
+#define COMPLETE_EXEC_LINK_DESC COMPLETE_SEP_STR L"Executable link"
+
+/**
+ Description for regular file
+*/
+#define COMPLETE_FILE_DESC COMPLETE_SEP_STR L"File"
+/**
+ Description for character device
+*/
+#define COMPLETE_CHAR_DESC COMPLETE_SEP_STR L"Character device"
+/**
+ Description for block device
+*/
+#define COMPLETE_BLOCK_DESC COMPLETE_SEP_STR L"Block device"
+/**
+ Description for fifo buffer
+*/
+#define COMPLETE_FIFO_DESC COMPLETE_SEP_STR L"Fifo"
+/**
+ Description for symlink
+*/
+#define COMPLETE_SYMLINK_DESC COMPLETE_SEP_STR L"Symbolic link"
+/**
+ Description for Rotten symlink
+*/
+#define COMPLETE_ROTTEN_SYMLINK_DESC COMPLETE_SEP_STR L"Rotten symbolic link"
+/**
+ Description for socket
+*/
+#define COMPLETE_SOCKET_DESC COMPLETE_SEP_STR L"Socket"
+/**
+ Description for directory
+*/
+#define COMPLETE_DIRECTORY_DESC COMPLETE_SEP_STR L"Directory"
+
+/**
+ Description for function
+*/
+#define COMPLETE_FUNCTION_DESC COMPLETE_SEP_STR L"Function"
+/**
+ Description for builtin command
+*/
+#define COMPLETE_BUILTIN_DESC COMPLETE_SEP_STR L"Builtin"
+
+/**
+ The command to run to get a description from a file suffix
+*/
+#define SUFFIX_CMD_STR L"mimedb 2>/dev/null -fd "
+
+/**
+ The maximum number of commands on which to perform description
+ lookup. The lookup process is quite time consuming, so this should
+ be set to a pretty low number.
+*/
+#define MAX_CMD_DESC_LOOKUP 10
+
+/**
+ Condition cache value returned from hashtable when this condition
+ has not yet been tested
+*/
+#define CC_NOT_TESTED 0
+
+/**
+ Condition cache value returned from hashtable when the condition is met
+*/
+#define CC_TRUE L"true"
+
+/**
+ Condition cache value returned from hashtable when the condition is not met
+*/
+#define CC_FALSE L"false"
+
+/**
+ Struct describing a completion option entry.
+
+ If short_opt and long_opt are both zero, the comp field must not be
+ empty and contains a list of arguments to the command.
+
+ If either short_opt or long_opt are non-zero, they specify a switch
+ for the command. If \c comp is also not empty, it contains a list
+ of arguments that may only follow after the specified switch.
+
+*/
+typedef struct complete_entry_opt
+{
+ /** Short style option */
+ wchar_t short_opt;
+ /** Long style option */
+ const wchar_t *long_opt;
+ /** Arguments to the option */
+ const wchar_t *comp;
+ /** Description of the completion */
+ const wchar_t *desc;
+ /** Condition under which to use the option */
+ const wchar_t *condition;
+ /** Must be one of the values SHARED, NO_FILES, NO_COMMON, EXCLUSIVE. */
+ int result_mode;
+ /** Next option in the linked list */
+ /** True if old style long options are used */
+ int old_mode;
+ struct complete_entry_opt *next;
+}
+ complete_entry_opt;
+
+/**
+ Struct describing a command completion
+*/
+typedef struct complete_entry
+{
+ /** True if command is a path */
+ int cmd_type;
+
+ /** Command string */
+ const wchar_t *cmd;
+ /** String containing all short option characters */
+ wchar_t *short_opt_str;
+ /** Linked list of all options */
+ complete_entry_opt *first_option;
+ /** Next command completion in the linked list */
+ struct complete_entry *next;
+ /** True if no other options than the ones supplied are possible */
+ int authorative;
+}
+ complete_entry;
+
+/** First node in the linked list of all completion entries */
+static complete_entry *first_entry=0;
+
+/** Hashtable containing all descriptions that describe an executable */
+static hash_table_t *suffix_hash=0;
+
+/**
+ Table of completions conditions that have already been tested and
+ the corresponding test results
+*/
+static hash_table_t *condition_cache=0;
+
+/**
+ Set of commands for which completions have already been loaded
+*/
+static hash_table_t *loaded_completions=0;
+
+void complete_init()
+{
+}
+
+/**
+ This command clears the cache of condition tests created by \c condition_test().
+*/
+static void condition_cache_clear()
+{
+ if( condition_cache )
+ {
+ hash_destroy( condition_cache );
+ free( condition_cache );
+ condition_cache = 0;
+ }
+}
+
+/**
+ Test if the specified script returns zero. The result is cached, so
+ that if multiple completions use the same condition, it needs only
+ be evaluated once. condition_cache_clear must be called after a
+ completion run to make sure that there are no stale completions.
+*/
+static int condition_test( const wchar_t *condition )
+{
+ const void *test_res;
+
+ if( !condition || !wcslen(condition) )
+ {
+// fwprintf( stderr, L"No condition specified\n" );
+ return 1;
+ }
+
+ if( !condition_cache )
+ {
+ condition_cache = malloc( sizeof( hash_table_t ) );
+ if( !condition_cache )
+ {
+ die_mem();
+
+ }
+
+ hash_init( condition_cache,
+ &hash_wcs_func,
+ &hash_wcs_cmp );
+
+
+ }
+
+ test_res = hash_get( condition_cache, condition );
+
+ if (test_res == CC_NOT_TESTED )
+ {
+ test_res = exec_subshell( condition, 0 )?CC_FALSE:CC_TRUE;
+// debug( 1, L"Eval returns %ls for '%ls'", test_res, condition );
+
+ hash_put( condition_cache, condition, test_res );
+
+ /*
+ Restore previous status information
+ */
+ }
+
+ if( test_res == CC_TRUE )
+ {
+// debug( 1, L"Use conditional completion on condition %ls", condition );
+ return 1;
+ }
+
+// debug( 1, L"Skip conditional completion on condition %ls", condition );
+ return 0;
+
+}
+
+
+/**
+ Recursively free all complete_entry_opt structs and their contents
+*/
+static void complete_free_opt_recursive( complete_entry_opt *o )
+{
+ if( o->next != 0 )
+ complete_free_opt_recursive( o->next );
+ free(o);
+}
+
+/**
+ Free a complete_entry and its contents
+*/
+static void complete_free_entry( complete_entry *c )
+{
+// free( c->cmd );
+ free( c->short_opt_str );
+ complete_free_opt_recursive( c->first_option );
+ free( c );
+}
+
+/**
+ Free hash key and hash value
+*/
+static void clear_hash_entry( const void *key, const void *data )
+{
+ free( (void *)key );
+ free( (void *)data );
+}
+
+
+static void clear_hash_value( const void *key, const void *data )
+{
+ free( (void *)data );
+}
+
+
+void complete_destroy()
+{
+ complete_entry *i=first_entry, *prev;
+ while( i )
+ {
+ prev = i;
+ i=i->next;
+ complete_free_entry( prev );
+ }
+
+ if( suffix_hash )
+ {
+ hash_foreach( suffix_hash, &clear_hash_entry );
+ hash_destroy( suffix_hash );
+ free( suffix_hash );
+ }
+
+ if( loaded_completions )
+ {
+ hash_foreach( loaded_completions,
+ &clear_hash_value );
+ hash_destroy( loaded_completions );
+ free( loaded_completions );
+ }
+
+}
+
+/**
+ Search for an exactly matching completion entry
+*/
+static complete_entry *complete_find_exact_entry( const wchar_t *cmd,
+ const int cmd_type )
+{
+ complete_entry *i;
+ for( i=first_entry; i; i=i->next )
+ {
+ if( ( wcscmp(cmd, i->cmd)==0) && ( cmd_type == i->cmd_type ))
+ {
+ return i;
+ }
+ }
+ return 0;
+}
+
+void complete_add( const wchar_t *cmd,
+ int cmd_type,
+ wchar_t short_opt,
+ const wchar_t *long_opt,
+ int old_mode,
+ int result_mode,
+ int authorative,
+ const wchar_t *condition,
+ const wchar_t *comp,
+ const wchar_t *desc )
+{
+ complete_entry *c =
+ complete_find_exact_entry( cmd, cmd_type );
+ complete_entry_opt *opt;
+ wchar_t *tmp;
+
+
+ if( c == 0 )
+ {
+ if( !(c = malloc( sizeof(complete_entry) )))
+ die_mem();
+
+
+ c->next = first_entry;
+ first_entry = c;
+
+ c->first_option = 0;
+
+ c->cmd = intern( cmd );
+ c->cmd_type = cmd_type;
+ c->short_opt_str = wcsdup(L"");
+ }
+
+/* wprintf( L"Add completion to option (short %lc, long %ls)\n", short_opt, long_opt );*/
+ if( !(opt = malloc( sizeof( complete_entry_opt ) )))
+ die_mem();
+
+ opt->next = c->first_option;
+ c->first_option = opt;
+ c->authorative = authorative;
+ if( short_opt != L'\0' )
+ {
+ int len = 1 + ((result_mode & NO_COMMON) != 0);
+ c->short_opt_str =
+ realloc( c->short_opt_str,
+ sizeof(wchar_t)*(wcslen( c->short_opt_str ) + 1 + len) );
+ wcsncat( c->short_opt_str,
+ &short_opt, 1 );
+ if( len == 2 )
+ wcscat( c->short_opt_str, L":" );
+ }
+
+ opt->short_opt = short_opt;
+ opt->result_mode = result_mode;
+ opt->old_mode=old_mode;
+
+ opt->comp = intern(comp);
+ opt->condition = intern(condition);
+ opt->long_opt = intern( long_opt );
+
+ if( desc && wcslen( desc ) )
+ {
+ tmp = wcsdupcat( COMPLETE_SEP_STR, desc );
+ opt->desc = intern( tmp );
+ free( tmp );
+ }
+ else
+ opt->desc = L"";
+}
+
+void complete_remove( const wchar_t *cmd,
+ int cmd_type,
+ wchar_t short_opt,
+ const wchar_t *long_opt )
+{
+ complete_entry *e, *eprev=0, *enext=0;
+ for( e = first_entry; e; e=enext )
+ {
+ enext=e->next;
+
+ if( (cmd_type == e->cmd_type ) &&
+ ( wcscmp( cmd, e->cmd) == 0 ) )
+ {
+ complete_entry_opt *o, *oprev=0, *onext=0;
+
+ if(( short_opt == 0 ) && (long_opt == 0 ) )
+ {
+ complete_free_opt_recursive( e->first_option );
+ e->first_option=0;
+ }
+ else
+ {
+
+ for( o= e->first_option; o; o=onext )
+ {
+ onext=o->next;
+
+ if( ( short_opt==o->short_opt ) ||
+ ( wcscmp( long_opt, o->long_opt ) == 0 ) )
+ {
+ wchar_t *pos;
+ /* fwprintf( stderr,
+ L"remove option -%lc --%ls\n",
+ o->short_opt?o->short_opt:L' ',
+ o->long_opt );
+ */
+ if( o->short_opt )
+ {
+ pos = wcschr( e->short_opt_str,
+ o->short_opt );
+ if( pos )
+ {
+ wchar_t *pos2 = pos+1;
+ while( *pos2 == L':' )
+ pos2++;
+
+ memmove( pos,
+ pos2,
+ sizeof(wchar_t)*wcslen(pos2) );
+ }
+ }
+
+ if( oprev == 0 )
+ {
+ e->first_option = o->next;
+ }
+ else
+ {
+ oprev->next = o->next;
+ }
+ free( o );
+ }
+ else
+ oprev = o;
+ }
+ }
+
+ if( e && (e->first_option == 0) )
+ {
+ if( eprev == 0 )
+ {
+ first_entry = e->next;
+ }
+ else
+ {
+ eprev->next = e->next;
+ }
+
+ free( e->short_opt_str );
+ free( e );
+ e=0;
+ }
+
+ }
+
+ if( e )
+ eprev = e;
+ }
+}
+
+/**
+ Find the full path and commandname from a command string. the
+ result of \c pathp must be freed by the caller, the result of \c
+ cmdp must not be freed by the caller.
+*/
+static void parse_cmd_string( const wchar_t *str,
+ wchar_t **pathp,
+ wchar_t **cmdp )
+{
+ wchar_t *cmd, *path;
+
+ /* Get the path of the command */
+ path = get_filename( str );
+ if( path == 0 )
+ path = wcsdup(L"");
+
+ /* Make sure the path is not included in the command */
+ cmd = wcsrchr( str, L'/' );
+ if( cmd != 0 )
+ cmd++;
+ else
+ cmd = (wchar_t *)str;
+
+ *pathp=path;
+ *cmdp=cmd;
+}
+
+int complete_is_valid_option( const wchar_t *str,
+ const wchar_t *opt,
+ array_list_t *errors )
+{
+
+ complete_entry *i;
+ complete_entry_opt *o;
+ wchar_t *cmd, *path;
+ int found_match = 0;
+ int authorative = 1;
+
+ int opt_found=0;
+
+ hash_table_t gnu_match_hash;
+
+ int is_gnu_opt=0;
+ int is_old_opt=0;
+ int is_short_opt=0;
+ int is_gnu_exact=0;
+ int gnu_opt_len=0;
+
+ char *short_validated;
+ /*
+ Check some generic things like -- and - options.
+ */
+ switch( wcslen(opt ) )
+ {
+
+ case 0:
+ return 1;
+
+
+ case 1:
+ return opt[0] == L'-';
+
+ case 2:
+ if( wcscmp( L"--", opt ) == 0 )
+ return 1;
+ }
+
+ if( opt[0] != L'-' )
+ {
+ if( errors )
+ al_push( errors, wcsdup(L"Option does not begin with a '-'") );
+
+ return 0;
+ }
+
+
+
+ if( !(short_validated = malloc( wcslen( opt ) )))
+ die_mem();
+
+ memset( short_validated, 0, wcslen( opt ) );
+
+ hash_init( &gnu_match_hash,
+ &hash_wcs_func,
+ &hash_wcs_cmp );
+
+ is_gnu_opt = opt[1]==L'-';
+ if( is_gnu_opt )
+ {
+ wchar_t *opt_end = wcschr(opt, L'=' );
+ if( opt_end )
+ gnu_opt_len = (opt_end-opt)-2;
+ else
+ gnu_opt_len = wcslen(opt)-2;
+// fwprintf( stderr, L"Length %d optend %d\n", gnu_opt_len, opt_end );
+
+ }
+
+ parse_cmd_string( str, &path, &cmd );
+
+ /*
+ Make sure completions are loaded for the specified command
+ */
+ complete_load( cmd, 0 );
+
+ for( i=first_entry; i; i=i->next )
+ {
+ wchar_t *match = i->cmd_type?path:cmd;
+ const wchar_t *a;
+
+ if( !wildcard_match( match, i->cmd ) )
+ continue;
+
+ found_match = 1;
+
+ if( !i->authorative )
+ {
+ authorative = 0;
+ break;
+ }
+
+
+ if( is_gnu_opt )
+ {
+
+ for( o = i->first_option; o; o=o->next )
+ {
+ if( o->old_mode )
+ continue;
+ //fwprintf( stderr, L"Compare \'%ls\' against \'%ls\'\n", &opt[2], o->long_opt );
+ if( wcsncmp( &opt[2], o->long_opt, gnu_opt_len )==0)
+ {
+ //fwprintf( stderr, L"Found gnu match %ls\n", o->long_opt );
+ hash_put( &gnu_match_hash, o->long_opt, L"" );
+ if( (wcsncmp( &opt[2],
+ o->long_opt,
+ wcslen( o->long_opt) )==0) )
+ is_gnu_exact=1;
+ }
+ }
+ }
+ else
+ {
+ /* Check for old style options */
+ for( o = i->first_option; o; o=o->next )
+ {
+ if( !o->old_mode )
+ continue;
+
+
+ if( wcscmp( &opt[1], o->long_opt )==0)
+ {
+// fwprintf( stderr, L"Found old match %ls\n", o->long_opt );
+ opt_found = 1;
+ is_old_opt = 1;
+ break;
+ }
+
+ }
+
+ if( is_old_opt )
+ break;
+
+
+ for( a = &opt[1]; *a; a++ )
+ {
+
+ wchar_t *str_pos = wcschr(i->short_opt_str, *a);
+
+ if (str_pos )
+ {
+ if( *(str_pos +1)==L':' )
+ {
+ /*
+ This is a short option with an embedded argument,
+ call complete_is_valid_argument on the argument.
+ */
+ wchar_t nopt[3];
+ nopt[0]=L'-';
+ nopt[1]=opt[1];
+ nopt[2]=L'\0';
+
+ //fwprintf( stderr, L"Pos %d, shortopt %lc has argument\n", a-opt, *a );
+ short_validated[a-opt] =
+ complete_is_valid_argument( str, nopt, &opt[2]);
+ }
+ else
+ {
+ // fwprintf( stderr, L"Pos %d, shortopt %lc is ok\n", a-opt, *a );
+ short_validated[a-opt]=1;
+ }
+
+ }
+ }
+
+ }
+
+
+ }
+ free( path );
+
+ if( authorative )
+ {
+
+ if( !is_gnu_opt && !is_old_opt )
+ is_short_opt = 1;
+
+
+ if( is_short_opt )
+ {
+ int j;
+
+ opt_found=1;
+ for( j=1; j<wcslen(opt); j++)
+ {
+ if ( !short_validated[j])
+ {
+ // fwprintf( stderr, L"Pos %d, shortopt %lc is not ok\n", j, opt[j] );
+ if( errors )
+ {
+ wchar_t str[2];
+ str[0] = opt[j];
+ str[1]=0;
+ al_push( errors,
+ wcsdupcat2(L"Unknown option \'", str, L"\'", 0) );
+ }
+
+ opt_found = 0;
+ break;
+ }
+
+ }
+ }
+
+ if( is_gnu_opt )
+ {
+// fwprintf( stderr, L"Found %d matches\n", hash_get_count( &gnu_match_hash ) );
+ opt_found = is_gnu_exact || (hash_get_count( &gnu_match_hash )==1);
+ if( errors && !opt_found )
+ {
+ if( hash_get_count( &gnu_match_hash )==0)
+ {
+ al_push( errors,
+ wcsdupcat2(L"Unknown option \'", opt, L"\'", 0) );
+ }
+ else
+ {
+ al_push( errors,
+ wcsdupcat2(L"Multiple matches for option \'", opt, L"\'", 0) );
+ }
+ }
+ }
+ }
+
+ hash_destroy( &gnu_match_hash );
+ free( short_validated );
+
+ return (authorative && found_match)?opt_found:1;
+}
+
+int complete_is_valid_argument( const wchar_t *str,
+ const wchar_t *opt,
+ const wchar_t *arg )
+{
+ return 1;
+}
+
+/**
+ Use the mimedb command to look up a description for a given suffix
+*/
+static const wchar_t *complete_get_desc_suffix( const wchar_t *suff_orig )
+{
+
+ int len = wcslen(suff_orig );
+
+ wchar_t *suff;
+ wchar_t *pos;
+
+ if( len == 0 )
+ return COMPLETE_FILE_DESC;
+
+ if( !suffix_hash )
+ {
+ suffix_hash = malloc( sizeof( hash_table_t) );
+ if( !suffix_hash )
+ die_mem();
+ hash_init( suffix_hash, &hash_wcs_func, &hash_wcs_cmp );
+ }
+
+ suff = wcsdup(suff_orig);
+
+ for( pos=suff; *pos; pos++ )
+ {
+ if( wcschr( L"?;#~@&", *pos ) )
+ {
+ *pos=0;
+ break;
+ }
+ }
+
+ suff = expand_escape( suff, 0 );
+
+ wchar_t *desc = (wchar_t *)hash_get( suffix_hash, suff );
+
+ if( !desc )
+ {
+
+ wchar_t *cmd = wcsdupcat( SUFFIX_CMD_STR, suff );
+
+ if( cmd )
+ {
+
+ array_list_t l;
+ al_init( &l );
+
+ exec_subshell( cmd, &l );
+ free(cmd);
+
+ if( al_get_count( &l )>0 )
+ {
+ wchar_t *ln = (wchar_t *)al_get(&l, 0 );
+ if( wcscmp( ln, L"unknown" ) != 0 )
+ {
+ desc = wcsdupcat( COMPLETE_SEP_STR, ln);
+ /*
+ I have decided I prefer to have the description
+ begin in uppercase and the whole universe will just
+ have to accept it. Hah!
+ */
+ desc[1]=towupper(desc[1]);
+ }
+ }
+
+ al_foreach( &l, (void (*)(const void *))&free );
+ al_destroy( &l );
+ }
+
+ if( !desc )
+ {
+ desc = wcsdup(COMPLETE_FILE_DESC);
+ }
+
+ hash_put( suffix_hash, suff!=suff_orig?suff:wcsdup(suff), desc );
+ }
+ else
+ {
+ free( suff );
+ }
+
+ return desc;
+}
+
+
+const wchar_t *complete_get_desc( const wchar_t *filename )
+{
+ struct stat buf;
+ const wchar_t *desc = COMPLETE_FILE_DESC;
+
+
+ if( lwstat( filename, &buf )==0)
+ {
+ if( waccess( filename, X_OK ) == 0 )
+ {
+ desc = COMPLETE_EXEC_DESC;
+ }
+
+ if( S_ISCHR(buf.st_mode) )
+ desc= COMPLETE_CHAR_DESC;
+ else if( S_ISBLK(buf.st_mode) )
+ desc = COMPLETE_BLOCK_DESC;
+ else if( S_ISFIFO(buf.st_mode) )
+ desc = COMPLETE_FIFO_DESC;
+ else if( S_ISLNK(buf.st_mode))
+ {
+ struct stat buf2;
+ desc = COMPLETE_SYMLINK_DESC;
+
+ if( waccess( filename, X_OK ) == 0 )
+ desc = COMPLETE_EXEC_LINK_DESC;
+
+ if( wstat( filename, &buf2 ) == 0 )
+ {
+ if( S_ISDIR(buf2.st_mode) )
+ {
+ desc = L"/" COMPLETE_SYMLINK_DESC;
+ }
+ }
+ else
+ {
+ switch( errno )
+ {
+ case ENOENT:
+ desc = COMPLETE_ROTTEN_SYMLINK_DESC;
+ break;
+
+ case EACCES:
+ break;
+
+ default:
+ wperror( L"stat" );
+ break;
+ }
+ }
+ }
+ else if( S_ISSOCK(buf.st_mode))
+ desc= COMPLETE_SOCKET_DESC;
+ else if( S_ISDIR(buf.st_mode) )
+ desc= L"/" COMPLETE_DIRECTORY_DESC;
+ }
+/* else
+ {
+
+ switch( errno )
+ {
+ case EACCES:
+ break;
+
+ default:
+ fprintf( stderr, L"The following error happened on file %ls\n", filename );
+ wperror( L"lstat" );
+ break;
+ }
+ }
+*/
+ if( desc == COMPLETE_FILE_DESC )
+ {
+ wchar_t *suffix = wcsrchr( filename, L'.' );
+ if( suffix != 0 )
+ {
+ if( !wcsrchr( suffix, L'/' ) )
+ {
+ desc = complete_get_desc_suffix( suffix );
+ }
+ }
+ }
+
+ return desc;
+}
+
+/**
+ Copy any strings in possible_comp which have the specified prefix
+ to the list comp_out. The prefix may contain wildcards.
+
+ There are three ways to specify descriptions for each
+ completion. Firstly, if a description has already been added to the
+ completion, it is _not_ replaced. Secondly, if the desc_func
+ function is specified, use it to determine a dynamic
+ completion. Thirdly, if none of the above are available, the desc
+ string is used as a description.
+
+ \param comp_out the destination list
+ \param wc_escaped the prefix, possibly containing wildcards. The wildcard should not have been unescaped, i.e. '*' should be used for any string, not the ANY_STRING character.
+ \param desc the default description, used for completions with no embedded description
+ \param desc_func the function that generates a description for those completions witout an embedded description
+ \param possible_comp the list of possible completions to iterate over
+*/
+static void copy_strings_with_prefix( array_list_t *comp_out,
+ const wchar_t *wc_escaped,
+ const wchar_t *desc,
+ const wchar_t *(*desc_func)(const wchar_t *),
+ array_list_t *possible_comp )
+{
+ int i;
+ wchar_t *wc;
+
+ wc = expand_one( wcsdup(wc_escaped), EXPAND_SKIP_SUBSHELL | EXPAND_SKIP_WILDCARDS);
+ if(!wc)
+ return;
+
+ if( wc[0] == L'~' )
+ {
+ wc=expand_tilde(wc);
+ }
+
+// int str_len = wcslen( str );
+ for( i=0; i<al_get_count( possible_comp ); i++ )
+ {
+ wchar_t *next_str = (wchar_t *)al_get( possible_comp, i );
+// fwprintf( stderr, L"try wc %ls against %ls\n", wc, next_str );
+
+ wildcard_complete( next_str, wc, desc, desc_func, comp_out );
+ }
+ free( wc );
+
+}
+
+/**
+ If command to complete is short enough, substitute
+ the description with the whatis information for the executable.
+*/
+static void complete_cmd_desc( const wchar_t *cmd, array_list_t *comp )
+{
+ int i;
+ const wchar_t *cmd_start;
+ int cmd_len;
+ wchar_t *apropos_cmd=0;
+ array_list_t list;
+ hash_table_t lookup;
+ wchar_t *whatis_path = env_get( L"__fish_whatis_path" );
+ wchar_t *esc;
+
+ if( !cmd )
+ return;
+
+ cmd_start=wcschr(cmd, L'/');
+ if( !cmd_start )
+ cmd_start = cmd;
+
+ cmd_len = wcslen(cmd_start);
+
+ /*
+ Using apropos with a single-character search term produces far
+ to many results - require at least two characters if we don't
+ know the location of the whatis-database.
+ */
+ if( (cmd_len < 2) && (!whatis_path) )
+ return;
+
+ if( wildcard_has( cmd_start, 0 ) )
+ {
+ return;
+ }
+
+ esc = expand_escape( wcsdup(cmd_start), 1 );
+
+ if( esc )
+ {
+
+ if( whatis_path )
+ {
+ apropos_cmd =wcsdupcat2( L"grep ^/dev/null -F <", whatis_path, L" ", esc, 0 );
+ }
+ else
+ {
+ apropos_cmd = wcsdupcat( L"apropos ^/dev/null ", esc );
+ }
+ free(esc);
+
+ al_init( &list );
+ hash_init( &lookup, &hash_wcs_func, &hash_wcs_cmp );
+
+ /*
+ First locate a list of possible descriptions using a single
+ call to apropos or a direct search if we know the location
+ of the whatis database. This can take some time on slower
+ systems with a large set of manuals, but it should be ok
+ since apropos is only called once.
+ */
+ exec_subshell( apropos_cmd, &list );
+ /*
+ Then discard anything that is not a possible completion and put
+ the result into a hashtable with the completion as key and the
+ description as value.
+
+ Should be reasonably fast, since no memory allocations are needed.
+ */
+ for( i=0; i<al_get_count( &list); i++ )
+ {
+ wchar_t *el = (wchar_t *)al_get( &list, i );
+ wchar_t *key, *key_end, *val_begin;
+
+ if( !el )
+ continue;
+
+ //fwprintf( stderr, L"%ls\n", el );
+ if( wcsncmp( el, cmd_start, cmd_len ) != 0 )
+ continue;
+ //fwprintf( stderr, L"%ls\n", el );
+ key = el + cmd_len;
+
+ key_end = wcschr( el, L' ' );
+ if( !key_end )
+ {
+ key_end = wcschr( el, L'\t' );
+ if( !key_end )
+ {
+ continue;
+ }
+ }
+
+ *key_end = 0;
+ val_begin=key_end+1;
+
+ //fwprintf( stderr, L"Key %ls\n", el );
+
+ while( *val_begin != L'-' && *val_begin)
+ {
+ val_begin++;
+ }
+
+ if( !val_begin )
+ {
+ continue;
+ }
+
+ val_begin++;
+
+ while( *val_begin == L' ' || *val_begin == L'\t' )
+ {
+ val_begin++;
+ }
+
+ if( !*val_begin )
+ {
+ continue;
+ }
+
+ /*
+ And once again I make sure the first character is uppercased
+ because I like it that way, and I get to decide these
+ things.
+ */
+ val_begin[0]=towupper(val_begin[0]);
+ hash_put( &lookup, key, val_begin );
+ }
+
+ /*
+ Then do a lookup on every completion and if a match is found,
+ change to the new description.
+
+ This needs to do a reallocation for every description added, but
+ there shouldn't be that many completions, so it should be ok.
+ */
+ for( i=0; i<al_get_count(comp); i++ )
+ {
+ wchar_t *el = (wchar_t *)al_get( comp, i );
+ wchar_t *cmd_end = wcschr( el,
+ COMPLETE_SEP );
+ wchar_t *new_desc;
+
+ if( cmd_end )
+ *cmd_end = 0;
+
+ new_desc = (wchar_t *)hash_get( &lookup,
+ el );
+
+ if( new_desc )
+ {
+ wchar_t *new_el = wcsdupcat2( el,
+ COMPLETE_SEP_STR,
+ new_desc,
+ 0 );
+ al_set( comp, i, new_el );
+ free( el );
+ }
+ else
+ {
+ if( cmd_end )
+ *cmd_end = COMPLETE_SEP;
+ }
+ }
+ }
+
+ hash_destroy( &lookup );
+ al_foreach( &list,
+ (void(*)(const void *))&free );
+ al_destroy( &list );
+ free( apropos_cmd );
+}
+
+/**
+ Complete the specified command name. Search for executables in the
+ path, executables defined using an absolute path, functions,
+ builtins and directories for implicit cd commands.
+
+ \param cmd the command string to find completions for
+
+ B\param comp the list to add all completions to
+*/
+static void complete_cmd( const wchar_t *cmd,
+ array_list_t *comp )
+{
+ wchar_t *path;
+ wchar_t *path_cpy;
+ wchar_t *nxt_path;
+ wchar_t *state;
+ array_list_t possible_comp;
+ int i;
+ array_list_t tmp;
+ wchar_t *nxt_completion;
+
+ wchar_t *cdpath = env_get(L"CDPATH");
+ wchar_t *cdpath_cpy = wcsdup( cdpath?cdpath:L"." );
+
+ if( (wcschr( cmd, L'/') != 0) || (cmd[0] == L'~' ) )
+ {
+ array_list_t tmp;
+ al_init( &tmp );
+
+ expand_string( wcsdup(cmd),
+ comp,
+ ACCEPT_INCOMPLETE | EXECUTABLES_ONLY );
+ complete_cmd_desc( cmd, comp );
+ al_destroy( &tmp );
+ }
+ else
+ {
+ path = env_get(L"PATH");
+ path_cpy = wcsdup( path );
+
+ for( nxt_path = wcstok( path_cpy, ARRAY_SEP_STR, &state );
+ nxt_path != 0;
+ nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) )
+ {
+ nxt_completion = wcsdupcat2( nxt_path,
+ (nxt_path[wcslen(nxt_path)-1]==L'/'?L"":L"/"),
+ cmd,
+ 0 );
+ if( ! nxt_completion )
+ continue;
+
+ al_init( &tmp );
+
+ expand_string( nxt_completion,
+ &tmp,
+ ACCEPT_INCOMPLETE |
+ EXECUTABLES_ONLY );
+
+ for( i=0; i<al_get_count(&tmp); i++ )
+ {
+ al_push( comp, al_get( &tmp, i ) );
+ }
+
+ al_destroy( &tmp );
+
+ }
+ free( path_cpy );
+
+ complete_cmd_desc( cmd, comp );
+
+ /*
+ These return the original strings - don't free them
+ */
+
+ al_init( &possible_comp );
+ function_get_names( &possible_comp, cmd[0] == L'_' );
+ copy_strings_with_prefix( comp, cmd, COMPLETE_FUNCTION_DESC, &function_get_desc, &possible_comp );
+ al_truncate( &possible_comp, 0 );
+
+ builtin_get_names( &possible_comp );
+ copy_strings_with_prefix( comp, cmd, COMPLETE_BUILTIN_DESC, &builtin_get_desc, &possible_comp );
+ al_destroy( &possible_comp );
+
+
+ }
+
+// fwprintf( stderr, L"cdpath %ls\n", cdpath_cpy );
+ /*
+ Tab complete implicit cd for directories in CDPATH
+ */
+ for( nxt_path = wcstok( cdpath_cpy, ARRAY_SEP_STR, &state );
+ nxt_path != 0;
+ nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) )
+ {
+ int i;
+ array_list_t tmp;
+ wchar_t *nxt_completion=
+ wcsdupcat2( nxt_path,
+ (nxt_path[wcslen(nxt_path)-1]==L'/'?L"":L"/"),
+ cmd,
+ 0 );
+ if( ! nxt_completion )
+ continue;
+
+ al_init( &tmp );
+
+ expand_string( nxt_completion,
+ &tmp,
+ ACCEPT_INCOMPLETE | DIRECTORIES_ONLY );
+
+ for( i=0; i<al_get_count(&tmp); i++ )
+ {
+ wchar_t *nxt = (wchar_t *)al_get( &tmp, i );
+
+ wchar_t *desc = wcsrchr( nxt, COMPLETE_SEP );
+ int is_valid = (desc && (wcscmp(desc,
+ COMPLETE_DIRECTORY_DESC)==0));
+ if( is_valid )
+ {
+ al_push( comp, nxt );
+ }
+ else
+ {
+ free(nxt);
+ }
+ }
+
+ al_destroy( &tmp );
+
+ }
+
+ free( cdpath_cpy );
+}
+
+/**
+ Evaluate the argument list (as supplied by complete -a) and insert
+ any return matching completions. Matching is done using\c
+ copy_strings_with_prefix, meaning the completion may contain
+ wildcards. Logically, this is not always the right thing to do, but
+ I have yet to come up with a case where one would not want this.
+
+ \param str The string to complete.
+ \param args The list of option arguments to be evaluated.
+ \param desc Description of the completion
+ \param comp_out The list into which the results will be inserted
+*/
+static void complete_from_args( const wchar_t *str,
+ const wchar_t *args,
+ const wchar_t *desc,
+ array_list_t *comp_out )
+{
+ array_list_t possible_comp;
+ al_init( &possible_comp );
+
+ eval_args( args, &possible_comp );
+
+ debug( 3, L"desc is '%ls', %d long\n", desc, wcslen(desc) );
+
+ copy_strings_with_prefix( comp_out, str, desc, 0, &possible_comp );
+
+ al_foreach( &possible_comp, (void (*)(const void *))&free );
+ al_destroy( &possible_comp );
+}
+
+/**
+ Match against an old style long option
+*/
+static int param_match_old( complete_entry_opt *e,
+ wchar_t *optstr )
+{
+ return (optstr[0] == L'-') && (wcscmp( e->long_opt, &optstr[1] ) == 0);
+}
+
+/**
+ Match a parameter
+*/
+static int param_match( complete_entry_opt *e,
+ wchar_t *optstr )
+{
+ if( e->short_opt != L'\0' &&
+ e->short_opt == optstr[1] )
+ return 1;
+
+ if( !e->old_mode && (wcsncmp( L"--", optstr, 2 ) == 0 ))
+ {
+ if( wcscmp( e->long_opt, &optstr[2] ) == 0 )
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ Test if a string is an option with an argument, like --color=auto or -I/usr/include
+*/
+static wchar_t *param_match2( complete_entry_opt *e,
+ wchar_t *optstr )
+{
+ if( e->short_opt != L'\0' && e->short_opt == optstr[1] )
+ return &optstr[2];
+ if( !e->old_mode && (wcsncmp( L"--", optstr, 2 ) == 0) )
+ {
+ int len = wcslen( e->long_opt );
+
+ if( wcsncmp( e->long_opt, &optstr[2],len ) == 0 )
+ {
+ if( optstr[len+2] == L'=' )
+ return &optstr[len+3];
+ }
+ }
+ return 0;
+}
+
+/**
+ Tests whether a short option is a viable completion
+*/
+static int short_ok( wchar_t *arg,
+ wchar_t nextopt,
+ wchar_t *allopt )
+{
+ wchar_t *ptr;
+
+ if( arg[0] != L'-')
+ return arg[0] == L'\0';
+ if( arg[1] == L'-' )
+ return 0;
+
+ if( wcschr( arg, nextopt ) != 0 )
+ return 0;
+
+ for( ptr = arg+1; *ptr; ptr++ )
+ {
+ wchar_t *tmp = wcschr( allopt, *ptr );
+ /* Unknown option */
+ if( tmp == 0 )
+ {
+ /*fwprintf( stderr, L"Unknown option %lc", *ptr );*/
+
+ return 0;
+ }
+
+ if( *(tmp+1) == L':' )
+ {
+/* fwprintf( stderr, L"Woot %ls", allopt );*/
+ return 0;
+ }
+
+ }
+
+ return 1;
+}
+
+void complete_load( wchar_t *cmd,
+ int reload )
+{
+ const wchar_t *path_var;
+ array_list_t path_list;
+ int i;
+ string_buffer_t path;
+ time_t *tm;
+
+ /*
+ First check that the specified completion hasn't already been loaded
+ */
+ if( !loaded_completions )
+ {
+ loaded_completions = malloc( sizeof( hash_table_t ) );
+ if( !loaded_completions )
+ {
+ die_mem();
+ }
+ hash_init( loaded_completions, &hash_wcs_func, &hash_wcs_cmp );
+ }
+
+ /*
+ Get modification time of file
+ */
+ tm = (time_t *)hash_get( loaded_completions, cmd );
+
+ /*
+ Return if already loaded and we are skipping reloading
+ */
+ if( !reload && tm )
+ return;
+
+ /*
+ Do we know where to look for completions?
+ */
+ path_var = env_get( L"fish_complete_path" );
+ if( !path_var )
+ return;
+
+ al_init( &path_list );
+
+ sb_init( &path );
+
+ expand_variable_array( path_var, &path_list );
+
+
+ /*
+ Iterate over path searching for suitable completion files
+ */
+ for( i=0; i<al_get_count( &path_list ); i++ )
+ {
+ struct stat buf;
+ wchar_t *next = (wchar_t *)al_get( &path_list, i );
+ sb_clear( &path );
+ sb_append2( &path, next, L"/", cmd, L".fish", 0 );
+ if( (wstat( (wchar_t *)path.buff, &buf )== 0) &&
+ (waccess( (wchar_t *)path.buff, R_OK ) == 0) )
+ {
+ if( !tm || (*tm != buf.st_mtime ) )
+ {
+ wchar_t *esc = expand_escape( wcsdup((wchar_t *)path.buff), 1 );
+ wchar_t *src_cmd = wcsdupcat( L". ", esc );
+
+/* if( tm )
+ debug( 0, L"Reload %ls completions, old time was %d, new time is %d",
+ cmd,
+ tm?*tm:-1,
+ buf.st_mtime);
+*/
+
+ if( !tm )
+ {
+ tm = malloc(sizeof(time_t));
+ if( !tm )
+ die_mem();
+ }
+
+ *tm = buf.st_mtime;
+ hash_put( loaded_completions,
+ intern( cmd ),
+ tm );
+
+
+ free( esc );
+
+ complete_remove( cmd, COMMAND, 0, 0 );
+
+ /*
+ Source the completion file for the specified completion
+ */
+ exec_subshell( src_cmd, 0 );
+ free(src_cmd);
+
+ break;
+ }
+ }
+ }
+
+ /*
+ If no file was found we insert a last modified time of Jan 1, 1970.
+ This way, the completions_path wont be searched over and over again
+ when reload is set to 0.
+ */
+ if( !tm )
+ {
+// debug( 0, L"Insert null timestamp for command %ls", cmd );
+
+ tm = malloc(sizeof(time_t));
+ if( !tm )
+ die_mem();
+
+ *tm = 0;
+ hash_put( loaded_completions, intern( cmd ), tm );
+ }
+
+ sb_destroy( &path );
+ al_foreach( &path_list, (void (*)(const void *))&free );
+
+ al_destroy( &path_list );
+}
+
+
+/**
+ Find completion for the argument str of command cmd_orig with
+ previous option popt. Insert results into comp_out. Return 0 if file
+ completion should be disabled, 1 otherwise.
+*/
+static int complete_param( wchar_t *cmd_orig,
+ wchar_t *popt,
+ wchar_t *str,
+ array_list_t *comp_out )
+{
+ complete_entry *i;
+ complete_entry_opt *o;
+
+ array_list_t matches;
+ wchar_t *cmd, *path;
+ int use_common=1, use_files=1;
+
+ parse_cmd_string( cmd_orig, &path, &cmd );
+
+ complete_load( cmd, 1 );
+
+ al_init( &matches );
+
+
+ for( i=first_entry; i; i=i->next )
+ {
+ wchar_t *match = i->cmd_type?path:cmd;
+
+ if( ( (!wildcard_match( match, i->cmd ) ) ) )
+ continue;
+
+/* wprintf( L"Found matching command %ls\n", i->cmd ); */
+
+ use_common=1;
+ if( str[0] == L'-' )
+ {
+ /* Check if we are entering a combined option and argument
+ * (like --color=auto or -I/usr/include) */
+ for( o = i->first_option; o; o=o->next )
+ {
+ wchar_t *arg;
+ if( (arg=param_match2( o, str ))!=0 && condition_test( o->condition ))
+ {
+
+/* wprintf( L"Use option with desc %ls\n", o->desc ); */
+ use_common &= ((o->result_mode & NO_COMMON )==0);
+ use_files &= ((o->result_mode & NO_FILES )==0);
+ complete_from_args( arg, o->comp, o->desc, comp_out );
+ }
+
+ }
+ }
+ else if( popt[0] == L'-' )
+ {
+ /* Check if the previous option has any specified
+ * arguments to match against */
+ int found_old = 0;
+
+ /*
+ If we are using old style long options, check for them
+ first
+ */
+ for( o = i->first_option; o; o=o->next )
+ {
+ if( o->old_mode )
+ {
+ if( param_match_old( o, popt ) && condition_test( o->condition ))
+ {
+ found_old = 1;
+ use_common &= ((o->result_mode & NO_COMMON )==0);
+ use_files &= ((o->result_mode & NO_FILES )==0);
+ complete_from_args( str, o->comp, o->desc, comp_out );
+ }
+ }
+ }
+
+ /*
+ No old style option matched, or we are not using old
+ style options. We check if any short (or gnu style
+ options do.
+ */
+ if( !found_old )
+ {
+ for( o = i->first_option; o; o=o->next )
+ {
+ /*
+ Gnu-style options with _optional_ arguments must
+ be specified as a single token, so that it can
+ be differed from a regular argument.
+ */
+ if( !o->old_mode && wcslen(o->long_opt) && !(o->result_mode & NO_COMMON) )
+ continue;
+
+ if( param_match( o, popt ) && condition_test( o->condition ))
+ {
+ use_common &= ((o->result_mode & NO_COMMON )==0);
+ use_files &= ((o->result_mode & NO_FILES )==0);
+ complete_from_args( str, o->comp, o->desc, comp_out );
+
+ }
+ }
+ }
+ }
+
+ if( use_common )
+ {
+
+ for( o = i->first_option; o; o=o->next )
+ {
+ /*
+ If this entry is for the base command,
+ check if any of the arguments match
+ */
+
+ if( !condition_test( o->condition ))
+ continue;
+
+
+ if( (o->short_opt == L'\0' ) && (o->long_opt[0]==L'\0'))
+ {
+ use_files &= ((o->result_mode & NO_FILES )==0);
+// debug( 0, L"Running argument command %ls", o->comp );
+ complete_from_args( str, o->comp, o->desc, comp_out );
+ }
+
+ if( wcslen(str) > 0 )
+ {
+ /*
+ Check if the short style option matches
+ */
+ if( o->short_opt != L'\0' &&
+ short_ok( str, o->short_opt, i->short_opt_str ) )
+ {
+ wchar_t *next_opt =
+ malloc( sizeof(wchar_t)*(2 + wcslen(o->desc)));
+ if( !next_opt )
+ die_mem();
+
+ next_opt[0]=o->short_opt;
+ next_opt[1]=L'\0';
+ wcscat( next_opt, o->desc );
+ al_push( comp_out, next_opt );
+ }
+
+ /*
+ Check if the long style option matches
+ */
+ if( o->long_opt[0] != L'\0' )
+ {
+ string_buffer_t whole_opt;
+ sb_init( &whole_opt );
+ sb_append2( &whole_opt, o->old_mode?L"-":L"--", o->long_opt, 0 );
+
+ if( wcsncmp( str, (wchar_t *)whole_opt.buff, wcslen(str) )==0)
+ {
+ /*
+ If the option requires arguments, add
+ option with an appended '=' . If the
+ option does not accept arguments, add
+ option. If option accepts but does not
+ require arguments, add both.
+ */
+
+ if( o->old_mode || !(o->result_mode & NO_COMMON ) )
+ {
+ al_push( comp_out,
+ wcsdupcat(&((wchar_t *)whole_opt.buff)[wcslen(str)], o->desc) );
+// fwprintf( stderr, L"Add without param %ls\n", o->long_opt );
+ }
+
+ if( !o->old_mode && ( wcslen(o->comp) || (o->result_mode & NO_COMMON ) ) )
+ {
+ al_push( comp_out,
+ wcsdupcat2(&((wchar_t *)whole_opt.buff)[wcslen(str)], L"=", o->desc, 0) );
+// fwprintf( stderr, L"Add with param %ls\n", o->long_opt );
+ }
+
+// fwprintf( stderr, L"Matching long option %ls\n", o->long_opt );
+ }
+ sb_destroy( &whole_opt );
+
+ }
+ }
+ }
+ }
+ }
+ free( path );
+ return use_files;
+}
+
+/**
+ Perform file completion on the specified string
+*/
+static void complete_param_expand( wchar_t *str,
+ array_list_t *comp_out,
+ int do_file )
+{
+ wchar_t *comp_str;
+ if( (wcsncmp( str, L"--", 2 )) == 0 && (comp_str = wcschr(str, L'=' ) ) )
+ {
+ comp_str++;
+ }
+ else
+ comp_str = str;
+
+// fwprintf( stderr, L"expand_string( \"%ls\", [list], ACCEPT_INCOMPLETE | %ls )\n", comp_str, do_file?L"0":L"EXPAND_SKIP_WILDCARDS" );
+
+ expand_string( wcsdup(comp_str), comp_out, ACCEPT_INCOMPLETE | (do_file?0:EXPAND_SKIP_WILDCARDS) );
+}
+
+/**
+ Complete the specified string as an environment variable
+*/
+static int complete_variable( const wchar_t *var,
+ array_list_t *comp )
+{
+ int i;
+ int varlen = wcslen( var );
+ int res = 0;
+
+ array_list_t names;
+ al_init( &names );
+ env_get_names( &names, 0 );
+/* wprintf( L"Search string %ls\n", var );*/
+
+/* wprintf( L"Got %d variables\n", al_get_count( &names ) );*/
+ for( i=0; i<al_get_count( &names ); i++ )
+ {
+ wchar_t *name = (wchar_t *)al_get( &names, i );
+ int namelen = wcslen( name );
+ if( varlen > namelen )
+ continue;
+
+/* wprintf( L"Try %ls\n", name );*/
+
+ if( wcsncmp( var, name, varlen) == 0 )
+ {
+ wchar_t *value = expand_escape_variable( env_get( name ));
+ wchar_t *blarg;
+ /*
+ What should the description of the variable be?
+
+ If the variable doesn't have a value, or of the value is
+ really long, we just describe it as 'Variable', but if
+ the value is 1..16 characters long, we describe it as
+ 'Variable: VALUE'.
+ */
+/* if( wcslen(value) < 1 || wcslen(value) > 16 )
+ {
+ blarg = wcsdupcat( &name[varlen], COMPLETE_VAR_DESC );
+ }
+ else
+ {*/
+ blarg = wcsdupcat2( &name[varlen], COMPLETE_VAR_DESC_VAL, value, 0 );
+// }
+
+ if( blarg )
+ {
+ res =1;
+ al_push( comp, blarg );
+ }
+ free( value );
+
+ }
+ }
+
+ al_destroy( &names );
+ return res;
+}
+
+/**
+ Search the specified string for the \$ sign, try to complete as an environment variable
+*/
+static int try_complete_variable( const wchar_t *cmd,
+ array_list_t *comp )
+{
+ int len = wcslen( cmd );
+ int i;
+
+ for( i=len-1; i>=0; i-- )
+ {
+ if( cmd[i] == L'$' )
+ {
+/* wprintf( L"Var prefix \'%ls\'\n", &cmd[i+1] );*/
+ return complete_variable( &cmd[i+1], comp );
+ }
+ if( !isalnum(cmd[i]) && cmd[i]!=L'_' )
+ {
+ return 0;
+ }
+
+ }
+ return 0;
+
+
+}
+
+/**
+ Try to complete the specified string as a username. This is used by ~USER type expansion.
+*/
+
+static int try_complete_user( const wchar_t *cmd,
+ array_list_t *comp )
+{
+ const wchar_t *first_char=0;
+ const wchar_t *p;
+ int mode = 0;
+ int res = 0;
+
+
+ for( p=cmd; *p; p++ )
+ {
+ switch( mode )
+ {
+ /*Between parameters*/
+ case 0:
+ switch( *p )
+ {
+ case L'\"':
+ mode=2;
+ p++;
+ first_char = p;
+ break;
+ case L' ':
+ case L'\t':
+ case L'\n':
+ case L'\r':
+ break;
+ default:
+ mode=1;
+ first_char = p;
+ }
+ break;
+ /*Inside non-quoted parameter*/
+ case 1:
+ switch( *p )
+ {
+ case L' ':
+ case L'\t':
+ case L'\n':
+ case L'\r':
+ mode = 0;
+ break;
+ }
+ break;
+ case 2:
+ switch( *p )
+ {
+ case L'\"':
+ if( *(p-1) != L'\\' )
+ mode =0;
+ break;
+ }
+ break;
+ }
+ }
+
+ if( mode != 0 )
+ {
+ if( *first_char ==L'~' )
+ {
+ const wchar_t *user_name = first_char+1;
+ wchar_t *name_end = wcschr( user_name, L'~' );
+ if( name_end == 0 )
+ {
+ struct passwd *pw;
+ int name_len = wcslen( user_name );
+
+/* wprintf( L"Complete name \'%ls\'\n", user_name );*/
+
+ setpwent();
+
+ while((pw=getpwent()) != 0)
+ {
+/* wprintf( L"Try %ls\n", pw->pw_name );*/
+ wchar_t *pw_name = str2wcs( pw->pw_name );
+ if( pw_name )
+ {
+ if( wcsncmp( user_name, pw_name, name_len )==0 )
+ {
+ wchar_t *blarg = wcsdupcat2( &pw_name[name_len],
+ L"/",
+ COMPLETE_USER_DESC,
+ 0 );
+ if( blarg != 0 )
+ {
+ al_push( comp, blarg );
+ res=1;
+ }
+ }
+ free( pw_name );
+ }
+ }
+
+ endpwent();
+
+ }
+
+ }
+
+
+ }
+
+ return res;
+}
+
+void complete( const wchar_t *cmd,
+ array_list_t *comp )
+{
+ wchar_t *begin, *end, *prev_begin, *prev_end, *buff;
+ tokenizer tok;
+ wchar_t *current_token=0, *current_command=0, *prev_token=0;
+
+ int on_command=0;
+ int pos;
+
+ int old_error_max = error_max;
+ int done=0;
+
+ error_max=0;
+
+ /**
+ If we are completing a variable name or a tilde expantion user
+ name, we do that and return. No need for any other competions.
+ */
+
+ if( try_complete_variable( cmd, comp ))
+ {
+ done=1;
+
+ }
+ else if( try_complete_user( cmd, comp ))
+ {
+ done=1;
+ }
+
+
+ /*
+ Set on_command to true if cursor is over a command, and set the
+ name of the current command, and various other parsing to find
+ out what we should complete, and how it should be completed.
+ */
+
+ if( !done )
+ {
+ reader_current_subshell_extent( &begin, &end );
+
+ if( !begin )
+ done=1;
+ }
+
+ if( !done )
+ {
+
+ pos = reader_get_cursor_pos()-(begin-reader_get_buffer());
+
+ buff = wcsndup( begin, end-begin );
+
+ if( !buff )
+ done=1;
+ }
+
+
+ if( !done )
+ {
+ int had_cmd=0;
+ int end_loop=0;
+
+ tok_init( &tok, buff, TOK_ACCEPT_UNFINISHED );
+
+ free( buff );
+
+ while( !end_loop )
+ {
+ switch( tok_last_type( &tok ) )
+ {
+ case TOK_STRING:
+ if( !had_cmd )
+ {
+ if( parser_is_subcommand( tok_last( &tok ) ) )
+ break;
+
+ current_command = wcsdup( tok_last( &tok ) );
+
+ on_command = (pos <= tok_get_pos( &tok) + wcslen( tok_last( &tok ) ) );
+ had_cmd=1;
+ }
+ break;
+
+ case TOK_END:
+ case TOK_PIPE:
+ case TOK_BACKGROUND:
+ had_cmd=0;
+ break;
+
+
+ case TOK_ERROR:
+ end_loop=1;
+ break;
+
+ }
+ if( tok_get_pos( &tok ) >= pos )
+ end_loop=1;
+
+ tok_next( &tok );
+
+ }
+
+ tok_destroy( &tok );
+
+
+ /*
+ Get the string to complete
+ */
+
+ reader_current_token_extent( &begin, &end, &prev_begin, &prev_end );
+
+ current_token = wcsndup( begin, reader_get_cursor_pos()-(begin-reader_get_buffer()) );
+ prev_token = wcsndup( prev_begin, prev_end - prev_begin );
+
+// fwprintf( stderr, L"on_command: %d, %ls %ls\n", on_command, current_compmand, current_token );
+
+ if( current_token && current_command && prev_token )
+ {
+
+ if( on_command )
+ {
+ /* Complete command filename */
+ complete_cmd( current_token,
+ comp );
+ }
+ else
+ {
+ /*
+ Complete parameter. Parameter expansion should be
+ performed against both the globbed and the unglobbed
+ version of the string, so we create a list containing
+ all possible versions of the string that is to be
+ expanded. This is potentially very slow.
+ */
+
+ int cmd_ok = 1;
+ int do_file = 1;
+
+ wchar_t *end_str = current_token;
+
+ /*
+ If the command is an function, we use the
+ completions of the first command in the function
+ and hope for the best...
+ */
+ if( function_exists(current_command ) )
+ {
+ tokenizer tok2;
+ tok_init( &tok2, function_get_definition( current_command), 0 );
+ wchar_t *new_cmd=0;
+
+ switch( tok_last_type( &tok2 ) )
+ {
+ case TOK_STRING:
+ new_cmd = expand_one( wcsdup(tok_last( &tok2 )),
+ EXPAND_SKIP_SUBSHELL | EXPAND_SKIP_VARIABLES );
+ break;
+ default:
+ cmd_ok = 0;
+ break;
+ }
+
+ tok_destroy( &tok2 );
+
+ if( cmd_ok )
+ {
+ do_file &= complete_param( new_cmd,
+ prev_token,
+ end_str,
+ comp );
+ }
+ if( new_cmd != 0 )
+ free(new_cmd);
+ }
+ // fwprintf( stderr, L"complete_param with end_str %ls\n", end_str );
+ do_file &= complete_param( current_command, prev_token, end_str, comp );
+
+ complete_param_expand( current_token, comp, do_file );
+ }
+ }
+
+ free( current_token );
+ free( current_command );
+ free( prev_token );
+
+ }
+
+ error_max=old_error_max;
+ condition_cache_clear();
+
+}
+
+static void append_switch( string_buffer_t *out,
+ const wchar_t *opt,
+ const wchar_t *argument )
+{
+ wchar_t *esc;
+
+ if( !argument || argument==L"" )
+ return;
+
+ esc = expand_escape( wcsdup(argument), 1 );
+ sb_printf( out, L" --%ls %ls", opt, esc );
+ free(esc);
+}
+
+void complete_print( string_buffer_t *out )
+{
+ complete_entry *e;
+
+ for( e = first_entry; e; e=e->next )
+ {
+ complete_entry_opt *o;
+ for( o= e->first_option; o; o=o->next )
+ {
+ wchar_t *modestr[] =
+ {
+ L"",
+ L" --no-files",
+ L" --require-parameter",
+ L" --exclusive"
+ }
+ ;
+
+ sb_printf( out,
+ L"complete%ls",
+ modestr[o->result_mode] );
+
+ append_switch( out,
+ e->cmd_type?L"path":L"command",
+ e->cmd );
+
+
+ if( o->short_opt != 0 )
+ {
+ sb_printf( out,
+ L" --short-option '%lc'",
+ o->short_opt );
+ }
+
+
+ append_switch( out,
+ o->old_mode?L"old-option":L"long-option",
+ o->long_opt );
+
+ append_switch( out,
+ L"description",
+ o->desc );
+
+ append_switch( out,
+ L"arguments",
+ o->comp );
+
+ append_switch( out,
+ L"condition",
+ o->condition );
+
+ sb_printf( out, L"\n" );
+ }
+ }
+}
+
diff --git a/complete.h b/complete.h
new file mode 100644
index 00000000..9a8f33ce
--- /dev/null
+++ b/complete.h
@@ -0,0 +1,155 @@
+/** \file complete.h
+ Prototypes for functions related to tab-completion.
+
+ These functions are used for storing and retrieving tab-completion data, as well as for performing tab-completion.
+*/
+
+/** Use all completions */
+#define SHARED 0
+/** Do not use file completion */
+#define NO_FILES 1
+/** Require a parameter after completion */
+#define NO_COMMON 2
+/** Only use the argument list specifies with completion after option. This is the same as (NO_FILES & NO_COMMON) */
+#define EXCLUSIVE 3
+
+/** Command is a path */
+#define PATH 1
+/** Command is not a path */
+#define COMMAND 0
+
+/** Separateor between completion and description*/
+#define COMPLETE_SEP L'\004'
+/** Separateor between completion and description*/
+#define COMPLETE_SEP_STR L"\004"
+
+/**
+ Character that separates the completion and description on programmable completions
+*/
+#define PROG_COMPLETE_SEP L'\t'
+
+/**
+ Initializes various structures used for tab-completion.
+*/
+void complete_init();
+
+/**
+ Destroys various structures used for tab-completion and free()s the memory used by them.
+*/
+void complete_destroy();
+
+/**
+
+ Add a completion.
+
+ Values are copied and should be freed by the caller.
+
+ Examples:
+
+ The command 'gcc -o' requires that a file follows it, so the
+ NO_COMMON option is suitable. This can be done using the following
+ line:
+
+ complete -c gcc -s o -r
+
+ The command 'grep -d' required that one of the strings 'read',
+ 'skip' or 'recurse' is used. As such, it is suitable to specify that
+ a completion requires one of them. This can be done using the
+ following line:
+
+ complete -c grep -s d -x -a "read skip recurse"
+
+
+ \param cmd Command to complete.
+ \param cmd_type If cmd_type is PATH, cmd will be interpreted as the absolute
+ path of the program (optionally containing wildcards), otherwise it
+ will be interpreted as the command name.
+ \param short_opt The single character name of an option. (-a is a short option, --all and -funroll are long options)
+ \param long_opt The multi character name of an option. (-a is a short option, --all and -funroll are long options)
+ \param long_mode Whether to use old style, single dash long options.
+ \param result_mode Whether to search further completions when this
+ completion has been succesfully matched. If result_mode is SHARED,
+ any other completions may also be used. If result_mode is NO_FILES,
+ file completion should not be used, but other completions may be
+ used. If result_mode is NO_COMMON, on option may follow it - only a
+ parameter. If result_mode is EXCLUSIVE, no option may follow it, and
+ file completion is not performed.
+ \param comp A space separated list of completions which may contain subshells.
+ \param desc A description of the completion.
+ \param authorative Whether there list of completions for this command is complete. If true, any options not matching one of the provided options will be flagged as an error by syntax highlighting.
+ \param condition a command to be run to check it this completion should be used. If \c condition is empty, the completion is always used.
+
+*/
+void complete_add( const wchar_t *cmd,
+ int cmd_type,
+ wchar_t short_opt,
+ const wchar_t *long_opt,
+ int long_mode,
+ int result_mode,
+ int authorative,
+ const wchar_t *condition,
+ const wchar_t *comp,
+ const wchar_t *desc );
+
+/**
+ Remove a previously defined completion
+*/
+void complete_remove( const wchar_t *cmd,
+ int cmd_type,
+ wchar_t short_opt,
+ const wchar_t *long_opt );
+
+/**
+ Find all completions of the command cmd, insert them into out. The
+ caller must free the variables returned in out. The results are
+ returned in the array_list_t 'out', in the format of wide character
+ strings, with each element consisting of a suggested completion and
+ a description of what kind of object this completion represents,
+ separated by a separator of type COMPLETE_SEP.
+
+ Values returned by this function should be freed by the caller.
+*/
+void complete( const wchar_t *cmd, array_list_t *out );
+
+/**
+ Print a list of all current completions into the string_buffer_t.
+
+ \param out The string_buffer_t to write completions to
+*/
+void complete_print( string_buffer_t *out );
+
+/**
+ Obtain a description string for the file specified by the filename.
+
+ The returned value is a string constant and should not be freed.
+
+ \param filename The file for which to find a description string
+*/
+const wchar_t *complete_get_desc( const wchar_t *filename );
+
+/**
+ Tests if the specified option is defined for the specified command
+*/
+int complete_is_valid_option( const wchar_t *str,
+ const wchar_t *opt,
+ array_list_t *errors );
+
+/**
+ Tests if the specified argument is valid for the specified option
+ and command
+*/
+int complete_is_valid_argument( const wchar_t *str,
+ const wchar_t *opt,
+ const wchar_t *arg );
+
+
+/**
+ Load command-specific completions for the specified command. This
+ is done automatically whenever completing any given command, so
+ there is no need to call this except in the case of completions
+ with internal dependencies.
+
+ \param cmd the command for which to load command-specific completions
+ \param reload should the commands completions be reloaded, even if they where previously loaded. (This is set to true on actual completions, so that changed completion are updated in running shells)
+*/
+void complete_load( wchar_t *cmd, int reload );
diff --git a/config.guess b/config.guess
new file mode 100755
index 00000000..2fc3acce
--- /dev/null
+++ b/config.guess
@@ -0,0 +1,1411 @@
+#! /bin/sh
+# Attempt to guess a canonical system name.
+# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+# 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
+
+timestamp='2003-06-17'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Originally written by Per Bothner <per@bothner.com>.
+# Please send patches to <config-patches@gnu.org>. Submit a context
+# diff and a properly formatted ChangeLog entry.
+#
+# This script attempts to guess a canonical system name similar to
+# config.sub. If it succeeds, it prints the system name on stdout, and
+# exits with 0. Otherwise, it exits with 1.
+#
+# The plan is that this can be called by configure scripts if you
+# don't specify an explicit build system type.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION]
+
+Output the configuration name of the system \`$me' is run on.
+
+Operation modes:
+ -h, --help print this help, then exit
+ -t, --time-stamp print date of last modification, then exit
+ -v, --version print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.guess ($timestamp)
+
+Originally written by Per Bothner.
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001
+Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+ case $1 in
+ --time-stamp | --time* | -t )
+ echo "$timestamp" ; exit 0 ;;
+ --version | -v )
+ echo "$version" ; exit 0 ;;
+ --help | --h* | -h )
+ echo "$usage"; exit 0 ;;
+ -- ) # Stop option processing
+ shift; break ;;
+ - ) # Use stdin as input.
+ break ;;
+ -* )
+ echo "$me: invalid option $1$help" >&2
+ exit 1 ;;
+ * )
+ break ;;
+ esac
+done
+
+if test $# != 0; then
+ echo "$me: too many arguments$help" >&2
+ exit 1
+fi
+
+trap 'exit 1' 1 2 15
+
+# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
+# compiler to aid in system detection is discouraged as it requires
+# temporary files to be created and, as you can see below, it is a
+# headache to deal with in a portable fashion.
+
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
+
+# Portable tmp directory creation inspired by the Autoconf team.
+
+set_cc_for_build='
+trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ;
+trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ;
+: ${TMPDIR=/tmp} ;
+ { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+ { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } ||
+ { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+ { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ;
+dummy=$tmp/dummy ;
+tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ;
+case $CC_FOR_BUILD,$HOST_CC,$CC in
+ ,,) echo "int x;" > $dummy.c ;
+ for c in cc gcc c89 c99 ; do
+ if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then
+ CC_FOR_BUILD="$c"; break ;
+ fi ;
+ done ;
+ if test x"$CC_FOR_BUILD" = x ; then
+ CC_FOR_BUILD=no_compiler_found ;
+ fi
+ ;;
+ ,,*) CC_FOR_BUILD=$CC ;;
+ ,*,*) CC_FOR_BUILD=$HOST_CC ;;
+esac ;'
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# (ghazi@noc.rutgers.edu 1994-08-24)
+if (test -f /.attbin/uname) >/dev/null 2>&1 ; then
+ PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+## for Red Hat Linux
+if test -f /etc/redhat-release ; then
+ VENDOR=redhat ;
+else
+ VENDOR= ;
+fi
+
+# Note: order is significant - the case branches are not exclusive.
+
+case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
+ *:NetBSD:*:*)
+ # NetBSD (nbsd) targets should (where applicable) match one or
+ # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*,
+ # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently
+ # switched to ELF, *-*-netbsd* would select the old
+ # object file format. This provides both forward
+ # compatibility and a consistent mechanism for selecting the
+ # object file format.
+ #
+ # Note: NetBSD doesn't particularly care about the vendor
+ # portion of the name. We always set it to "unknown".
+ sysctl="sysctl -n hw.machine_arch"
+ UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \
+ /usr/sbin/$sysctl 2>/dev/null || echo unknown)`
+ case "${UNAME_MACHINE_ARCH}" in
+ armeb) machine=armeb-unknown ;;
+ arm*) machine=arm-unknown ;;
+ sh3el) machine=shl-unknown ;;
+ sh3eb) machine=sh-unknown ;;
+ *) machine=${UNAME_MACHINE_ARCH}-unknown ;;
+ esac
+ # The Operating System including object format, if it has switched
+ # to ELF recently, or will in the future.
+ case "${UNAME_MACHINE_ARCH}" in
+ arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+ eval $set_cc_for_build
+ if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
+ | grep __ELF__ >/dev/null
+ then
+ # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
+ # Return netbsd for either. FIX?
+ os=netbsd
+ else
+ os=netbsdelf
+ fi
+ ;;
+ *)
+ os=netbsd
+ ;;
+ esac
+ # The OS release
+ # Debian GNU/NetBSD machines have a different userland, and
+ # thus, need a distinct triplet. However, they do not need
+ # kernel version information, so it can be replaced with a
+ # suitable tag, in the style of linux-gnu.
+ case "${UNAME_VERSION}" in
+ Debian*)
+ release='-gnu'
+ ;;
+ *)
+ release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+ ;;
+ esac
+ # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
+ # contains redundant information, the shorter form:
+ # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
+ echo "${machine}-${os}${release}"
+ exit 0 ;;
+ amiga:OpenBSD:*:*)
+ echo m68k-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ arc:OpenBSD:*:*)
+ echo mipsel-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ hp300:OpenBSD:*:*)
+ echo m68k-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ mac68k:OpenBSD:*:*)
+ echo m68k-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ macppc:OpenBSD:*:*)
+ echo powerpc-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ mvme68k:OpenBSD:*:*)
+ echo m68k-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ mvme88k:OpenBSD:*:*)
+ echo m88k-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ mvmeppc:OpenBSD:*:*)
+ echo powerpc-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ pmax:OpenBSD:*:*)
+ echo mipsel-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ sgi:OpenBSD:*:*)
+ echo mipseb-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ sun3:OpenBSD:*:*)
+ echo m68k-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ wgrisc:OpenBSD:*:*)
+ echo mipsel-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ *:OpenBSD:*:*)
+ echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE}
+ exit 0 ;;
+ alpha:OSF1:*:*)
+ if test $UNAME_RELEASE = "V4.0"; then
+ UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+ fi
+ # According to Compaq, /usr/sbin/psrinfo has been available on
+ # OSF/1 and Tru64 systems produced since 1995. I hope that
+ # covers most systems running today. This code pipes the CPU
+ # types through head -n 1, so we only detect the type of CPU 0.
+ ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1`
+ case "$ALPHA_CPU_TYPE" in
+ "EV4 (21064)")
+ UNAME_MACHINE="alpha" ;;
+ "EV4.5 (21064)")
+ UNAME_MACHINE="alpha" ;;
+ "LCA4 (21066/21068)")
+ UNAME_MACHINE="alpha" ;;
+ "EV5 (21164)")
+ UNAME_MACHINE="alphaev5" ;;
+ "EV5.6 (21164A)")
+ UNAME_MACHINE="alphaev56" ;;
+ "EV5.6 (21164PC)")
+ UNAME_MACHINE="alphapca56" ;;
+ "EV5.7 (21164PC)")
+ UNAME_MACHINE="alphapca57" ;;
+ "EV6 (21264)")
+ UNAME_MACHINE="alphaev6" ;;
+ "EV6.7 (21264A)")
+ UNAME_MACHINE="alphaev67" ;;
+ "EV6.8CB (21264C)")
+ UNAME_MACHINE="alphaev68" ;;
+ "EV6.8AL (21264B)")
+ UNAME_MACHINE="alphaev68" ;;
+ "EV6.8CX (21264D)")
+ UNAME_MACHINE="alphaev68" ;;
+ "EV6.9A (21264/EV69A)")
+ UNAME_MACHINE="alphaev69" ;;
+ "EV7 (21364)")
+ UNAME_MACHINE="alphaev7" ;;
+ "EV7.9 (21364A)")
+ UNAME_MACHINE="alphaev79" ;;
+ esac
+ # A Vn.n version is a released version.
+ # A Tn.n version is a released field test version.
+ # A Xn.n version is an unreleased experimental baselevel.
+ # 1.2 uses "1.2" for uname -r.
+ echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+ exit 0 ;;
+ Alpha*:OpenVMS:*:*)
+ echo alpha-hp-vms
+ exit 0 ;;
+ Alpha\ *:Windows_NT*:*)
+ # How do we know it's Interix rather than the generic POSIX subsystem?
+ # Should we change UNAME_MACHINE based on the output of uname instead
+ # of the specific Alpha model?
+ echo alpha-pc-interix
+ exit 0 ;;
+ 21064:Windows_NT:50:3)
+ echo alpha-dec-winnt3.5
+ exit 0 ;;
+ Amiga*:UNIX_System_V:4.0:*)
+ echo m68k-unknown-sysv4
+ exit 0;;
+ *:[Aa]miga[Oo][Ss]:*:*)
+ echo ${UNAME_MACHINE}-unknown-amigaos
+ exit 0 ;;
+ *:[Mm]orph[Oo][Ss]:*:*)
+ echo ${UNAME_MACHINE}-unknown-morphos
+ exit 0 ;;
+ *:OS/390:*:*)
+ echo i370-ibm-openedition
+ exit 0 ;;
+ arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+ echo arm-acorn-riscix${UNAME_RELEASE}
+ exit 0;;
+ SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
+ echo hppa1.1-hitachi-hiuxmpp
+ exit 0;;
+ Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+ # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
+ if test "`(/bin/universe) 2>/dev/null`" = att ; then
+ echo pyramid-pyramid-sysv3
+ else
+ echo pyramid-pyramid-bsd
+ fi
+ exit 0 ;;
+ NILE*:*:*:dcosx)
+ echo pyramid-pyramid-svr4
+ exit 0 ;;
+ DRS?6000:unix:4.0:6*)
+ echo sparc-icl-nx6
+ exit 0 ;;
+ DRS?6000:UNIX_SV:4.2*:7*)
+ case `/usr/bin/uname -p` in
+ sparc) echo sparc-icl-nx7 && exit 0 ;;
+ esac ;;
+ sun4H:SunOS:5.*:*)
+ echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit 0 ;;
+ sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+ echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit 0 ;;
+ i86pc:SunOS:5.*:*)
+ echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit 0 ;;
+ sun4*:SunOS:6*:*)
+ # According to config.sub, this is the proper way to canonicalize
+ # SunOS6. Hard to guess exactly what SunOS6 will be like, but
+ # it's likely to be more like Solaris than SunOS4.
+ echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit 0 ;;
+ sun4*:SunOS:*:*)
+ case "`/usr/bin/arch -k`" in
+ Series*|S4*)
+ UNAME_RELEASE=`uname -v`
+ ;;
+ esac
+ # Japanese Language versions have a version number like `4.1.3-JL'.
+ echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'`
+ exit 0 ;;
+ sun3*:SunOS:*:*)
+ echo m68k-sun-sunos${UNAME_RELEASE}
+ exit 0 ;;
+ sun*:*:4.2BSD:*)
+ UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+ test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3
+ case "`/bin/arch`" in
+ sun3)
+ echo m68k-sun-sunos${UNAME_RELEASE}
+ ;;
+ sun4)
+ echo sparc-sun-sunos${UNAME_RELEASE}
+ ;;
+ esac
+ exit 0 ;;
+ aushp:SunOS:*:*)
+ echo sparc-auspex-sunos${UNAME_RELEASE}
+ exit 0 ;;
+ # The situation for MiNT is a little confusing. The machine name
+ # can be virtually everything (everything which is not
+ # "atarist" or "atariste" at least should have a processor
+ # > m68000). The system name ranges from "MiNT" over "FreeMiNT"
+ # to the lowercase version "mint" (or "freemint"). Finally
+ # the system name "TOS" denotes a system which is actually not
+ # MiNT. But MiNT is downward compatible to TOS, so this should
+ # be no problem.
+ atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+ echo m68k-atari-mint${UNAME_RELEASE}
+ exit 0 ;;
+ atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+ echo m68k-atari-mint${UNAME_RELEASE}
+ exit 0 ;;
+ *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+ echo m68k-atari-mint${UNAME_RELEASE}
+ exit 0 ;;
+ milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+ echo m68k-milan-mint${UNAME_RELEASE}
+ exit 0 ;;
+ hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+ echo m68k-hades-mint${UNAME_RELEASE}
+ exit 0 ;;
+ *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+ echo m68k-unknown-mint${UNAME_RELEASE}
+ exit 0 ;;
+ powerpc:machten:*:*)
+ echo powerpc-apple-machten${UNAME_RELEASE}
+ exit 0 ;;
+ RISC*:Mach:*:*)
+ echo mips-dec-mach_bsd4.3
+ exit 0 ;;
+ RISC*:ULTRIX:*:*)
+ echo mips-dec-ultrix${UNAME_RELEASE}
+ exit 0 ;;
+ VAX*:ULTRIX*:*:*)
+ echo vax-dec-ultrix${UNAME_RELEASE}
+ exit 0 ;;
+ 2020:CLIX:*:* | 2430:CLIX:*:*)
+ echo clipper-intergraph-clix${UNAME_RELEASE}
+ exit 0 ;;
+ mips:*:*:UMIPS | mips:*:*:RISCos)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+#ifdef __cplusplus
+#include <stdio.h> /* for printf() prototype */
+ int main (int argc, char *argv[]) {
+#else
+ int main (argc, argv) int argc; char *argv[]; {
+#endif
+ #if defined (host_mips) && defined (MIPSEB)
+ #if defined (SYSTYPE_SYSV)
+ printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0);
+ #endif
+ #if defined (SYSTYPE_SVR4)
+ printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0);
+ #endif
+ #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+ printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0);
+ #endif
+ #endif
+ exit (-1);
+ }
+EOF
+ $CC_FOR_BUILD -o $dummy $dummy.c \
+ && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \
+ && exit 0
+ echo mips-mips-riscos${UNAME_RELEASE}
+ exit 0 ;;
+ Motorola:PowerMAX_OS:*:*)
+ echo powerpc-motorola-powermax
+ exit 0 ;;
+ Motorola:*:4.3:PL8-*)
+ echo powerpc-harris-powermax
+ exit 0 ;;
+ Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
+ echo powerpc-harris-powermax
+ exit 0 ;;
+ Night_Hawk:Power_UNIX:*:*)
+ echo powerpc-harris-powerunix
+ exit 0 ;;
+ m88k:CX/UX:7*:*)
+ echo m88k-harris-cxux7
+ exit 0 ;;
+ m88k:*:4*:R4*)
+ echo m88k-motorola-sysv4
+ exit 0 ;;
+ m88k:*:3*:R3*)
+ echo m88k-motorola-sysv3
+ exit 0 ;;
+ AViiON:dgux:*:*)
+ # DG/UX returns AViiON for all architectures
+ UNAME_PROCESSOR=`/usr/bin/uname -p`
+ if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ]
+ then
+ if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \
+ [ ${TARGET_BINARY_INTERFACE}x = x ]
+ then
+ echo m88k-dg-dgux${UNAME_RELEASE}
+ else
+ echo m88k-dg-dguxbcs${UNAME_RELEASE}
+ fi
+ else
+ echo i586-dg-dgux${UNAME_RELEASE}
+ fi
+ exit 0 ;;
+ M88*:DolphinOS:*:*) # DolphinOS (SVR3)
+ echo m88k-dolphin-sysv3
+ exit 0 ;;
+ M88*:*:R3*:*)
+ # Delta 88k system running SVR3
+ echo m88k-motorola-sysv3
+ exit 0 ;;
+ XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+ echo m88k-tektronix-sysv3
+ exit 0 ;;
+ Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+ echo m68k-tektronix-bsd
+ exit 0 ;;
+ *:IRIX*:*:*)
+ echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'`
+ exit 0 ;;
+ ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+ echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id
+ exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX '
+ i*86:AIX:*:*)
+ echo i386-ibm-aix
+ exit 0 ;;
+ ia64:AIX:*:*)
+ if [ -x /usr/bin/oslevel ] ; then
+ IBM_REV=`/usr/bin/oslevel`
+ else
+ IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+ fi
+ echo ${UNAME_MACHINE}-ibm-aix${IBM_REV}
+ exit 0 ;;
+ *:AIX:2:3)
+ if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #include <sys/systemcfg.h>
+
+ main()
+ {
+ if (!__power_pc())
+ exit(1);
+ puts("powerpc-ibm-aix3.2.5");
+ exit(0);
+ }
+EOF
+ $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0
+ echo rs6000-ibm-aix3.2.5
+ elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+ echo rs6000-ibm-aix3.2.4
+ else
+ echo rs6000-ibm-aix3.2
+ fi
+ exit 0 ;;
+ *:AIX:*:[45])
+ IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
+ if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then
+ IBM_ARCH=rs6000
+ else
+ IBM_ARCH=powerpc
+ fi
+ if [ -x /usr/bin/oslevel ] ; then
+ IBM_REV=`/usr/bin/oslevel`
+ else
+ IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+ fi
+ echo ${IBM_ARCH}-ibm-aix${IBM_REV}
+ exit 0 ;;
+ *:AIX:*:*)
+ echo rs6000-ibm-aix
+ exit 0 ;;
+ ibmrt:4.4BSD:*|romp-ibm:BSD:*)
+ echo romp-ibm-bsd4.4
+ exit 0 ;;
+ ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and
+ echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to
+ exit 0 ;; # report: romp-ibm BSD 4.3
+ *:BOSX:*:*)
+ echo rs6000-bull-bosx
+ exit 0 ;;
+ DPX/2?00:B.O.S.:*:*)
+ echo m68k-bull-sysv3
+ exit 0 ;;
+ 9000/[34]??:4.3bsd:1.*:*)
+ echo m68k-hp-bsd
+ exit 0 ;;
+ hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+ echo m68k-hp-bsd4.4
+ exit 0 ;;
+ 9000/[34678]??:HP-UX:*:*)
+ HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+ case "${UNAME_MACHINE}" in
+ 9000/31? ) HP_ARCH=m68000 ;;
+ 9000/[34]?? ) HP_ARCH=m68k ;;
+ 9000/[678][0-9][0-9])
+ if [ -x /usr/bin/getconf ]; then
+ sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
+ sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
+ case "${sc_cpu_version}" in
+ 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0
+ 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1
+ 532) # CPU_PA_RISC2_0
+ case "${sc_kernel_bits}" in
+ 32) HP_ARCH="hppa2.0n" ;;
+ 64) HP_ARCH="hppa2.0w" ;;
+ '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20
+ esac ;;
+ esac
+ fi
+ if [ "${HP_ARCH}" = "" ]; then
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+
+ #define _HPUX_SOURCE
+ #include <stdlib.h>
+ #include <unistd.h>
+
+ int main ()
+ {
+ #if defined(_SC_KERNEL_BITS)
+ long bits = sysconf(_SC_KERNEL_BITS);
+ #endif
+ long cpu = sysconf (_SC_CPU_VERSION);
+
+ switch (cpu)
+ {
+ case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+ case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+ case CPU_PA_RISC2_0:
+ #if defined(_SC_KERNEL_BITS)
+ switch (bits)
+ {
+ case 64: puts ("hppa2.0w"); break;
+ case 32: puts ("hppa2.0n"); break;
+ default: puts ("hppa2.0"); break;
+ } break;
+ #else /* !defined(_SC_KERNEL_BITS) */
+ puts ("hppa2.0"); break;
+ #endif
+ default: puts ("hppa1.0"); break;
+ }
+ exit (0);
+ }
+EOF
+ (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
+ test -z "$HP_ARCH" && HP_ARCH=hppa
+ fi ;;
+ esac
+ if [ ${HP_ARCH} = "hppa2.0w" ]
+ then
+ # avoid double evaluation of $set_cc_for_build
+ test -n "$CC_FOR_BUILD" || eval $set_cc_for_build
+ if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E -) | grep __LP64__ >/dev/null
+ then
+ HP_ARCH="hppa2.0w"
+ else
+ HP_ARCH="hppa64"
+ fi
+ fi
+ echo ${HP_ARCH}-hp-hpux${HPUX_REV}
+ exit 0 ;;
+ ia64:HP-UX:*:*)
+ HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+ echo ia64-hp-hpux${HPUX_REV}
+ exit 0 ;;
+ 3050*:HI-UX:*:*)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #include <unistd.h>
+ int
+ main ()
+ {
+ long cpu = sysconf (_SC_CPU_VERSION);
+ /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+ true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct
+ results, however. */
+ if (CPU_IS_PA_RISC (cpu))
+ {
+ switch (cpu)
+ {
+ case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+ case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+ case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+ default: puts ("hppa-hitachi-hiuxwe2"); break;
+ }
+ }
+ else if (CPU_IS_HP_MC68K (cpu))
+ puts ("m68k-hitachi-hiuxwe2");
+ else puts ("unknown-hitachi-hiuxwe2");
+ exit (0);
+ }
+EOF
+ $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0
+ echo unknown-hitachi-hiuxwe2
+ exit 0 ;;
+ 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* )
+ echo hppa1.1-hp-bsd
+ exit 0 ;;
+ 9000/8??:4.3bsd:*:*)
+ echo hppa1.0-hp-bsd
+ exit 0 ;;
+ *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
+ echo hppa1.0-hp-mpeix
+ exit 0 ;;
+ hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* )
+ echo hppa1.1-hp-osf
+ exit 0 ;;
+ hp8??:OSF1:*:*)
+ echo hppa1.0-hp-osf
+ exit 0 ;;
+ i*86:OSF1:*:*)
+ if [ -x /usr/sbin/sysversion ] ; then
+ echo ${UNAME_MACHINE}-unknown-osf1mk
+ else
+ echo ${UNAME_MACHINE}-unknown-osf1
+ fi
+ exit 0 ;;
+ parisc*:Lites*:*:*)
+ echo hppa1.1-hp-lites
+ exit 0 ;;
+ C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+ echo c1-convex-bsd
+ exit 0 ;;
+ C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+ if getsysinfo -f scalar_acc
+ then echo c32-convex-bsd
+ else echo c2-convex-bsd
+ fi
+ exit 0 ;;
+ C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+ echo c34-convex-bsd
+ exit 0 ;;
+ C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+ echo c38-convex-bsd
+ exit 0 ;;
+ C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+ echo c4-convex-bsd
+ exit 0 ;;
+ CRAY*Y-MP:*:*:*)
+ echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit 0 ;;
+ CRAY*[A-Z]90:*:*:*)
+ echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \
+ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
+ -e 's/\.[^.]*$/.X/'
+ exit 0 ;;
+ CRAY*TS:*:*:*)
+ echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit 0 ;;
+ CRAY*T3E:*:*:*)
+ echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit 0 ;;
+ CRAY*SV1:*:*:*)
+ echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit 0 ;;
+ *:UNICOS/mp:*:*)
+ echo nv1-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit 0 ;;
+ F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
+ FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+ FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+ FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
+ echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+ exit 0 ;;
+ i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
+ echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE}
+ exit 0 ;;
+ sparc*:BSD/OS:*:*)
+ echo sparc-unknown-bsdi${UNAME_RELEASE}
+ exit 0 ;;
+ *:BSD/OS:*:*)
+ echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE}
+ exit 0 ;;
+ *:FreeBSD:*:*|*:GNU/FreeBSD:*:*)
+ # Determine whether the default compiler uses glibc.
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #include <features.h>
+ #if __GLIBC__ >= 2
+ LIBC=gnu
+ #else
+ LIBC=
+ #endif
+EOF
+ eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=`
+ echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`${LIBC:+-$LIBC}
+ exit 0 ;;
+ i*:CYGWIN*:*)
+ echo ${UNAME_MACHINE}-pc-cygwin
+ exit 0 ;;
+ i*:MINGW*:*)
+ echo ${UNAME_MACHINE}-pc-mingw32
+ exit 0 ;;
+ i*:PW*:*)
+ echo ${UNAME_MACHINE}-pc-pw32
+ exit 0 ;;
+ x86:Interix*:[34]*)
+ echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//'
+ exit 0 ;;
+ [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*)
+ echo i${UNAME_MACHINE}-pc-mks
+ exit 0 ;;
+ i*:Windows_NT*:* | Pentium*:Windows_NT*:*)
+ # How do we know it's Interix rather than the generic POSIX subsystem?
+ # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we
+ # UNAME_MACHINE based on the output of uname instead of i386?
+ echo i586-pc-interix
+ exit 0 ;;
+ i*:UWIN*:*)
+ echo ${UNAME_MACHINE}-pc-uwin
+ exit 0 ;;
+ p*:CYGWIN*:*)
+ echo powerpcle-unknown-cygwin
+ exit 0 ;;
+ prep*:SunOS:5.*:*)
+ echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit 0 ;;
+ *:GNU:*:*)
+ echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'`
+ exit 0 ;;
+ i*86:Minix:*:*)
+ echo ${UNAME_MACHINE}-pc-minix
+ exit 0 ;;
+ arm*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit 0 ;;
+ cris:Linux:*:*)
+ echo cris-axis-linux-gnu
+ exit 0 ;;
+ ia64:Linux:*:*)
+ echo ${UNAME_MACHINE}-${VENDOR:-unknown}-linux-gnu
+ exit 0 ;;
+ m68*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit 0 ;;
+ mips:Linux:*:*)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #undef CPU
+ #undef mips
+ #undef mipsel
+ #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+ CPU=mipsel
+ #else
+ #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+ CPU=mips
+ #else
+ CPU=
+ #endif
+ #endif
+EOF
+ eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=`
+ test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0
+ ;;
+ mips64:Linux:*:*)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #undef CPU
+ #undef mips64
+ #undef mips64el
+ #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+ CPU=mips64el
+ #else
+ #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+ CPU=mips64
+ #else
+ CPU=
+ #endif
+ #endif
+EOF
+ eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=`
+ test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0
+ ;;
+ ppc:Linux:*:*)
+ echo powerpc-${VENDOR:-unknown}-linux-gnu
+ exit 0 ;;
+ ppc64:Linux:*:*)
+ echo powerpc64-${VENDOR:-unknown}-linux-gnu
+ exit 0 ;;
+ alpha:Linux:*:*)
+ case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
+ EV5) UNAME_MACHINE=alphaev5 ;;
+ EV56) UNAME_MACHINE=alphaev56 ;;
+ PCA56) UNAME_MACHINE=alphapca56 ;;
+ PCA57) UNAME_MACHINE=alphapca56 ;;
+ EV6) UNAME_MACHINE=alphaev6 ;;
+ EV67) UNAME_MACHINE=alphaev67 ;;
+ EV68*) UNAME_MACHINE=alphaev68 ;;
+ esac
+ objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null
+ if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi
+ echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC}
+ exit 0 ;;
+ parisc:Linux:*:* | hppa:Linux:*:*)
+ # Look for CPU level
+ case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
+ PA7*) echo hppa1.1-unknown-linux-gnu ;;
+ PA8*) echo hppa2.0-unknown-linux-gnu ;;
+ *) echo hppa-unknown-linux-gnu ;;
+ esac
+ exit 0 ;;
+ parisc64:Linux:*:* | hppa64:Linux:*:*)
+ echo hppa64-unknown-linux-gnu
+ exit 0 ;;
+ s390:Linux:*:* | s390x:Linux:*:*)
+ echo ${UNAME_MACHINE}-${VENDOR:-ibm}-linux-gnu
+ exit 0 ;;
+ sh64*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit 0 ;;
+ sh*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit 0 ;;
+ sparc:Linux:*:* | sparc64:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit 0 ;;
+ x86_64:Linux:*:*)
+ echo x86_64-${VENDOR:-unknown}-linux-gnu
+ exit 0 ;;
+ i*86:Linux:*:*)
+ # The BFD linker knows what the default object file format is, so
+ # first see if it will tell us. cd to the root directory to prevent
+ # problems with other programs or directories called `ld' in the path.
+ # Set LC_ALL=C to ensure ld outputs messages in English.
+ ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \
+ | sed -ne '/supported targets:/!d
+ s/[ ][ ]*/ /g
+ s/.*supported targets: *//
+ s/ .*//
+ p'`
+ case "$ld_supported_targets" in
+ elf32-i386)
+ TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu"
+ ;;
+ a.out-i386-linux)
+ echo "${UNAME_MACHINE}-pc-linux-gnuaout"
+ exit 0 ;;
+ coff-i386)
+ echo "${UNAME_MACHINE}-pc-linux-gnucoff"
+ exit 0 ;;
+ "")
+ # Either a pre-BFD a.out linker (linux-gnuoldld) or
+ # one that does not give us useful --help.
+ echo "${UNAME_MACHINE}-pc-linux-gnuoldld"
+ exit 0 ;;
+ esac
+ # Determine whether the default compiler is a.out or elf
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #include <features.h>
+ #ifdef __ELF__
+ # ifdef __GLIBC__
+ # if __GLIBC__ >= 2
+ LIBC=gnu
+ # else
+ LIBC=gnulibc1
+ # endif
+ # else
+ LIBC=gnulibc1
+ # endif
+ #else
+ #ifdef __INTEL_COMPILER
+ LIBC=gnu
+ #else
+ LIBC=gnuaout
+ #endif
+ #endif
+EOF
+ eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=`
+ test x"${LIBC}" != x && echo "${UNAME_MACHINE}-${VENDOR:-pc}-linux-${LIBC}" && exit 0
+ test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0
+ ;;
+ i*86:DYNIX/ptx:4*:*)
+ # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
+ # earlier versions are messed up and put the nodename in both
+ # sysname and nodename.
+ echo i386-sequent-sysv4
+ exit 0 ;;
+ i*86:UNIX_SV:4.2MP:2.*)
+ # Unixware is an offshoot of SVR4, but it has its own version
+ # number series starting with 2...
+ # I am not positive that other SVR4 systems won't match this,
+ # I just have to hope. -- rms.
+ # Use sysv4.2uw... so that sysv4* matches it.
+ echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION}
+ exit 0 ;;
+ i*86:OS/2:*:*)
+ # If we were able to find `uname', then EMX Unix compatibility
+ # is probably installed.
+ echo ${UNAME_MACHINE}-pc-os2-emx
+ exit 0 ;;
+ i*86:XTS-300:*:STOP)
+ echo ${UNAME_MACHINE}-unknown-stop
+ exit 0 ;;
+ i*86:atheos:*:*)
+ echo ${UNAME_MACHINE}-unknown-atheos
+ exit 0 ;;
+ i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*)
+ echo i386-unknown-lynxos${UNAME_RELEASE}
+ exit 0 ;;
+ i*86:*DOS:*:*)
+ echo ${UNAME_MACHINE}-pc-msdosdjgpp
+ exit 0 ;;
+ i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*)
+ UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'`
+ if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+ echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL}
+ else
+ echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL}
+ fi
+ exit 0 ;;
+ i*86:*:5:[78]*)
+ case `/bin/uname -X | grep "^Machine"` in
+ *486*) UNAME_MACHINE=i486 ;;
+ *Pentium) UNAME_MACHINE=i586 ;;
+ *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
+ esac
+ echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+ exit 0 ;;
+ i*86:*:3.2:*)
+ if test -f /usr/options/cb.name; then
+ UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+ echo ${UNAME_MACHINE}-pc-isc$UNAME_REL
+ elif /bin/uname -X 2>/dev/null >/dev/null ; then
+ UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
+ (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
+ (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
+ && UNAME_MACHINE=i586
+ (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
+ && UNAME_MACHINE=i686
+ (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
+ && UNAME_MACHINE=i686
+ echo ${UNAME_MACHINE}-pc-sco$UNAME_REL
+ else
+ echo ${UNAME_MACHINE}-pc-sysv32
+ fi
+ exit 0 ;;
+ pc:*:*:*)
+ # Left here for compatibility:
+ # uname -m prints for DJGPP always 'pc', but it prints nothing about
+ # the processor, so we play safe by assuming i386.
+ echo i386-pc-msdosdjgpp
+ exit 0 ;;
+ Intel:Mach:3*:*)
+ echo i386-pc-mach3
+ exit 0 ;;
+ paragon:*:*:*)
+ echo i860-intel-osf1
+ exit 0 ;;
+ i860:*:4.*:*) # i860-SVR4
+ if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+ echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4
+ else # Add other i860-SVR4 vendors below as they are discovered.
+ echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4
+ fi
+ exit 0 ;;
+ mini*:CTIX:SYS*5:*)
+ # "miniframe"
+ echo m68010-convergent-sysv
+ exit 0 ;;
+ mc68k:UNIX:SYSTEM5:3.51m)
+ echo m68k-convergent-sysv
+ exit 0 ;;
+ M680?0:D-NIX:5.3:*)
+ echo m68k-diab-dnix
+ exit 0 ;;
+ M68*:*:R3V[567]*:*)
+ test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;;
+ 3[34]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0)
+ OS_REL=''
+ test -r /etc/.relid \
+ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+ && echo i486-ncr-sysv4.3${OS_REL} && exit 0
+ /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+ && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;;
+ 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+ && echo i486-ncr-sysv4 && exit 0 ;;
+ m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
+ echo m68k-unknown-lynxos${UNAME_RELEASE}
+ exit 0 ;;
+ mc68030:UNIX_System_V:4.*:*)
+ echo m68k-atari-sysv4
+ exit 0 ;;
+ TSUNAMI:LynxOS:2.*:*)
+ echo sparc-unknown-lynxos${UNAME_RELEASE}
+ exit 0 ;;
+ rs6000:LynxOS:2.*:*)
+ echo rs6000-unknown-lynxos${UNAME_RELEASE}
+ exit 0 ;;
+ PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*)
+ echo powerpc-unknown-lynxos${UNAME_RELEASE}
+ exit 0 ;;
+ SM[BE]S:UNIX_SV:*:*)
+ echo mips-dde-sysv${UNAME_RELEASE}
+ exit 0 ;;
+ RM*:ReliantUNIX-*:*:*)
+ echo mips-sni-sysv4
+ exit 0 ;;
+ RM*:SINIX-*:*:*)
+ echo mips-sni-sysv4
+ exit 0 ;;
+ *:SINIX-*:*:*)
+ if uname -p 2>/dev/null >/dev/null ; then
+ UNAME_MACHINE=`(uname -p) 2>/dev/null`
+ echo ${UNAME_MACHINE}-sni-sysv4
+ else
+ echo ns32k-sni-sysv
+ fi
+ exit 0 ;;
+ PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+ # says <Richard.M.Bartel@ccMail.Census.GOV>
+ echo i586-unisys-sysv4
+ exit 0 ;;
+ *:UNIX_System_V:4*:FTX*)
+ # From Gerald Hewes <hewes@openmarket.com>.
+ # How about differentiating between stratus architectures? -djm
+ echo hppa1.1-stratus-sysv4
+ exit 0 ;;
+ *:*:*:FTX*)
+ # From seanf@swdc.stratus.com.
+ echo i860-stratus-sysv4
+ exit 0 ;;
+ *:VOS:*:*)
+ # From Paul.Green@stratus.com.
+ echo hppa1.1-stratus-vos
+ exit 0 ;;
+ mc68*:A/UX:*:*)
+ echo m68k-apple-aux${UNAME_RELEASE}
+ exit 0 ;;
+ news*:NEWS-OS:6*:*)
+ echo mips-sony-newsos6
+ exit 0 ;;
+ R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+ if [ -d /usr/nec ]; then
+ echo mips-nec-sysv${UNAME_RELEASE}
+ else
+ echo mips-unknown-sysv${UNAME_RELEASE}
+ fi
+ exit 0 ;;
+ BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only.
+ echo powerpc-be-beos
+ exit 0 ;;
+ BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only.
+ echo powerpc-apple-beos
+ exit 0 ;;
+ BePC:BeOS:*:*) # BeOS running on Intel PC compatible.
+ echo i586-pc-beos
+ exit 0 ;;
+ SX-4:SUPER-UX:*:*)
+ echo sx4-nec-superux${UNAME_RELEASE}
+ exit 0 ;;
+ SX-5:SUPER-UX:*:*)
+ echo sx5-nec-superux${UNAME_RELEASE}
+ exit 0 ;;
+ SX-6:SUPER-UX:*:*)
+ echo sx6-nec-superux${UNAME_RELEASE}
+ exit 0 ;;
+ Power*:Rhapsody:*:*)
+ echo powerpc-apple-rhapsody${UNAME_RELEASE}
+ exit 0 ;;
+ *:Rhapsody:*:*)
+ echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
+ exit 0 ;;
+ *:Darwin:*:*)
+ case `uname -p` in
+ *86) UNAME_PROCESSOR=i686 ;;
+ powerpc) UNAME_PROCESSOR=powerpc ;;
+ esac
+ echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE}
+ exit 0 ;;
+ *:procnto*:*:* | *:QNX:[0123456789]*:*)
+ UNAME_PROCESSOR=`uname -p`
+ if test "$UNAME_PROCESSOR" = "x86"; then
+ UNAME_PROCESSOR=i386
+ UNAME_MACHINE=pc
+ fi
+ echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE}
+ exit 0 ;;
+ *:QNX:*:4*)
+ echo i386-pc-qnx
+ exit 0 ;;
+ NSR-[DGKLNPTVW]:NONSTOP_KERNEL:*:*)
+ echo nsr-tandem-nsk${UNAME_RELEASE}
+ exit 0 ;;
+ *:NonStop-UX:*:*)
+ echo mips-compaq-nonstopux
+ exit 0 ;;
+ BS2000:POSIX*:*:*)
+ echo bs2000-siemens-sysv
+ exit 0 ;;
+ DS/*:UNIX_System_V:*:*)
+ echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE}
+ exit 0 ;;
+ *:Plan9:*:*)
+ # "uname -m" is not consistent, so use $cputype instead. 386
+ # is converted to i386 for consistency with other x86
+ # operating systems.
+ if test "$cputype" = "386"; then
+ UNAME_MACHINE=i386
+ else
+ UNAME_MACHINE="$cputype"
+ fi
+ echo ${UNAME_MACHINE}-unknown-plan9
+ exit 0 ;;
+ *:TOPS-10:*:*)
+ echo pdp10-unknown-tops10
+ exit 0 ;;
+ *:TENEX:*:*)
+ echo pdp10-unknown-tenex
+ exit 0 ;;
+ KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
+ echo pdp10-dec-tops20
+ exit 0 ;;
+ XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
+ echo pdp10-xkl-tops20
+ exit 0 ;;
+ *:TOPS-20:*:*)
+ echo pdp10-unknown-tops20
+ exit 0 ;;
+ *:ITS:*:*)
+ echo pdp10-unknown-its
+ exit 0 ;;
+ SEI:*:*:SEIUX)
+ echo mips-sei-seiux${UNAME_RELEASE}
+ exit 0 ;;
+esac
+
+#echo '(No uname command or uname output not recognized.)' 1>&2
+#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2
+
+eval $set_cc_for_build
+cat >$dummy.c <<EOF
+#ifdef _SEQUENT_
+# include <sys/types.h>
+# include <sys/utsname.h>
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+ /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed,
+ I don't know.... */
+ printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+ printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+ "4"
+#else
+ ""
+#endif
+ ); exit (0);
+#endif
+#endif
+
+#if defined (__arm) && defined (__acorn) && defined (__unix)
+ printf ("arm-acorn-riscix"); exit (0);
+#endif
+
+#if defined (hp300) && !defined (hpux)
+ printf ("m68k-hp-bsd\n"); exit (0);
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+ int version;
+ version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+ if (version < 4)
+ printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+ else
+ printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+ exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+ printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+ printf ("ns32k-encore-mach\n"); exit (0);
+#else
+ printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+ printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+ printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+ printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+ struct utsname un;
+
+ uname(&un);
+
+ if (strncmp(un.version, "V2", 2) == 0) {
+ printf ("i386-sequent-ptx2\n"); exit (0);
+ }
+ if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+ printf ("i386-sequent-ptx1\n"); exit (0);
+ }
+ printf ("i386-sequent-ptx\n"); exit (0);
+
+#endif
+
+#if defined (vax)
+# if !defined (ultrix)
+# include <sys/param.h>
+# if defined (BSD)
+# if BSD == 43
+ printf ("vax-dec-bsd4.3\n"); exit (0);
+# else
+# if BSD == 199006
+ printf ("vax-dec-bsd4.3reno\n"); exit (0);
+# else
+ printf ("vax-dec-bsd\n"); exit (0);
+# endif
+# endif
+# else
+ printf ("vax-dec-bsd\n"); exit (0);
+# endif
+# else
+ printf ("vax-dec-ultrix\n"); exit (0);
+# endif
+#endif
+
+#if defined (alliant) && defined (i860)
+ printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+ exit (1);
+}
+EOF
+
+$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && exit 0
+
+# Apollos put the system type in the environment.
+
+test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; }
+
+# Convex versions that predate uname can use getsysinfo(1)
+
+if [ -x /usr/convex/getsysinfo ]
+then
+ case `getsysinfo -f cpu_type` in
+ c1*)
+ echo c1-convex-bsd
+ exit 0 ;;
+ c2*)
+ if getsysinfo -f scalar_acc
+ then echo c32-convex-bsd
+ else echo c2-convex-bsd
+ fi
+ exit 0 ;;
+ c34*)
+ echo c34-convex-bsd
+ exit 0 ;;
+ c38*)
+ echo c38-convex-bsd
+ exit 0 ;;
+ c4*)
+ echo c4-convex-bsd
+ exit 0 ;;
+ esac
+fi
+
+cat >&2 <<EOF
+$0: unable to guess system type
+
+This script, last modified $timestamp, has failed to recognize
+the operating system you are using. It is advised that you
+download the most up to date version of the config scripts from
+
+ ftp://ftp.gnu.org/pub/gnu/config/
+
+If the version you run ($0) is already up to date, please
+send the following data and any information you think might be
+pertinent to <config-patches@gnu.org> in order to provide the needed
+information to handle your system.
+
+config.guess timestamp = $timestamp
+
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null`
+
+hostinfo = `(hostinfo) 2>/dev/null`
+/bin/universe = `(/bin/universe) 2>/dev/null`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null`
+/bin/arch = `(/bin/arch) 2>/dev/null`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
+
+UNAME_MACHINE = ${UNAME_MACHINE}
+UNAME_RELEASE = ${UNAME_RELEASE}
+UNAME_SYSTEM = ${UNAME_SYSTEM}
+UNAME_VERSION = ${UNAME_VERSION}
+EOF
+
+exit 1
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 00000000..65f14ac9
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,82 @@
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* CPU type */
+#undef CPU
+
+/* Documentation directory */
+#undef DOCDIR
+
+/* Define to 1 if you have the `futimes' function. */
+#undef HAVE_FUTIMES
+
+/* Define to 1 if you have the <getopt.h> header file. */
+#undef HAVE_GETOPT_H
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the <ncurses.h> header file. */
+#undef HAVE_NCURSES_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <termio.h> header file. */
+#undef HAVE_TERMIO_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to 1 if you have the `wcswidth' function. */
+#undef HAVE_WCSWIDTH
+
+/* Define to 1 if you have the `wcwidth' function. */
+#undef HAVE_WCWIDTH
+
+/* Define to 1 if you have the `wprintf' function. */
+#undef HAVE_WPRINTF
+
+/* Define to 1 if you have the file `AC_File'. */
+#undef HAVE__PROC_SELF_STAT
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Installation directory */
+#undef PREFIX
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Evil kludge to get Power based machines to work */
+#undef TPUTS_KLUDGE
diff --git a/config.sub b/config.sub
new file mode 100755
index 00000000..7cee3d6e
--- /dev/null
+++ b/config.sub
@@ -0,0 +1,1500 @@
+#! /bin/sh
+# Configuration validation subroutine script.
+# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+# 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
+
+timestamp='2003-06-18'
+
+# This file is (in principle) common to ALL GNU software.
+# The presence of a machine in this file suggests that SOME GNU software
+# can handle that machine. It does not imply ALL GNU software can.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Please send patches to <config-patches@gnu.org>. Submit a context
+# diff and a properly formatted ChangeLog entry.
+#
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support. The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION] CPU-MFR-OPSYS
+ $0 [OPTION] ALIAS
+
+Canonicalize a configuration name.
+
+Operation modes:
+ -h, --help print this help, then exit
+ -t, --time-stamp print date of last modification, then exit
+ -v, --version print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.sub ($timestamp)
+
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001
+Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+ case $1 in
+ --time-stamp | --time* | -t )
+ echo "$timestamp" ; exit 0 ;;
+ --version | -v )
+ echo "$version" ; exit 0 ;;
+ --help | --h* | -h )
+ echo "$usage"; exit 0 ;;
+ -- ) # Stop option processing
+ shift; break ;;
+ - ) # Use stdin as input.
+ break ;;
+ -* )
+ echo "$me: invalid option $1$help"
+ exit 1 ;;
+
+ *local*)
+ # First pass through any local machine types.
+ echo $1
+ exit 0;;
+
+ * )
+ break ;;
+ esac
+done
+
+case $# in
+ 0) echo "$me: missing argument$help" >&2
+ exit 1;;
+ 1) ;;
+ *) echo "$me: too many arguments$help" >&2
+ exit 1;;
+esac
+
+# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any).
+# Here we must recognize all the valid KERNEL-OS combinations.
+maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
+case $maybe_os in
+ nto-qnx* | linux-gnu* | freebsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*)
+ os=-$maybe_os
+ basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
+ ;;
+ *)
+ basic_machine=`echo $1 | sed 's/-[^-]*$//'`
+ if [ $basic_machine != $1 ]
+ then os=`echo $1 | sed 's/.*-/-/'`
+ else os=; fi
+ ;;
+esac
+
+### Let's recognize common machines as not being operating systems so
+### that things like config.sub decstation-3100 work. We also
+### recognize some manufacturers as not being operating systems, so we
+### can provide default operating systems below.
+case $os in
+ -sun*os*)
+ # Prevent following clause from handling this invalid input.
+ ;;
+ -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \
+ -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \
+ -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \
+ -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
+ -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
+ -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
+ -apple | -axis)
+ os=
+ basic_machine=$1
+ ;;
+ -sim | -cisco | -oki | -wec | -winbond)
+ os=
+ basic_machine=$1
+ ;;
+ -scout)
+ ;;
+ -wrs)
+ os=-vxworks
+ basic_machine=$1
+ ;;
+ -chorusos*)
+ os=-chorusos
+ basic_machine=$1
+ ;;
+ -chorusrdb)
+ os=-chorusrdb
+ basic_machine=$1
+ ;;
+ -hiux*)
+ os=-hiuxwe2
+ ;;
+ -sco5)
+ os=-sco3.2v5
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco4)
+ os=-sco3.2v4
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco3.2.[4-9]*)
+ os=`echo $os | sed -e 's/sco3.2./sco3.2v/'`
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco3.2v[4-9]*)
+ # Don't forget version if it is 3.2v4 or newer.
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco*)
+ os=-sco3.2v2
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -udk*)
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -isc)
+ os=-isc2.2
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -clix*)
+ basic_machine=clipper-intergraph
+ ;;
+ -isc*)
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -lynx*)
+ os=-lynxos
+ ;;
+ -ptx*)
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'`
+ ;;
+ -windowsnt*)
+ os=`echo $os | sed -e 's/windowsnt/winnt/'`
+ ;;
+ -psos*)
+ os=-psos
+ ;;
+ -mint | -mint[0-9]*)
+ basic_machine=m68k-atari
+ os=-mint
+ ;;
+esac
+
+# Decode aliases for certain CPU-COMPANY combinations.
+case $basic_machine in
+ # Recognize the basic CPU types without company name.
+ # Some are omitted here because they have special meanings below.
+ 1750a | 580 \
+ | a29k \
+ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \
+ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \
+ | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \
+ | c4x | clipper \
+ | d10v | d30v | dlx | dsp16xx \
+ | fr30 | frv \
+ | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+ | i370 | i860 | i960 | ia64 \
+ | ip2k \
+ | m32r | m68000 | m68k | m88k | mcore \
+ | mips | mipsbe | mipseb | mipsel | mipsle \
+ | mips16 \
+ | mips64 | mips64el \
+ | mips64vr | mips64vrel \
+ | mips64orion | mips64orionel \
+ | mips64vr4100 | mips64vr4100el \
+ | mips64vr4300 | mips64vr4300el \
+ | mips64vr5000 | mips64vr5000el \
+ | mipsisa32 | mipsisa32el \
+ | mipsisa32r2 | mipsisa32r2el \
+ | mipsisa64 | mipsisa64el \
+ | mipsisa64sb1 | mipsisa64sb1el \
+ | mipsisa64sr71k | mipsisa64sr71kel \
+ | mipstx39 | mipstx39el \
+ | mn10200 | mn10300 \
+ | msp430 \
+ | ns16k | ns32k \
+ | openrisc | or32 \
+ | pdp10 | pdp11 | pj | pjl \
+ | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \
+ | pyramid \
+ | s390 | s390x \
+ | sh | sh[1234] | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \
+ | sh64 | sh64le \
+ | sparc | sparc64 | sparc86x | sparclet | sparclite | sparcv9 | sparcv9b \
+ | strongarm \
+ | tahoe | thumb | tic4x | tic80 | tron \
+ | v850 | v850e \
+ | we32k \
+ | x86 | xscale | xstormy16 | xtensa \
+ | z8k)
+ basic_machine=$basic_machine-unknown
+ ;;
+ m6811 | m68hc11 | m6812 | m68hc12)
+ # Motorola 68HC11/12.
+ basic_machine=$basic_machine-unknown
+ os=-none
+ ;;
+ m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k)
+ ;;
+
+ # We use `pc' rather than `unknown'
+ # because (1) that's what they normally are, and
+ # (2) the word "unknown" tends to confuse beginning users.
+ i*86 | x86_64)
+ basic_machine=$basic_machine-pc
+ ;;
+ # Object if more than one company name word.
+ *-*-*)
+ echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+ exit 1
+ ;;
+ # Recognize the basic CPU types with company name.
+ 580-* \
+ | a29k-* \
+ | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \
+ | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \
+ | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \
+ | arm-* | armbe-* | armle-* | armeb-* | armv*-* \
+ | avr-* \
+ | bs2000-* \
+ | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \
+ | clipper-* | cydra-* \
+ | d10v-* | d30v-* | dlx-* \
+ | elxsi-* \
+ | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \
+ | h8300-* | h8500-* \
+ | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
+ | i*86-* | i860-* | i960-* | ia64-* \
+ | ip2k-* \
+ | m32r-* \
+ | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
+ | m88110-* | m88k-* | mcore-* \
+ | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \
+ | mips16-* \
+ | mips64-* | mips64el-* \
+ | mips64vr-* | mips64vrel-* \
+ | mips64orion-* | mips64orionel-* \
+ | mips64vr4100-* | mips64vr4100el-* \
+ | mips64vr4300-* | mips64vr4300el-* \
+ | mips64vr5000-* | mips64vr5000el-* \
+ | mipsisa32-* | mipsisa32el-* \
+ | mipsisa32r2-* | mipsisa32r2el-* \
+ | mipsisa64-* | mipsisa64el-* \
+ | mipsisa64sb1-* | mipsisa64sb1el-* \
+ | mipsisa64sr71k-* | mipsisa64sr71kel-* \
+ | mipstx39-* | mipstx39el-* \
+ | msp430-* \
+ | none-* | np1-* | nv1-* | ns16k-* | ns32k-* \
+ | orion-* \
+ | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
+ | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \
+ | pyramid-* \
+ | romp-* | rs6000-* \
+ | s390-* | s390x-* \
+ | sh-* | sh[1234]-* | sh[23]e-* | sh[34]eb-* | shbe-* \
+ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
+ | sparc-* | sparc64-* | sparc86x-* | sparclet-* | sparclite-* \
+ | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \
+ | tahoe-* | thumb-* \
+ | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \
+ | tron-* \
+ | v850-* | v850e-* | vax-* \
+ | we32k-* \
+ | x86-* | x86_64-* | xps100-* | xscale-* | xstormy16-* \
+ | xtensa-* \
+ | ymp-* \
+ | z8k-*)
+ ;;
+ # Recognize the various machine names and aliases which stand
+ # for a CPU type and a company and sometimes even an OS.
+ 386bsd)
+ basic_machine=i386-unknown
+ os=-bsd
+ ;;
+ 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+ basic_machine=m68000-att
+ ;;
+ 3b*)
+ basic_machine=we32k-att
+ ;;
+ a29khif)
+ basic_machine=a29k-amd
+ os=-udi
+ ;;
+ adobe68k)
+ basic_machine=m68010-adobe
+ os=-scout
+ ;;
+ alliant | fx80)
+ basic_machine=fx80-alliant
+ ;;
+ altos | altos3068)
+ basic_machine=m68k-altos
+ ;;
+ am29k)
+ basic_machine=a29k-none
+ os=-bsd
+ ;;
+ amd64)
+ basic_machine=x86_64-pc
+ ;;
+ amdahl)
+ basic_machine=580-amdahl
+ os=-sysv
+ ;;
+ amiga | amiga-*)
+ basic_machine=m68k-unknown
+ ;;
+ amigaos | amigados)
+ basic_machine=m68k-unknown
+ os=-amigaos
+ ;;
+ amigaunix | amix)
+ basic_machine=m68k-unknown
+ os=-sysv4
+ ;;
+ apollo68)
+ basic_machine=m68k-apollo
+ os=-sysv
+ ;;
+ apollo68bsd)
+ basic_machine=m68k-apollo
+ os=-bsd
+ ;;
+ aux)
+ basic_machine=m68k-apple
+ os=-aux
+ ;;
+ balance)
+ basic_machine=ns32k-sequent
+ os=-dynix
+ ;;
+ c90)
+ basic_machine=c90-cray
+ os=-unicos
+ ;;
+ convex-c1)
+ basic_machine=c1-convex
+ os=-bsd
+ ;;
+ convex-c2)
+ basic_machine=c2-convex
+ os=-bsd
+ ;;
+ convex-c32)
+ basic_machine=c32-convex
+ os=-bsd
+ ;;
+ convex-c34)
+ basic_machine=c34-convex
+ os=-bsd
+ ;;
+ convex-c38)
+ basic_machine=c38-convex
+ os=-bsd
+ ;;
+ cray | j90)
+ basic_machine=j90-cray
+ os=-unicos
+ ;;
+ crds | unos)
+ basic_machine=m68k-crds
+ ;;
+ cris | cris-* | etrax*)
+ basic_machine=cris-axis
+ ;;
+ da30 | da30-*)
+ basic_machine=m68k-da30
+ ;;
+ decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn)
+ basic_machine=mips-dec
+ ;;
+ decsystem10* | dec10*)
+ basic_machine=pdp10-dec
+ os=-tops10
+ ;;
+ decsystem20* | dec20*)
+ basic_machine=pdp10-dec
+ os=-tops20
+ ;;
+ delta | 3300 | motorola-3300 | motorola-delta \
+ | 3300-motorola | delta-motorola)
+ basic_machine=m68k-motorola
+ ;;
+ delta88)
+ basic_machine=m88k-motorola
+ os=-sysv3
+ ;;
+ dpx20 | dpx20-*)
+ basic_machine=rs6000-bull
+ os=-bosx
+ ;;
+ dpx2* | dpx2*-bull)
+ basic_machine=m68k-bull
+ os=-sysv3
+ ;;
+ ebmon29k)
+ basic_machine=a29k-amd
+ os=-ebmon
+ ;;
+ elxsi)
+ basic_machine=elxsi-elxsi
+ os=-bsd
+ ;;
+ encore | umax | mmax)
+ basic_machine=ns32k-encore
+ ;;
+ es1800 | OSE68k | ose68k | ose | OSE)
+ basic_machine=m68k-ericsson
+ os=-ose
+ ;;
+ fx2800)
+ basic_machine=i860-alliant
+ ;;
+ genix)
+ basic_machine=ns32k-ns
+ ;;
+ gmicro)
+ basic_machine=tron-gmicro
+ os=-sysv
+ ;;
+ go32)
+ basic_machine=i386-pc
+ os=-go32
+ ;;
+ h3050r* | hiux*)
+ basic_machine=hppa1.1-hitachi
+ os=-hiuxwe2
+ ;;
+ h8300hms)
+ basic_machine=h8300-hitachi
+ os=-hms
+ ;;
+ h8300xray)
+ basic_machine=h8300-hitachi
+ os=-xray
+ ;;
+ h8500hms)
+ basic_machine=h8500-hitachi
+ os=-hms
+ ;;
+ harris)
+ basic_machine=m88k-harris
+ os=-sysv3
+ ;;
+ hp300-*)
+ basic_machine=m68k-hp
+ ;;
+ hp300bsd)
+ basic_machine=m68k-hp
+ os=-bsd
+ ;;
+ hp300hpux)
+ basic_machine=m68k-hp
+ os=-hpux
+ ;;
+ hp3k9[0-9][0-9] | hp9[0-9][0-9])
+ basic_machine=hppa1.0-hp
+ ;;
+ hp9k2[0-9][0-9] | hp9k31[0-9])
+ basic_machine=m68000-hp
+ ;;
+ hp9k3[2-9][0-9])
+ basic_machine=m68k-hp
+ ;;
+ hp9k6[0-9][0-9] | hp6[0-9][0-9])
+ basic_machine=hppa1.0-hp
+ ;;
+ hp9k7[0-79][0-9] | hp7[0-79][0-9])
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k78[0-9] | hp78[0-9])
+ # FIXME: really hppa2.0-hp
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+ # FIXME: really hppa2.0-hp
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k8[0-9][13679] | hp8[0-9][13679])
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k8[0-9][0-9] | hp8[0-9][0-9])
+ basic_machine=hppa1.0-hp
+ ;;
+ hppa-next)
+ os=-nextstep3
+ ;;
+ hppaosf)
+ basic_machine=hppa1.1-hp
+ os=-osf
+ ;;
+ hppro)
+ basic_machine=hppa1.1-hp
+ os=-proelf
+ ;;
+ i370-ibm* | ibm*)
+ basic_machine=i370-ibm
+ ;;
+# I'm not sure what "Sysv32" means. Should this be sysv3.2?
+ i*86v32)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-sysv32
+ ;;
+ i*86v4*)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-sysv4
+ ;;
+ i*86v)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-sysv
+ ;;
+ i*86sol2)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-solaris2
+ ;;
+ i386mach)
+ basic_machine=i386-mach
+ os=-mach
+ ;;
+ i386-vsta | vsta)
+ basic_machine=i386-unknown
+ os=-vsta
+ ;;
+ iris | iris4d)
+ basic_machine=mips-sgi
+ case $os in
+ -irix*)
+ ;;
+ *)
+ os=-irix4
+ ;;
+ esac
+ ;;
+ isi68 | isi)
+ basic_machine=m68k-isi
+ os=-sysv
+ ;;
+ m88k-omron*)
+ basic_machine=m88k-omron
+ ;;
+ magnum | m3230)
+ basic_machine=mips-mips
+ os=-sysv
+ ;;
+ merlin)
+ basic_machine=ns32k-utek
+ os=-sysv
+ ;;
+ mingw32)
+ basic_machine=i386-pc
+ os=-mingw32
+ ;;
+ miniframe)
+ basic_machine=m68000-convergent
+ ;;
+ *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*)
+ basic_machine=m68k-atari
+ os=-mint
+ ;;
+ mips3*-*)
+ basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`
+ ;;
+ mips3*)
+ basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown
+ ;;
+ mmix*)
+ basic_machine=mmix-knuth
+ os=-mmixware
+ ;;
+ monitor)
+ basic_machine=m68k-rom68k
+ os=-coff
+ ;;
+ morphos)
+ basic_machine=powerpc-unknown
+ os=-morphos
+ ;;
+ msdos)
+ basic_machine=i386-pc
+ os=-msdos
+ ;;
+ mvs)
+ basic_machine=i370-ibm
+ os=-mvs
+ ;;
+ ncr3000)
+ basic_machine=i486-ncr
+ os=-sysv4
+ ;;
+ netbsd386)
+ basic_machine=i386-unknown
+ os=-netbsd
+ ;;
+ netwinder)
+ basic_machine=armv4l-rebel
+ os=-linux
+ ;;
+ news | news700 | news800 | news900)
+ basic_machine=m68k-sony
+ os=-newsos
+ ;;
+ news1000)
+ basic_machine=m68030-sony
+ os=-newsos
+ ;;
+ news-3600 | risc-news)
+ basic_machine=mips-sony
+ os=-newsos
+ ;;
+ necv70)
+ basic_machine=v70-nec
+ os=-sysv
+ ;;
+ next | m*-next )
+ basic_machine=m68k-next
+ case $os in
+ -nextstep* )
+ ;;
+ -ns2*)
+ os=-nextstep2
+ ;;
+ *)
+ os=-nextstep3
+ ;;
+ esac
+ ;;
+ nh3000)
+ basic_machine=m68k-harris
+ os=-cxux
+ ;;
+ nh[45]000)
+ basic_machine=m88k-harris
+ os=-cxux
+ ;;
+ nindy960)
+ basic_machine=i960-intel
+ os=-nindy
+ ;;
+ mon960)
+ basic_machine=i960-intel
+ os=-mon960
+ ;;
+ nonstopux)
+ basic_machine=mips-compaq
+ os=-nonstopux
+ ;;
+ np1)
+ basic_machine=np1-gould
+ ;;
+ nv1)
+ basic_machine=nv1-cray
+ os=-unicosmp
+ ;;
+ nsr-tandem)
+ basic_machine=nsr-tandem
+ ;;
+ op50n-* | op60c-*)
+ basic_machine=hppa1.1-oki
+ os=-proelf
+ ;;
+ or32 | or32-*)
+ basic_machine=or32-unknown
+ os=-coff
+ ;;
+ OSE68000 | ose68000)
+ basic_machine=m68000-ericsson
+ os=-ose
+ ;;
+ os68k)
+ basic_machine=m68k-none
+ os=-os68k
+ ;;
+ pa-hitachi)
+ basic_machine=hppa1.1-hitachi
+ os=-hiuxwe2
+ ;;
+ paragon)
+ basic_machine=i860-intel
+ os=-osf
+ ;;
+ pbd)
+ basic_machine=sparc-tti
+ ;;
+ pbb)
+ basic_machine=m68k-tti
+ ;;
+ pc532 | pc532-*)
+ basic_machine=ns32k-pc532
+ ;;
+ pentium | p5 | k5 | k6 | nexgen | viac3)
+ basic_machine=i586-pc
+ ;;
+ pentiumpro | p6 | 6x86 | athlon | athlon_*)
+ basic_machine=i686-pc
+ ;;
+ pentiumii | pentium2 | pentiumiii | pentium3)
+ basic_machine=i686-pc
+ ;;
+ pentium4)
+ basic_machine=i786-pc
+ ;;
+ pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+ basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentiumpro-* | p6-* | 6x86-* | athlon-*)
+ basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+ basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentium4-*)
+ basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pn)
+ basic_machine=pn-gould
+ ;;
+ power) basic_machine=power-ibm
+ ;;
+ ppc) basic_machine=powerpc-unknown
+ ;;
+ ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ppcle | powerpclittle | ppc-le | powerpc-little)
+ basic_machine=powerpcle-unknown
+ ;;
+ ppcle-* | powerpclittle-*)
+ basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ppc64) basic_machine=powerpc64-unknown
+ ;;
+ ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ppc64le | powerpc64little | ppc64-le | powerpc64-little)
+ basic_machine=powerpc64le-unknown
+ ;;
+ ppc64le-* | powerpc64little-*)
+ basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ps2)
+ basic_machine=i386-ibm
+ ;;
+ pw32)
+ basic_machine=i586-unknown
+ os=-pw32
+ ;;
+ rom68k)
+ basic_machine=m68k-rom68k
+ os=-coff
+ ;;
+ rm[46]00)
+ basic_machine=mips-siemens
+ ;;
+ rtpc | rtpc-*)
+ basic_machine=romp-ibm
+ ;;
+ sa29200)
+ basic_machine=a29k-amd
+ os=-udi
+ ;;
+ sb1)
+ basic_machine=mipsisa64sb1-unknown
+ ;;
+ sb1el)
+ basic_machine=mipsisa64sb1el-unknown
+ ;;
+ sei)
+ basic_machine=mips-sei
+ os=-seiux
+ ;;
+ sequent)
+ basic_machine=i386-sequent
+ ;;
+ sh)
+ basic_machine=sh-hitachi
+ os=-hms
+ ;;
+ sh64)
+ basic_machine=sh64-unknown
+ ;;
+ sparclite-wrs | simso-wrs)
+ basic_machine=sparclite-wrs
+ os=-vxworks
+ ;;
+ sps7)
+ basic_machine=m68k-bull
+ os=-sysv2
+ ;;
+ spur)
+ basic_machine=spur-unknown
+ ;;
+ st2000)
+ basic_machine=m68k-tandem
+ ;;
+ stratus)
+ basic_machine=i860-stratus
+ os=-sysv4
+ ;;
+ sun2)
+ basic_machine=m68000-sun
+ ;;
+ sun2os3)
+ basic_machine=m68000-sun
+ os=-sunos3
+ ;;
+ sun2os4)
+ basic_machine=m68000-sun
+ os=-sunos4
+ ;;
+ sun3os3)
+ basic_machine=m68k-sun
+ os=-sunos3
+ ;;
+ sun3os4)
+ basic_machine=m68k-sun
+ os=-sunos4
+ ;;
+ sun4os3)
+ basic_machine=sparc-sun
+ os=-sunos3
+ ;;
+ sun4os4)
+ basic_machine=sparc-sun
+ os=-sunos4
+ ;;
+ sun4sol2)
+ basic_machine=sparc-sun
+ os=-solaris2
+ ;;
+ sun3 | sun3-*)
+ basic_machine=m68k-sun
+ ;;
+ sun4)
+ basic_machine=sparc-sun
+ ;;
+ sun386 | sun386i | roadrunner)
+ basic_machine=i386-sun
+ ;;
+ sv1)
+ basic_machine=sv1-cray
+ os=-unicos
+ ;;
+ symmetry)
+ basic_machine=i386-sequent
+ os=-dynix
+ ;;
+ t3e)
+ basic_machine=alphaev5-cray
+ os=-unicos
+ ;;
+ t90)
+ basic_machine=t90-cray
+ os=-unicos
+ ;;
+ tic54x | c54x*)
+ basic_machine=tic54x-unknown
+ os=-coff
+ ;;
+ tic55x | c55x*)
+ basic_machine=tic55x-unknown
+ os=-coff
+ ;;
+ tic6x | c6x*)
+ basic_machine=tic6x-unknown
+ os=-coff
+ ;;
+ tx39)
+ basic_machine=mipstx39-unknown
+ ;;
+ tx39el)
+ basic_machine=mipstx39el-unknown
+ ;;
+ toad1)
+ basic_machine=pdp10-xkl
+ os=-tops20
+ ;;
+ tower | tower-32)
+ basic_machine=m68k-ncr
+ ;;
+ udi29k)
+ basic_machine=a29k-amd
+ os=-udi
+ ;;
+ ultra3)
+ basic_machine=a29k-nyu
+ os=-sym1
+ ;;
+ v810 | necv810)
+ basic_machine=v810-nec
+ os=-none
+ ;;
+ vaxv)
+ basic_machine=vax-dec
+ os=-sysv
+ ;;
+ vms)
+ basic_machine=vax-dec
+ os=-vms
+ ;;
+ vpp*|vx|vx-*)
+ basic_machine=f301-fujitsu
+ ;;
+ vxworks960)
+ basic_machine=i960-wrs
+ os=-vxworks
+ ;;
+ vxworks68)
+ basic_machine=m68k-wrs
+ os=-vxworks
+ ;;
+ vxworks29k)
+ basic_machine=a29k-wrs
+ os=-vxworks
+ ;;
+ w65*)
+ basic_machine=w65-wdc
+ os=-none
+ ;;
+ w89k-*)
+ basic_machine=hppa1.1-winbond
+ os=-proelf
+ ;;
+ xps | xps100)
+ basic_machine=xps100-honeywell
+ ;;
+ ymp)
+ basic_machine=ymp-cray
+ os=-unicos
+ ;;
+ z8k-*-coff)
+ basic_machine=z8k-unknown
+ os=-sim
+ ;;
+ none)
+ basic_machine=none-none
+ os=-none
+ ;;
+
+# Here we handle the default manufacturer of certain CPU types. It is in
+# some cases the only manufacturer, in others, it is the most popular.
+ w89k)
+ basic_machine=hppa1.1-winbond
+ ;;
+ op50n)
+ basic_machine=hppa1.1-oki
+ ;;
+ op60c)
+ basic_machine=hppa1.1-oki
+ ;;
+ romp)
+ basic_machine=romp-ibm
+ ;;
+ rs6000)
+ basic_machine=rs6000-ibm
+ ;;
+ vax)
+ basic_machine=vax-dec
+ ;;
+ pdp10)
+ # there are many clones, so DEC is not a safe bet
+ basic_machine=pdp10-unknown
+ ;;
+ pdp11)
+ basic_machine=pdp11-dec
+ ;;
+ we32k)
+ basic_machine=we32k-att
+ ;;
+ sh3 | sh4 | sh[34]eb | sh[1234]le | sh[23]ele)
+ basic_machine=sh-unknown
+ ;;
+ sh64)
+ basic_machine=sh64-unknown
+ ;;
+ sparc | sparcv9 | sparcv9b)
+ basic_machine=sparc-sun
+ ;;
+ cydra)
+ basic_machine=cydra-cydrome
+ ;;
+ orion)
+ basic_machine=orion-highlevel
+ ;;
+ orion105)
+ basic_machine=clipper-highlevel
+ ;;
+ mac | mpw | mac-mpw)
+ basic_machine=m68k-apple
+ ;;
+ pmac | pmac-mpw)
+ basic_machine=powerpc-apple
+ ;;
+ *-unknown)
+ # Make sure to match an already-canonicalized machine name.
+ ;;
+ *)
+ echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+ exit 1
+ ;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $basic_machine in
+ *-digital*)
+ basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'`
+ ;;
+ *-commodore*)
+ basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'`
+ ;;
+ *)
+ ;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if [ x"$os" != x"" ]
+then
+case $os in
+ # First match some system type aliases
+ # that might get confused with valid system types.
+ # -solaris* is a basic system type, with this one exception.
+ -solaris1 | -solaris1.*)
+ os=`echo $os | sed -e 's|solaris1|sunos4|'`
+ ;;
+ -solaris)
+ os=-solaris2
+ ;;
+ -svr4*)
+ os=-sysv4
+ ;;
+ -unixware*)
+ os=-sysv4.2uw
+ ;;
+ -gnu/linux*)
+ os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'`
+ ;;
+ # First accept the basic system types.
+ # The portable systems comes first.
+ # Each alternative MUST END IN A *, to match a version number.
+ # -sysv* is not here because it comes later, after sysvr4.
+ -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
+ | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\
+ | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \
+ | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
+ | -aos* \
+ | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
+ | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
+ | -hiux* | -386bsd* | -netbsd* | -openbsd* | -freebsd* | -riscix* \
+ | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
+ | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
+ | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
+ | -chorusos* | -chorusrdb* \
+ | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
+ | -mingw32* | -linux-gnu* | -uxpv* | -beos* | -mpeix* | -udk* \
+ | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
+ | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
+ | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
+ | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
+ | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
+ | -powermax* | -dnix* | -nx6 | -nx7 | -sei*)
+ # Remember, each alternative MUST END IN *, to match a version number.
+ ;;
+ -qnx*)
+ case $basic_machine in
+ x86-* | i*86-*)
+ ;;
+ *)
+ os=-nto$os
+ ;;
+ esac
+ ;;
+ -nto-qnx*)
+ ;;
+ -nto*)
+ os=`echo $os | sed -e 's|nto|nto-qnx|'`
+ ;;
+ -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \
+ | -windows* | -osx | -abug | -netware* | -os9* | -beos* \
+ | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*)
+ ;;
+ -mac*)
+ os=`echo $os | sed -e 's|mac|macos|'`
+ ;;
+ -linux*)
+ os=`echo $os | sed -e 's|linux|linux-gnu|'`
+ ;;
+ -sunos5*)
+ os=`echo $os | sed -e 's|sunos5|solaris2|'`
+ ;;
+ -sunos6*)
+ os=`echo $os | sed -e 's|sunos6|solaris3|'`
+ ;;
+ -opened*)
+ os=-openedition
+ ;;
+ -wince*)
+ os=-wince
+ ;;
+ -osfrose*)
+ os=-osfrose
+ ;;
+ -osf*)
+ os=-osf
+ ;;
+ -utek*)
+ os=-bsd
+ ;;
+ -dynix*)
+ os=-bsd
+ ;;
+ -acis*)
+ os=-aos
+ ;;
+ -atheos*)
+ os=-atheos
+ ;;
+ -386bsd)
+ os=-bsd
+ ;;
+ -ctix* | -uts*)
+ os=-sysv
+ ;;
+ -nova*)
+ os=-rtmk-nova
+ ;;
+ -ns2 )
+ os=-nextstep2
+ ;;
+ -nsk*)
+ os=-nsk
+ ;;
+ # Preserve the version number of sinix5.
+ -sinix5.*)
+ os=`echo $os | sed -e 's|sinix|sysv|'`
+ ;;
+ -sinix*)
+ os=-sysv4
+ ;;
+ -triton*)
+ os=-sysv3
+ ;;
+ -oss*)
+ os=-sysv3
+ ;;
+ -svr4)
+ os=-sysv4
+ ;;
+ -svr3)
+ os=-sysv3
+ ;;
+ -sysvr4)
+ os=-sysv4
+ ;;
+ # This must come after -sysvr4.
+ -sysv*)
+ ;;
+ -ose*)
+ os=-ose
+ ;;
+ -es1800*)
+ os=-ose
+ ;;
+ -xenix)
+ os=-xenix
+ ;;
+ -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+ os=-mint
+ ;;
+ -aros*)
+ os=-aros
+ ;;
+ -kaos*)
+ os=-kaos
+ ;;
+ -none)
+ ;;
+ *)
+ # Get rid of the `-' at the beginning of $os.
+ os=`echo $os | sed 's/[^-]*-//'`
+ echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2
+ exit 1
+ ;;
+esac
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system. Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+case $basic_machine in
+ *-acorn)
+ os=-riscix1.2
+ ;;
+ arm*-rebel)
+ os=-linux
+ ;;
+ arm*-semi)
+ os=-aout
+ ;;
+ c4x-* | tic4x-*)
+ os=-coff
+ ;;
+ # This must come before the *-dec entry.
+ pdp10-*)
+ os=-tops20
+ ;;
+ pdp11-*)
+ os=-none
+ ;;
+ *-dec | vax-*)
+ os=-ultrix4.2
+ ;;
+ m68*-apollo)
+ os=-domain
+ ;;
+ i386-sun)
+ os=-sunos4.0.2
+ ;;
+ m68000-sun)
+ os=-sunos3
+ # This also exists in the configure program, but was not the
+ # default.
+ # os=-sunos4
+ ;;
+ m68*-cisco)
+ os=-aout
+ ;;
+ mips*-cisco)
+ os=-elf
+ ;;
+ mips*-*)
+ os=-elf
+ ;;
+ or32-*)
+ os=-coff
+ ;;
+ *-tti) # must be before sparc entry or we get the wrong os.
+ os=-sysv3
+ ;;
+ sparc-* | *-sun)
+ os=-sunos4.1.1
+ ;;
+ *-be)
+ os=-beos
+ ;;
+ *-ibm)
+ os=-aix
+ ;;
+ *-wec)
+ os=-proelf
+ ;;
+ *-winbond)
+ os=-proelf
+ ;;
+ *-oki)
+ os=-proelf
+ ;;
+ *-hp)
+ os=-hpux
+ ;;
+ *-hitachi)
+ os=-hiux
+ ;;
+ i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+ os=-sysv
+ ;;
+ *-cbm)
+ os=-amigaos
+ ;;
+ *-dg)
+ os=-dgux
+ ;;
+ *-dolphin)
+ os=-sysv3
+ ;;
+ m68k-ccur)
+ os=-rtu
+ ;;
+ m88k-omron*)
+ os=-luna
+ ;;
+ *-next )
+ os=-nextstep
+ ;;
+ *-sequent)
+ os=-ptx
+ ;;
+ *-crds)
+ os=-unos
+ ;;
+ *-ns)
+ os=-genix
+ ;;
+ i370-*)
+ os=-mvs
+ ;;
+ *-next)
+ os=-nextstep3
+ ;;
+ *-gould)
+ os=-sysv
+ ;;
+ *-highlevel)
+ os=-bsd
+ ;;
+ *-encore)
+ os=-bsd
+ ;;
+ *-sgi)
+ os=-irix
+ ;;
+ *-siemens)
+ os=-sysv4
+ ;;
+ *-masscomp)
+ os=-rtu
+ ;;
+ f30[01]-fujitsu | f700-fujitsu)
+ os=-uxpv
+ ;;
+ *-rom68k)
+ os=-coff
+ ;;
+ *-*bug)
+ os=-coff
+ ;;
+ *-apple)
+ os=-macos
+ ;;
+ *-atari*)
+ os=-mint
+ ;;
+ *)
+ os=-none
+ ;;
+esac
+fi
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer. We pick the logical manufacturer.
+vendor=unknown
+case $basic_machine in
+ *-unknown)
+ case $os in
+ -riscix*)
+ vendor=acorn
+ ;;
+ -sunos*)
+ vendor=sun
+ ;;
+ -aix*)
+ vendor=ibm
+ ;;
+ -beos*)
+ vendor=be
+ ;;
+ -hpux*)
+ vendor=hp
+ ;;
+ -mpeix*)
+ vendor=hp
+ ;;
+ -hiux*)
+ vendor=hitachi
+ ;;
+ -unos*)
+ vendor=crds
+ ;;
+ -dgux*)
+ vendor=dg
+ ;;
+ -luna*)
+ vendor=omron
+ ;;
+ -genix*)
+ vendor=ns
+ ;;
+ -mvs* | -opened*)
+ vendor=ibm
+ ;;
+ -ptx*)
+ vendor=sequent
+ ;;
+ -vxsim* | -vxworks* | -windiss*)
+ vendor=wrs
+ ;;
+ -aux*)
+ vendor=apple
+ ;;
+ -hms*)
+ vendor=hitachi
+ ;;
+ -mpw* | -macos*)
+ vendor=apple
+ ;;
+ -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+ vendor=atari
+ ;;
+ -vos*)
+ vendor=stratus
+ ;;
+ esac
+ basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"`
+ ;;
+esac
+
+echo $basic_machine$os
+exit 0
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/configure b/configure
new file mode 100755
index 00000000..0c742a92
--- /dev/null
+++ b/configure
@@ -0,0 +1,4692 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.59 for fish 1.14.0.
+#
+# Report bugs to <axel@liljencrantz.se>.
+#
+# Copyright (C) 2003 Free Software Foundation, Inc.
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## --------------------- ##
+## M4sh Initialization. ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+ set -o posix
+fi
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# Support unset when possible.
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+ as_unset=unset
+else
+ as_unset=false
+fi
+
+
+# Work around bugs in pre-3.0 UWIN ksh.
+$as_unset ENV MAIL MAILPATH
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+for as_var in \
+ LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+ LC_TELEPHONE LC_TIME
+do
+ if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then
+ eval $as_var=C; export $as_var
+ else
+ $as_unset $as_var
+ fi
+done
+
+# Required to use basename.
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename /) >/dev/null 2>&1 && test "X`basename / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+
+# Name of the executable.
+as_me=`$as_basename "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)$' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+ /^X\/\(\/\/\)$/{ s//\1/; q; }
+ /^X\/\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ echo "#! /bin/sh" >conf$$.sh
+ echo "exit 0" >>conf$$.sh
+ chmod +x conf$$.sh
+ if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+ PATH_SEPARATOR=';'
+ else
+ PATH_SEPARATOR=:
+ fi
+ rm -f conf$$.sh
+fi
+
+
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" || {
+ # Find who we are. Look in the path if we contain no path at all
+ # relative or not.
+ case $0 in
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+ ;;
+ esac
+ # We did not find ourselves, most probably we were run as `sh COMMAND'
+ # in which case we are not to be found in the path.
+ if test "x$as_myself" = x; then
+ as_myself=$0
+ fi
+ if test ! -f "$as_myself"; then
+ { echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2
+ { (exit 1); exit 1; }; }
+ fi
+ case $CONFIG_SHELL in
+ '')
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for as_base in sh bash ksh sh5; do
+ case $as_dir in
+ /*)
+ if ("$as_dir/$as_base" -c '
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then
+ $as_unset BASH_ENV || test "${BASH_ENV+set}" != set || { BASH_ENV=; export BASH_ENV; }
+ $as_unset ENV || test "${ENV+set}" != set || { ENV=; export ENV; }
+ CONFIG_SHELL=$as_dir/$as_base
+ export CONFIG_SHELL
+ exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+ fi;;
+ esac
+ done
+done
+;;
+ esac
+
+ # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+ # uniformly replaced by the line number. The first 'sed' inserts a
+ # line-number line before each line; the second 'sed' does the real
+ # work. The second script uses 'N' to pair each line-number line
+ # with the numbered line, and appends trailing '-' during
+ # substitution so that $LINENO is not a special case at line end.
+ # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+ # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-)
+ sed '=' <$as_myself |
+ sed '
+ N
+ s,$,-,
+ : loop
+ s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+ t loop
+ s,-$,,
+ s,^['$as_cr_digits']*\n,,
+ ' >$as_me.lineno &&
+ chmod +x $as_me.lineno ||
+ { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2
+ { (exit 1); exit 1; }; }
+
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensible to this).
+ . ./$as_me.lineno
+ # Exit status is that of the last command.
+ exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+ *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T=' ' ;;
+ *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+ *) ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+ # We could just check for DJGPP; but this test a) works b) is more generic
+ # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+ if test -f conf$$.exe; then
+ # Don't use ln at all; we don't have any links
+ as_ln_s='cp -p'
+ else
+ as_ln_s='ln -s'
+ fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+else
+ as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p=:
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS=" $as_nl"
+
+# CDPATH.
+$as_unset CDPATH
+
+
+# Name of the host.
+# hostname on some systems (SVR3.2, Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+exec 6>&1
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_config_libobj_dir=.
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+# Maximum number of lines to put in a shell here document.
+# This variable seems obsolete. It should probably be removed, and
+# only ac_max_sed_lines should be used.
+: ${ac_max_here_lines=38}
+
+# Identity of this package.
+PACKAGE_NAME='fish'
+PACKAGE_TARNAME='fish'
+PACKAGE_VERSION='1.14.0'
+PACKAGE_STRING='fish 1.14.0'
+PACKAGE_BUGREPORT='axel@liljencrantz.se'
+
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#endif
+#if HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#else
+# if HAVE_STDINT_H
+# include <stdint.h>
+# endif
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS build build_cpu build_vendor build_os host host_cpu host_vendor host_os target target_cpu target_vendor target_os CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT CPP INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA XSEL XSEL_MAN XSEL_MAN_PATH PREFIX fishdir fishfile fishinputfile docdir LIBDIR INCLUDEDIR EGREP CURSESLIB LIBOBJS LTLIBOBJS'
+ac_subst_files=''
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+ac_prev=
+for ac_option
+do
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval "$ac_prev=\$ac_option"
+ ac_prev=
+ continue
+ fi
+
+ ac_optarg=`expr "x$ac_option" : 'x[^=]*=\(.*\)'`
+
+ # Accept the important Cygnus configure options, so we can diagnose typos.
+
+ case $ac_option in
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir=$ac_optarg ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build_alias ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build_alias=$ac_optarg ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file=$ac_optarg ;;
+
+ --config-cache | -C)
+ cache_file=config.cache ;;
+
+ -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+ | --da=*)
+ datadir=$ac_optarg ;;
+
+ -disable-* | --disable-*)
+ ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+ { (exit 1); exit 1; }; }
+ ac_feature=`echo $ac_feature | sed 's/-/_/g'`
+ eval "enable_$ac_feature=no" ;;
+
+ -enable-* | --enable-*)
+ ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+ { (exit 1); exit 1; }; }
+ ac_feature=`echo $ac_feature | sed 's/-/_/g'`
+ case $ac_option in
+ *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "enable_$ac_feature='$ac_optarg'" ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix=$ac_optarg ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he | -h)
+ ac_init_help=long ;;
+ -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+ ac_init_help=recursive ;;
+ -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+ ac_init_help=short ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host_alias ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host_alias=$ac_optarg ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir=$ac_optarg ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir=$ac_optarg ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir=$ac_optarg ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir=$ac_optarg ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst \
+ | --locals | --local | --loca | --loc | --lo)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+ | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+ localstatedir=$ac_optarg ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir=$ac_optarg ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c | -n)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir=$ac_optarg ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix=$ac_optarg ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix=$ac_optarg ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix=$ac_optarg ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name=$ac_optarg ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir=$ac_optarg ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir=$ac_optarg ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site=$ac_optarg ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir=$ac_optarg ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir=$ac_optarg ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target_alias ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target_alias=$ac_optarg ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers | -V)
+ ac_init_version=: ;;
+
+ -with-* | --with-*)
+ ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid package name: $ac_package" >&2
+ { (exit 1); exit 1; }; }
+ ac_package=`echo $ac_package| sed 's/-/_/g'`
+ case $ac_option in
+ *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "with_$ac_package='$ac_optarg'" ;;
+
+ -without-* | --without-*)
+ ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid package name: $ac_package" >&2
+ { (exit 1); exit 1; }; }
+ ac_package=`echo $ac_package | sed 's/-/_/g'`
+ eval "with_$ac_package=no" ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes=$ac_optarg ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries=$ac_optarg ;;
+
+ -*) { echo "$as_me: error: unrecognized option: $ac_option
+Try \`$0 --help' for more information." >&2
+ { (exit 1); exit 1; }; }
+ ;;
+
+ *=*)
+ ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid variable name: $ac_envvar" >&2
+ { (exit 1); exit 1; }; }
+ ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`
+ eval "$ac_envvar='$ac_optarg'"
+ export $ac_envvar ;;
+
+ *)
+ # FIXME: should be removed in autoconf 3.0.
+ echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+ expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+ echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+ : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+ { echo "$as_me: error: missing argument to $ac_option" >&2
+ { (exit 1); exit 1; }; }
+fi
+
+# Be sure to have absolute paths.
+for ac_var in exec_prefix prefix
+do
+ eval ac_val=$`echo $ac_var`
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* | NONE | '' ) ;;
+ *) { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# Be sure to have absolute paths.
+for ac_var in bindir sbindir libexecdir datadir sysconfdir sharedstatedir \
+ localstatedir libdir includedir oldincludedir infodir mandir
+do
+ eval ac_val=$`echo $ac_var`
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* ) ;;
+ *) { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+ if test "x$build_alias" = x; then
+ cross_compiling=maybe
+ echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host.
+ If a cross compiler is detected then cross compile mode will be used." >&2
+ elif test "x$build_alias" != "x$host_alias"; then
+ cross_compiling=yes
+ fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then its parent.
+ ac_confdir=`(dirname "$0") 2>/dev/null ||
+$as_expr X"$0" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$0" : 'X\(//\)[^/]' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$0" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ srcdir=$ac_confdir
+ if test ! -r $srcdir/$ac_unique_file; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+ if test "$ac_srcdir_defaulted" = yes; then
+ { echo "$as_me: error: cannot find sources ($ac_unique_file) in $ac_confdir or .." >&2
+ { (exit 1); exit 1; }; }
+ else
+ { echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2
+ { (exit 1); exit 1; }; }
+ fi
+fi
+(cd $srcdir && test -r ./$ac_unique_file) 2>/dev/null ||
+ { echo "$as_me: error: sources are in $srcdir, but \`cd $srcdir' does not work" >&2
+ { (exit 1); exit 1; }; }
+srcdir=`echo "$srcdir" | sed 's%\([^\\/]\)[\\/]*$%\1%'`
+ac_env_build_alias_set=${build_alias+set}
+ac_env_build_alias_value=$build_alias
+ac_cv_env_build_alias_set=${build_alias+set}
+ac_cv_env_build_alias_value=$build_alias
+ac_env_host_alias_set=${host_alias+set}
+ac_env_host_alias_value=$host_alias
+ac_cv_env_host_alias_set=${host_alias+set}
+ac_cv_env_host_alias_value=$host_alias
+ac_env_target_alias_set=${target_alias+set}
+ac_env_target_alias_value=$target_alias
+ac_cv_env_target_alias_set=${target_alias+set}
+ac_cv_env_target_alias_value=$target_alias
+ac_env_CC_set=${CC+set}
+ac_env_CC_value=$CC
+ac_cv_env_CC_set=${CC+set}
+ac_cv_env_CC_value=$CC
+ac_env_CFLAGS_set=${CFLAGS+set}
+ac_env_CFLAGS_value=$CFLAGS
+ac_cv_env_CFLAGS_set=${CFLAGS+set}
+ac_cv_env_CFLAGS_value=$CFLAGS
+ac_env_LDFLAGS_set=${LDFLAGS+set}
+ac_env_LDFLAGS_value=$LDFLAGS
+ac_cv_env_LDFLAGS_set=${LDFLAGS+set}
+ac_cv_env_LDFLAGS_value=$LDFLAGS
+ac_env_CPPFLAGS_set=${CPPFLAGS+set}
+ac_env_CPPFLAGS_value=$CPPFLAGS
+ac_cv_env_CPPFLAGS_set=${CPPFLAGS+set}
+ac_cv_env_CPPFLAGS_value=$CPPFLAGS
+ac_env_CPP_set=${CPP+set}
+ac_env_CPP_value=$CPP
+ac_cv_env_CPP_set=${CPP+set}
+ac_cv_env_CPP_value=$CPP
+ac_env_docdir_set=${docdir+set}
+ac_env_docdir_value=$docdir
+ac_cv_env_docdir_set=${docdir+set}
+ac_cv_env_docdir_value=$docdir
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat <<_ACEOF
+\`configure' configures fish 1.14.0 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE. See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+ -h, --help display this help and exit
+ --help=short display options specific to this package
+ --help=recursive display the short help of all the included packages
+ -V, --version display version information and exit
+ -q, --quiet, --silent do not print \`checking...' messages
+ --cache-file=FILE cache test results in FILE [disabled]
+ -C, --config-cache alias for \`--cache-file=config.cache'
+ -n, --no-create do not create output files
+ --srcdir=DIR find the sources in DIR [configure dir or \`..']
+
+_ACEOF
+
+ cat <<_ACEOF
+Installation directories:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --datadir=DIR read-only architecture-independent data [PREFIX/share]
+ --sysconfdir=DIR read-only single-machine data [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc [/usr/include]
+ --infodir=DIR info documentation [PREFIX/info]
+ --mandir=DIR man documentation [PREFIX/man]
+_ACEOF
+
+ cat <<\_ACEOF
+
+System types:
+ --build=BUILD configure for building on BUILD [guessed]
+ --host=HOST cross-compile to build programs to run on HOST [BUILD]
+ --target=TARGET configure for building compilers for TARGET [HOST]
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+ case $ac_init_help in
+ short | recursive ) echo "Configuration of fish 1.14.0:";;
+ esac
+ cat <<\_ACEOF
+
+Optional Packages:
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --without-xsel do not build the xsel program needed for X clipboard
+ integration
+
+Some influential environment variables:
+ CC C compiler command
+ CFLAGS C compiler flags
+ LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
+ nonstandard directory <lib dir>
+ CPPFLAGS C/C++ preprocessor flags, e.g. -I<include dir> if you have
+ headers in a nonstandard directory <include dir>
+ CPP C preprocessor
+ docdir Documentation direcotry
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <axel@liljencrantz.se>.
+_ACEOF
+fi
+
+if test "$ac_init_help" = "recursive"; then
+ # If there are subdirs, report their specific --help.
+ ac_popdir=`pwd`
+ for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+ test -d $ac_dir || continue
+ ac_builddir=.
+
+if test "$ac_dir" != .; then
+ ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+ ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+ .) # No --srcdir option. We are building in place.
+ ac_srcdir=.
+ if test -z "$ac_top_builddir"; then
+ ac_top_srcdir=.
+ else
+ ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+ fi ;;
+ [\\/]* | ?:[\\/]* ) # Absolute path.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir ;;
+ *) # Relative path.
+ ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+
+# Do not use `cd foo && pwd` to compute absolute paths, because
+# the directories may not exist.
+case `pwd` in
+.) ac_abs_builddir="$ac_dir";;
+*)
+ case "$ac_dir" in
+ .) ac_abs_builddir=`pwd`;;
+ [\\/]* | ?:[\\/]* ) ac_abs_builddir="$ac_dir";;
+ *) ac_abs_builddir=`pwd`/"$ac_dir";;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_builddir=${ac_top_builddir}.;;
+*)
+ case ${ac_top_builddir}. in
+ .) ac_abs_top_builddir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_top_builddir=${ac_top_builddir}.;;
+ *) ac_abs_top_builddir=$ac_abs_builddir/${ac_top_builddir}.;;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_srcdir=$ac_srcdir;;
+*)
+ case $ac_srcdir in
+ .) ac_abs_srcdir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_srcdir=$ac_srcdir;;
+ *) ac_abs_srcdir=$ac_abs_builddir/$ac_srcdir;;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_srcdir=$ac_top_srcdir;;
+*)
+ case $ac_top_srcdir in
+ .) ac_abs_top_srcdir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_top_srcdir=$ac_top_srcdir;;
+ *) ac_abs_top_srcdir=$ac_abs_builddir/$ac_top_srcdir;;
+ esac;;
+esac
+
+ cd $ac_dir
+ # Check for guested configure; otherwise get Cygnus style configure.
+ if test -f $ac_srcdir/configure.gnu; then
+ echo
+ $SHELL $ac_srcdir/configure.gnu --help=recursive
+ elif test -f $ac_srcdir/configure; then
+ echo
+ $SHELL $ac_srcdir/configure --help=recursive
+ elif test -f $ac_srcdir/configure.ac ||
+ test -f $ac_srcdir/configure.in; then
+ echo
+ $ac_configure --help
+ else
+ echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+ fi
+ cd $ac_popdir
+ done
+fi
+
+test -n "$ac_init_help" && exit 0
+if $ac_init_version; then
+ cat <<\_ACEOF
+fish configure 1.14.0
+generated by GNU Autoconf 2.59
+
+Copyright (C) 2003 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+ exit 0
+fi
+exec 5>config.log
+cat >&5 <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by fish $as_me 1.14.0, which was
+generated by GNU Autoconf 2.59. Invocation command line was
+
+ $ $0 $@
+
+_ACEOF
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown`
+
+/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+hostinfo = `(hostinfo) 2>/dev/null || echo unknown`
+/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown`
+/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ echo "PATH: $as_dir"
+done
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_sep=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+ for ac_arg
+ do
+ case $ac_arg in
+ -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ continue ;;
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*)
+ ac_arg=`echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ case $ac_pass in
+ 1) ac_configure_args0="$ac_configure_args0 '$ac_arg'" ;;
+ 2)
+ ac_configure_args1="$ac_configure_args1 '$ac_arg'"
+ if test $ac_must_keep_next = true; then
+ ac_must_keep_next=false # Got value, back to normal.
+ else
+ case $ac_arg in
+ *=* | --config-cache | -C | -disable-* | --disable-* \
+ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+ | -with-* | --with-* | -without-* | --without-* | --x)
+ case "$ac_configure_args0 " in
+ "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+ esac
+ ;;
+ -* ) ac_must_keep_next=true ;;
+ esac
+ fi
+ ac_configure_args="$ac_configure_args$ac_sep'$ac_arg'"
+ # Get rid of the leading space.
+ ac_sep=" "
+ ;;
+ esac
+ done
+done
+$as_unset ac_configure_args0 || test "${ac_configure_args0+set}" != set || { ac_configure_args0=; export ac_configure_args0; }
+$as_unset ac_configure_args1 || test "${ac_configure_args1+set}" != set || { ac_configure_args1=; export ac_configure_args1; }
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log. We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Be sure not to use single quotes in there, as some shells,
+# such as our DU 5.0 friend, will then `close' the trap.
+trap 'exit_status=$?
+ # Save into config.log some information that might help in debugging.
+ {
+ echo
+
+ cat <<\_ASBOX
+## ---------------- ##
+## Cache variables. ##
+## ---------------- ##
+_ASBOX
+ echo
+ # The following way of writing the cache mishandles newlines in values,
+{
+ (set) 2>&1 |
+ case `(ac_space='"'"' '"'"'; set | grep ac_space) 2>&1` in
+ *ac_space=\ *)
+ sed -n \
+ "s/'"'"'/'"'"'\\\\'"'"''"'"'/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='"'"'\\2'"'"'/p"
+ ;;
+ *)
+ sed -n \
+ "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p"
+ ;;
+ esac;
+}
+ echo
+
+ cat <<\_ASBOX
+## ----------------- ##
+## Output variables. ##
+## ----------------- ##
+_ASBOX
+ echo
+ for ac_var in $ac_subst_vars
+ do
+ eval ac_val=$`echo $ac_var`
+ echo "$ac_var='"'"'$ac_val'"'"'"
+ done | sort
+ echo
+
+ if test -n "$ac_subst_files"; then
+ cat <<\_ASBOX
+## ------------- ##
+## Output files. ##
+## ------------- ##
+_ASBOX
+ echo
+ for ac_var in $ac_subst_files
+ do
+ eval ac_val=$`echo $ac_var`
+ echo "$ac_var='"'"'$ac_val'"'"'"
+ done | sort
+ echo
+ fi
+
+ if test -s confdefs.h; then
+ cat <<\_ASBOX
+## ----------- ##
+## confdefs.h. ##
+## ----------- ##
+_ASBOX
+ echo
+ sed "/^$/d" confdefs.h | sort
+ echo
+ fi
+ test "$ac_signal" != 0 &&
+ echo "$as_me: caught signal $ac_signal"
+ echo "$as_me: exit $exit_status"
+ } >&5
+ rm -f core *.core &&
+ rm -rf conftest* confdefs* conf$$* $ac_clean_files &&
+ exit $exit_status
+ ' 0
+for ac_signal in 1 2 13 15; do
+ trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo >confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+ if test "x$prefix" != xNONE; then
+ CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+ else
+ CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+ fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+ if test -r "$ac_site_file"; then
+ { echo "$as_me:$LINENO: loading site script $ac_site_file" >&5
+echo "$as_me: loading site script $ac_site_file" >&6;}
+ sed 's/^/| /' "$ac_site_file" >&5
+ . "$ac_site_file"
+ fi
+done
+
+if test -r "$cache_file"; then
+ # Some versions of bash will fail to source /dev/null (special
+ # files actually), so we avoid doing that.
+ if test -f "$cache_file"; then
+ { echo "$as_me:$LINENO: loading cache $cache_file" >&5
+echo "$as_me: loading cache $cache_file" >&6;}
+ case $cache_file in
+ [\\/]* | ?:[\\/]* ) . $cache_file;;
+ *) . ./$cache_file;;
+ esac
+ fi
+else
+ { echo "$as_me:$LINENO: creating cache $cache_file" >&5
+echo "$as_me: creating cache $cache_file" >&6;}
+ >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in `(set) 2>&1 |
+ sed -n 's/^ac_env_\([a-zA-Z_0-9]*\)_set=.*/\1/p'`; do
+ eval ac_old_set=\$ac_cv_env_${ac_var}_set
+ eval ac_new_set=\$ac_env_${ac_var}_set
+ eval ac_old_val="\$ac_cv_env_${ac_var}_value"
+ eval ac_new_val="\$ac_env_${ac_var}_value"
+ case $ac_old_set,$ac_new_set in
+ set,)
+ { echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,set)
+ { echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,);;
+ *)
+ if test "x$ac_old_val" != "x$ac_new_val"; then
+ { echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5
+echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+ { echo "$as_me:$LINENO: former value: $ac_old_val" >&5
+echo "$as_me: former value: $ac_old_val" >&2;}
+ { echo "$as_me:$LINENO: current value: $ac_new_val" >&5
+echo "$as_me: current value: $ac_new_val" >&2;}
+ ac_cache_corrupted=:
+ fi;;
+ esac
+ # Pass precious variables to config.status.
+ if test "$ac_new_set" = set; then
+ case $ac_new_val in
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*)
+ ac_arg=$ac_var=`echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+ *) ac_arg=$ac_var=$ac_new_val ;;
+ esac
+ case " $ac_configure_args " in
+ *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
+ *) ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+ esac
+ fi
+done
+if $ac_cache_corrupted; then
+ { echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5
+echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+ { { echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5
+echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ac_aux_dir=
+for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do
+ if test -f $ac_dir/install-sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install-sh -c"
+ break
+ elif test -f $ac_dir/install.sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install.sh -c"
+ break
+ elif test -f $ac_dir/shtool; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/shtool install -c"
+ break
+ fi
+done
+if test -z "$ac_aux_dir"; then
+ { { echo "$as_me:$LINENO: error: cannot find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." >&5
+echo "$as_me: error: cannot find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." >&2;}
+ { (exit 1); exit 1; }; }
+fi
+ac_config_guess="$SHELL $ac_aux_dir/config.guess"
+ac_config_sub="$SHELL $ac_aux_dir/config.sub"
+ac_configure="$SHELL $ac_aux_dir/configure" # This should be Cygnus configure.
+
+# Make sure we can run config.sub.
+$ac_config_sub sun4 >/dev/null 2>&1 ||
+ { { echo "$as_me:$LINENO: error: cannot run $ac_config_sub" >&5
+echo "$as_me: error: cannot run $ac_config_sub" >&2;}
+ { (exit 1); exit 1; }; }
+
+echo "$as_me:$LINENO: checking build system type" >&5
+echo $ECHO_N "checking build system type... $ECHO_C" >&6
+if test "${ac_cv_build+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_cv_build_alias=$build_alias
+test -z "$ac_cv_build_alias" &&
+ ac_cv_build_alias=`$ac_config_guess`
+test -z "$ac_cv_build_alias" &&
+ { { echo "$as_me:$LINENO: error: cannot guess build type; you must specify one" >&5
+echo "$as_me: error: cannot guess build type; you must specify one" >&2;}
+ { (exit 1); exit 1; }; }
+ac_cv_build=`$ac_config_sub $ac_cv_build_alias` ||
+ { { echo "$as_me:$LINENO: error: $ac_config_sub $ac_cv_build_alias failed" >&5
+echo "$as_me: error: $ac_config_sub $ac_cv_build_alias failed" >&2;}
+ { (exit 1); exit 1; }; }
+
+fi
+echo "$as_me:$LINENO: result: $ac_cv_build" >&5
+echo "${ECHO_T}$ac_cv_build" >&6
+build=$ac_cv_build
+build_cpu=`echo $ac_cv_build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
+build_vendor=`echo $ac_cv_build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
+build_os=`echo $ac_cv_build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
+
+
+echo "$as_me:$LINENO: checking host system type" >&5
+echo $ECHO_N "checking host system type... $ECHO_C" >&6
+if test "${ac_cv_host+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_cv_host_alias=$host_alias
+test -z "$ac_cv_host_alias" &&
+ ac_cv_host_alias=$ac_cv_build_alias
+ac_cv_host=`$ac_config_sub $ac_cv_host_alias` ||
+ { { echo "$as_me:$LINENO: error: $ac_config_sub $ac_cv_host_alias failed" >&5
+echo "$as_me: error: $ac_config_sub $ac_cv_host_alias failed" >&2;}
+ { (exit 1); exit 1; }; }
+
+fi
+echo "$as_me:$LINENO: result: $ac_cv_host" >&5
+echo "${ECHO_T}$ac_cv_host" >&6
+host=$ac_cv_host
+host_cpu=`echo $ac_cv_host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
+host_vendor=`echo $ac_cv_host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
+host_os=`echo $ac_cv_host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
+
+
+echo "$as_me:$LINENO: checking target system type" >&5
+echo $ECHO_N "checking target system type... $ECHO_C" >&6
+if test "${ac_cv_target+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_cv_target_alias=$target_alias
+test "x$ac_cv_target_alias" = "x" &&
+ ac_cv_target_alias=$ac_cv_host_alias
+ac_cv_target=`$ac_config_sub $ac_cv_target_alias` ||
+ { { echo "$as_me:$LINENO: error: $ac_config_sub $ac_cv_target_alias failed" >&5
+echo "$as_me: error: $ac_config_sub $ac_cv_target_alias failed" >&2;}
+ { (exit 1); exit 1; }; }
+
+fi
+echo "$as_me:$LINENO: result: $ac_cv_target" >&5
+echo "${ECHO_T}$ac_cv_target" >&6
+target=$ac_cv_target
+target_cpu=`echo $ac_cv_target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
+target_vendor=`echo $ac_cv_target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
+target_os=`echo $ac_cv_target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
+
+
+# The aliases save the names the user supplied, while $host etc.
+# will get canonicalized.
+test -n "$target_alias" &&
+ test "$program_prefix$program_suffix$program_transform_name" = \
+ NONENONEs,x,x, &&
+ program_prefix=${target_alias}-
+
+if test $target_cpu = powerpc; then
+
+cat >>confdefs.h <<\_ACEOF
+#define TPUTS_KLUDGE 1
+_ACEOF
+
+fi
+
+
+cat >>confdefs.h <<_ACEOF
+#define CPU L"$target_cpu"
+_ACEOF
+
+
+ ac_config_headers="$ac_config_headers config.h"
+
+
+# Checks for programs.
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}gcc"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+ ac_ct_CC=$CC
+ # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="gcc"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+ CC=$ac_ct_CC
+else
+ CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}cc"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+ ac_ct_CC=$CC
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="cc"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+ CC=$ac_ct_CC
+else
+ CC="$ac_cv_prog_CC"
+fi
+
+fi
+if test -z "$CC"; then
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+ ac_prog_rejected=yes
+ continue
+ fi
+ ac_cv_prog_CC="cc"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+if test $ac_prog_rejected = yes; then
+ # We found a bogon in the path, so make sure we never use it.
+ set dummy $ac_cv_prog_CC
+ shift
+ if test $# != 0; then
+ # We chose a different compiler from the bogus one.
+ # However, it has the same basename, so the bogon will be chosen
+ # first if we set CC to just the basename; use the full file name.
+ shift
+ ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+ fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+fi
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in cl
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+ test -n "$CC" && break
+ done
+fi
+if test -z "$CC"; then
+ ac_ct_CC=$CC
+ for ac_prog in cl
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="$ac_prog"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+ test -n "$ac_ct_CC" && break
+done
+
+ CC=$ac_ct_CC
+fi
+
+fi
+
+
+test -z "$CC" && { { echo "$as_me:$LINENO: error: no acceptable C compiler found in \$PATH
+See \`config.log' for more details." >&5
+echo "$as_me: error: no acceptable C compiler found in \$PATH
+See \`config.log' for more details." >&2;}
+ { (exit 1); exit 1; }; }
+
+# Provide some information about the compiler.
+echo "$as_me:$LINENO:" \
+ "checking for C compiler version" >&5
+ac_compiler=`set X $ac_compile; echo $2`
+{ (eval echo "$as_me:$LINENO: \"$ac_compiler --version </dev/null >&5\"") >&5
+ (eval $ac_compiler --version </dev/null >&5) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }
+{ (eval echo "$as_me:$LINENO: \"$ac_compiler -v </dev/null >&5\"") >&5
+ (eval $ac_compiler -v </dev/null >&5) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }
+{ (eval echo "$as_me:$LINENO: \"$ac_compiler -V </dev/null >&5\"") >&5
+ (eval $ac_compiler -V </dev/null >&5) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }
+
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+echo "$as_me:$LINENO: checking for C compiler default output file name" >&5
+echo $ECHO_N "checking for C compiler default output file name... $ECHO_C" >&6
+ac_link_default=`echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+if { (eval echo "$as_me:$LINENO: \"$ac_link_default\"") >&5
+ (eval $ac_link_default) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; then
+ # Find the output, starting from the most likely. This scheme is
+# not robust to junk in `.', hence go to wildcards (a.*) only as a last
+# resort.
+
+# Be careful to initialize this variable, since it used to be cached.
+# Otherwise an old cache value of `no' led to `EXEEXT = no' in a Makefile.
+ac_cv_exeext=
+# b.out is created by i960 compilers.
+for ac_file in a_out.exe a.exe conftest.exe a.out conftest a.* conftest.* b.out
+do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.o | *.obj )
+ ;;
+ conftest.$ac_ext )
+ # This is the source file.
+ ;;
+ [ab].out )
+ # We found the default executable, but exeext='' is most
+ # certainly right.
+ break;;
+ *.* )
+ ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ # FIXME: I believe we export ac_cv_exeext for Libtool,
+ # but it would be cool to find out if it's true. Does anybody
+ # maintain Libtool? --akim.
+ export ac_cv_exeext
+ break;;
+ * )
+ break;;
+ esac
+done
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { echo "$as_me:$LINENO: error: C compiler cannot create executables
+See \`config.log' for more details." >&5
+echo "$as_me: error: C compiler cannot create executables
+See \`config.log' for more details." >&2;}
+ { (exit 77); exit 77; }; }
+fi
+
+ac_exeext=$ac_cv_exeext
+echo "$as_me:$LINENO: result: $ac_file" >&5
+echo "${ECHO_T}$ac_file" >&6
+
+# Check the compiler produces executables we can run. If not, either
+# the compiler is broken, or we cross compile.
+echo "$as_me:$LINENO: checking whether the C compiler works" >&5
+echo $ECHO_N "checking whether the C compiler works... $ECHO_C" >&6
+# FIXME: These cross compiler hacks should be removed for Autoconf 3.0
+# If not cross compiling, check that we can run a simple program.
+if test "$cross_compiling" != yes; then
+ if { ac_try='./$ac_file'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ cross_compiling=no
+ else
+ if test "$cross_compiling" = maybe; then
+ cross_compiling=yes
+ else
+ { { echo "$as_me:$LINENO: error: cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details." >&2;}
+ { (exit 1); exit 1; }; }
+ fi
+ fi
+fi
+echo "$as_me:$LINENO: result: yes" >&5
+echo "${ECHO_T}yes" >&6
+
+rm -f a.out a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+# Check the compiler produces executables we can run. If not, either
+# the compiler is broken, or we cross compile.
+echo "$as_me:$LINENO: checking whether we are cross compiling" >&5
+echo $ECHO_N "checking whether we are cross compiling... $ECHO_C" >&6
+echo "$as_me:$LINENO: result: $cross_compiling" >&5
+echo "${ECHO_T}$cross_compiling" >&6
+
+echo "$as_me:$LINENO: checking for suffix of executables" >&5
+echo $ECHO_N "checking for suffix of executables... $ECHO_C" >&6
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; then
+ # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.o | *.obj ) ;;
+ *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ export ac_cv_exeext
+ break;;
+ * ) break;;
+ esac
+done
+else
+ { { echo "$as_me:$LINENO: error: cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details." >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+rm -f conftest$ac_cv_exeext
+echo "$as_me:$LINENO: result: $ac_cv_exeext" >&5
+echo "${ECHO_T}$ac_cv_exeext" >&6
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+echo "$as_me:$LINENO: checking for suffix of object files" >&5
+echo $ECHO_N "checking for suffix of object files... $ECHO_C" >&6
+if test "${ac_cv_objext+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; then
+ for ac_file in `(ls conftest.o conftest.obj; ls conftest.*) 2>/dev/null`; do
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg ) ;;
+ *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+ break;;
+ esac
+done
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { echo "$as_me:$LINENO: error: cannot compute suffix of object files: cannot compile
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute suffix of object files: cannot compile
+See \`config.log' for more details." >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: $ac_cv_objext" >&5
+echo "${ECHO_T}$ac_cv_objext" >&6
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+echo "$as_me:$LINENO: checking whether we are using the GNU C compiler" >&5
+echo $ECHO_N "checking whether we are using the GNU C compiler... $ECHO_C" >&6
+if test "${ac_cv_c_compiler_gnu+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+int
+main ()
+{
+#ifndef __GNUC__
+ choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_compiler_gnu=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_compiler_gnu=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+echo "$as_me:$LINENO: result: $ac_cv_c_compiler_gnu" >&5
+echo "${ECHO_T}$ac_cv_c_compiler_gnu" >&6
+GCC=`test $ac_compiler_gnu = yes && echo yes`
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+CFLAGS="-g"
+echo "$as_me:$LINENO: checking whether $CC accepts -g" >&5
+echo $ECHO_N "checking whether $CC accepts -g... $ECHO_C" >&6
+if test "${ac_cv_prog_cc_g+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_prog_cc_g=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_prog_cc_g=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: $ac_cv_prog_cc_g" >&5
+echo "${ECHO_T}$ac_cv_prog_cc_g" >&6
+if test "$ac_test_CFLAGS" = set; then
+ CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+ if test "$GCC" = yes; then
+ CFLAGS="-g -O2"
+ else
+ CFLAGS="-g"
+ fi
+else
+ if test "$GCC" = yes; then
+ CFLAGS="-O2"
+ else
+ CFLAGS=
+ fi
+fi
+echo "$as_me:$LINENO: checking for $CC option to accept ANSI C" >&5
+echo $ECHO_N "checking for $CC option to accept ANSI C... $ECHO_C" >&6
+if test "${ac_cv_prog_cc_stdc+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_cv_prog_cc_stdc=no
+ac_save_CC=$CC
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+ char **p;
+ int i;
+{
+ return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+ char *s;
+ va_list v;
+ va_start (v,p);
+ s = g (p, va_arg (v,int));
+ va_end (v);
+ return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has
+ function prototypes and stuff, but not '\xHH' hex character constants.
+ These don't provoke an error unfortunately, instead are silently treated
+ as 'x'. The following induces an error, until -std1 is added to get
+ proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an
+ array size at least. It's necessary to write '\x00'==0 to get something
+ that's true only with -std1. */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1];
+ ;
+ return 0;
+}
+_ACEOF
+# Don't try gcc -ansi; that turns off useful extensions and
+# breaks some systems' header files.
+# AIX -qlanglvl=ansi
+# Ultrix and OSF/1 -std1
+# HP-UX 10.20 and later -Ae
+# HP-UX older versions -Aa -D_HPUX_SOURCE
+# SVR4 -Xc -D__EXTENSIONS__
+for ac_arg in "" -qlanglvl=ansi -std1 -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+ CC="$ac_save_CC $ac_arg"
+ rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_prog_cc_stdc=$ac_arg
+break
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+fi
+rm -f conftest.err conftest.$ac_objext
+done
+rm -f conftest.$ac_ext conftest.$ac_objext
+CC=$ac_save_CC
+
+fi
+
+case "x$ac_cv_prog_cc_stdc" in
+ x|xno)
+ echo "$as_me:$LINENO: result: none needed" >&5
+echo "${ECHO_T}none needed" >&6 ;;
+ *)
+ echo "$as_me:$LINENO: result: $ac_cv_prog_cc_stdc" >&5
+echo "${ECHO_T}$ac_cv_prog_cc_stdc" >&6
+ CC="$CC $ac_cv_prog_cc_stdc" ;;
+esac
+
+# Some people use a C++ compiler to compile C. Since we use `exit',
+# in C++ we need to declare it. In case someone uses the same compiler
+# for both compiling C and C++ we need to have the C++ compiler decide
+# the declaration of exit, since it's the most demanding environment.
+cat >conftest.$ac_ext <<_ACEOF
+#ifndef __cplusplus
+ choke me
+#endif
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ for ac_declaration in \
+ '' \
+ 'extern "C" void std::exit (int) throw (); using std::exit;' \
+ 'extern "C" void std::exit (int); using std::exit;' \
+ 'extern "C" void exit (int) throw ();' \
+ 'extern "C" void exit (int);' \
+ 'void exit (int);'
+do
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+$ac_declaration
+#include <stdlib.h>
+int
+main ()
+{
+exit (42);
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ :
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+continue
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+$ac_declaration
+int
+main ()
+{
+exit (42);
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ break
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+done
+rm -f conftest*
+if test -n "$ac_declaration"; then
+ echo '#ifdef __cplusplus' >>confdefs.h
+ echo $ac_declaration >>confdefs.h
+ echo '#endif' >>confdefs.h
+fi
+
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+echo "$as_me:$LINENO: checking how to run the C preprocessor" >&5
+echo $ECHO_N "checking how to run the C preprocessor... $ECHO_C" >&6
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+ CPP=
+fi
+if test -z "$CPP"; then
+ if test "${ac_cv_prog_CPP+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ # Double quotes because CPP needs to be expanded
+ for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+ do
+ ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ # <limits.h> exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+ Syntax error
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+ (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } >/dev/null; then
+ if test -s conftest.err; then
+ ac_cpp_err=$ac_c_preproc_warn_flag
+ ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+ else
+ ac_cpp_err=
+ fi
+else
+ ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+ :
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether non-existent headers
+ # can be detected and how.
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+ (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } >/dev/null; then
+ if test -s conftest.err; then
+ ac_cpp_err=$ac_c_preproc_warn_flag
+ ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+ else
+ ac_cpp_err=
+ fi
+else
+ ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+ # Broken: success on invalid input.
+continue
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then
+ break
+fi
+
+ done
+ ac_cv_prog_CPP=$CPP
+
+fi
+ CPP=$ac_cv_prog_CPP
+else
+ ac_cv_prog_CPP=$CPP
+fi
+echo "$as_me:$LINENO: result: $CPP" >&5
+echo "${ECHO_T}$CPP" >&6
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ # <limits.h> exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+ Syntax error
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+ (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } >/dev/null; then
+ if test -s conftest.err; then
+ ac_cpp_err=$ac_c_preproc_warn_flag
+ ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+ else
+ ac_cpp_err=
+ fi
+else
+ ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+ :
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether non-existent headers
+ # can be detected and how.
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+ (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } >/dev/null; then
+ if test -s conftest.err; then
+ ac_cpp_err=$ac_c_preproc_warn_flag
+ ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+ else
+ ac_cpp_err=
+ fi
+else
+ ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+ # Broken: success on invalid input.
+continue
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then
+ :
+else
+ { { echo "$as_me:$LINENO: error: C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details." >&5
+echo "$as_me: error: C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details." >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+# Find a good install program. We prefer a C program (faster),
+# so one script is as good as another. But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# OS/2's system install, which has a completely different semantic
+# ./install, which can be erroneously created by make from ./install.sh.
+echo "$as_me:$LINENO: checking for a BSD-compatible install" >&5
+echo $ECHO_N "checking for a BSD-compatible install... $ECHO_C" >&6
+if test -z "$INSTALL"; then
+if test "${ac_cv_path_install+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in
+ ./ | .// | /cC/* | \
+ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+ ?:\\/os2\\/install\\/* | ?:\\/OS2\\/INSTALL\\/* | \
+ /usr/ucb/* ) ;;
+ *)
+ # OSF1 and SCO ODT 3.0 have their own names for install.
+ # Don't use installbsd from OSF since it installs stuff as root
+ # by default.
+ for ac_prog in ginstall scoinst install; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then
+ if test $ac_prog = install &&
+ grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # AIX install. It has an incompatible calling convention.
+ :
+ elif test $ac_prog = install &&
+ grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # program-specific install script used by HP pwplus--don't use.
+ :
+ else
+ ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+ break 3
+ fi
+ fi
+ done
+ done
+ ;;
+esac
+done
+
+
+fi
+ if test "${ac_cv_path_install+set}" = set; then
+ INSTALL=$ac_cv_path_install
+ else
+ # As a last resort, use the slow shell script. We don't cache a
+ # path for INSTALL within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the path is relative.
+ INSTALL=$ac_install_sh
+ fi
+fi
+echo "$as_me:$LINENO: result: $INSTALL" >&5
+echo "${ECHO_T}$INSTALL" >&6
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+#AC_ISC_POSIX
+#AC_PROG_MAKE_SET
+
+# Optionally drop xsel
+
+# Check whether --with-xsel or --without-xsel was given.
+if test "${with_xsel+set}" = set; then
+ withval="$with_xsel"
+ xsel=$withval
+else
+ xsel=with_xsel
+fi;
+
+if [ "$xsel" = "with_xsel" ]; then
+ XSEL=xsel-0.9.6/xsel
+
+ XSEL_MAN=xsel.1x
+
+ XSEL_MAN_PATH=xsel-0.9.6/xsel.1x
+
+else
+ XSEL=
+
+ XSEL_MAN=
+
+ XSEL_MAN_PATH=
+
+fi
+
+if [ "$prefix" = NONE ]; then
+
+cat >>confdefs.h <<_ACEOF
+#define PREFIX L"/usr/local"
+_ACEOF
+
+ PREFIX=/usr/local
+
+ sysconfdir=/etc
+
+ export prefix=/usr/local
+else
+
+cat >>confdefs.h <<_ACEOF
+#define PREFIX L"$prefix"
+_ACEOF
+
+ PREFIX=$prefix
+
+ sysconfdir=/etc
+
+fi
+
+if echo $prefix | grep \^$HOME >/dev/null; then
+ sysconfdir=$HOME
+
+ fishdir=/.fish.d
+
+ fishfile=/.fish
+
+ fishinputfile=/.fish_inputrc
+
+ { echo "$as_me:$LINENO: \"Install in $HOME\"" >&5
+echo "$as_me: \"Install in $HOME\"" >&6;}
+else
+ fishdir=/fish.d
+
+ fishfile=/fish
+
+ fishinputfile=/fish_inputrc
+
+fi
+
+
+
+if test -z $docdir; then
+ docdir=$datadir/doc/fish
+
+fi
+
+
+cat >>confdefs.h <<_ACEOF
+#define DOCDIR L"$(eval echo $docdir)"
+_ACEOF
+
+
+# See if Linux procfs is present
+echo "$as_me:$LINENO: checking for /proc/self/stat" >&5
+echo $ECHO_N "checking for /proc/self/stat... $ECHO_C" >&6
+if test "${ac_cv_file__proc_self_stat+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ test "$cross_compiling" = yes &&
+ { { echo "$as_me:$LINENO: error: cannot check for file existence when cross compiling" >&5
+echo "$as_me: error: cannot check for file existence when cross compiling" >&2;}
+ { (exit 1); exit 1; }; }
+if test -r "/proc/self/stat"; then
+ ac_cv_file__proc_self_stat=yes
+else
+ ac_cv_file__proc_self_stat=no
+fi
+fi
+echo "$as_me:$LINENO: result: $ac_cv_file__proc_self_stat" >&5
+echo "${ECHO_T}$ac_cv_file__proc_self_stat" >&6
+if test $ac_cv_file__proc_self_stat = yes; then
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE__PROC_SELF_STAT 1
+_ACEOF
+
+
+fi
+
+
+# See if NetBSD pkgsrc is installed
+echo "$as_me:$LINENO: checking for /usr/pkg/lib" >&5
+echo $ECHO_N "checking for /usr/pkg/lib... $ECHO_C" >&6
+if test "${ac_cv_file__usr_pkg_lib+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ test "$cross_compiling" = yes &&
+ { { echo "$as_me:$LINENO: error: cannot check for file existence when cross compiling" >&5
+echo "$as_me: error: cannot check for file existence when cross compiling" >&2;}
+ { (exit 1); exit 1; }; }
+if test -r "/usr/pkg/lib"; then
+ ac_cv_file__usr_pkg_lib=yes
+else
+ ac_cv_file__usr_pkg_lib=no
+fi
+fi
+echo "$as_me:$LINENO: result: $ac_cv_file__usr_pkg_lib" >&5
+echo "${ECHO_T}$ac_cv_file__usr_pkg_lib" >&6
+if test $ac_cv_file__usr_pkg_lib = yes; then
+ LIBDIR=-L/usr/pkg/lib\ -R/usr/pkg/lib
+
+fi
+
+echo "$as_me:$LINENO: checking for /usr/pkg/include" >&5
+echo $ECHO_N "checking for /usr/pkg/include... $ECHO_C" >&6
+if test "${ac_cv_file__usr_pkg_include+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ test "$cross_compiling" = yes &&
+ { { echo "$as_me:$LINENO: error: cannot check for file existence when cross compiling" >&5
+echo "$as_me: error: cannot check for file existence when cross compiling" >&2;}
+ { (exit 1); exit 1; }; }
+if test -r "/usr/pkg/include"; then
+ ac_cv_file__usr_pkg_include=yes
+else
+ ac_cv_file__usr_pkg_include=no
+fi
+fi
+echo "$as_me:$LINENO: result: $ac_cv_file__usr_pkg_include" >&5
+echo "${ECHO_T}$ac_cv_file__usr_pkg_include" >&6
+if test $ac_cv_file__usr_pkg_include = yes; then
+ INCLUDEDIR=-I/usr/pkg/include
+
+fi
+
+
+
+
+
+
+
+for ac_func in wprintf futimes wcwidth wcswidth
+do
+as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh`
+echo "$as_me:$LINENO: checking for $ac_func" >&5
+echo $ECHO_N "checking for $ac_func... $ECHO_C" >&6
+if eval "test \"\${$as_ac_var+set}\" = set"; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+/* Define $ac_func to an innocuous variant, in case <limits.h> declares $ac_func.
+ For example, HP-UX 11i <limits.h> declares gettimeofday. */
+#define $ac_func innocuous_$ac_func
+
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func (); below.
+ Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ <limits.h> exists even on freestanding compilers. */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $ac_func
+
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func ();
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+char (*f) () = $ac_func;
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+int
+main ()
+{
+return f != $ac_func;
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest$ac_exeext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ eval "$as_ac_var=yes"
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+eval "$as_ac_var=no"
+fi
+rm -f conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_var'}'`" >&5
+echo "${ECHO_T}`eval echo '${'$as_ac_var'}'`" >&6
+if test `eval echo '${'$as_ac_var'}'` = yes; then
+ cat >>confdefs.h <<_ACEOF
+#define `echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+done
+
+
+echo "$as_me:$LINENO: checking for egrep" >&5
+echo $ECHO_N "checking for egrep... $ECHO_C" >&6
+if test "${ac_cv_prog_egrep+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if echo a | (grep -E '(a|b)') >/dev/null 2>&1
+ then ac_cv_prog_egrep='grep -E'
+ else ac_cv_prog_egrep='egrep'
+ fi
+fi
+echo "$as_me:$LINENO: result: $ac_cv_prog_egrep" >&5
+echo "${ECHO_T}$ac_cv_prog_egrep" >&6
+ EGREP=$ac_cv_prog_egrep
+
+
+echo "$as_me:$LINENO: checking for ANSI C header files" >&5
+echo $ECHO_N "checking for ANSI C header files... $ECHO_C" >&6
+if test "${ac_cv_header_stdc+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_header_stdc=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_header_stdc=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+ # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "memchr" >/dev/null 2>&1; then
+ :
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "free" >/dev/null 2>&1; then
+ :
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+ if test "$cross_compiling" = yes; then
+ :
+else
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <ctype.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+ (('a' <= (c) && (c) <= 'i') \
+ || ('j' <= (c) && (c) <= 'r') \
+ || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ if (XOR (islower (i), ISLOWER (i))
+ || toupper (i) != TOUPPER (i))
+ exit(2);
+ exit (0);
+}
+_ACEOF
+rm -f conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } && { ac_try='./conftest$ac_exeext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ :
+else
+ echo "$as_me: program exited with status $ac_status" >&5
+echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+( exit $ac_status )
+ac_cv_header_stdc=no
+fi
+rm -f core *.core gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext
+fi
+fi
+fi
+echo "$as_me:$LINENO: result: $ac_cv_header_stdc" >&5
+echo "${ECHO_T}$ac_cv_header_stdc" >&6
+if test $ac_cv_header_stdc = yes; then
+
+cat >>confdefs.h <<\_ACEOF
+#define STDC_HEADERS 1
+_ACEOF
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+
+
+
+
+
+
+
+
+
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+ inttypes.h stdint.h unistd.h
+do
+as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh`
+echo "$as_me:$LINENO: checking for $ac_header" >&5
+echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6
+if eval "test \"\${$as_ac_Header+set}\" = set"; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+$ac_includes_default
+
+#include <$ac_header>
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ eval "$as_ac_Header=yes"
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+eval "$as_ac_Header=no"
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5
+echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6
+if test `eval echo '${'$as_ac_Header'}'` = yes; then
+ cat >>confdefs.h <<_ACEOF
+#define `echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+
+
+for ac_header in getopt.h termio.h
+do
+as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh`
+if eval "test \"\${$as_ac_Header+set}\" = set"; then
+ echo "$as_me:$LINENO: checking for $ac_header" >&5
+echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6
+if eval "test \"\${$as_ac_Header+set}\" = set"; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+fi
+echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5
+echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6
+else
+ # Is the header compilable?
+echo "$as_me:$LINENO: checking $ac_header usability" >&5
+echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+$ac_includes_default
+#include <$ac_header>
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_header_compiler=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_header_compiler=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+echo "$as_me:$LINENO: result: $ac_header_compiler" >&5
+echo "${ECHO_T}$ac_header_compiler" >&6
+
+# Is the header present?
+echo "$as_me:$LINENO: checking $ac_header presence" >&5
+echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <$ac_header>
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+ (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } >/dev/null; then
+ if test -s conftest.err; then
+ ac_cpp_err=$ac_c_preproc_warn_flag
+ ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+ else
+ ac_cpp_err=
+ fi
+else
+ ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+ ac_header_preproc=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_header_preproc=no
+fi
+rm -f conftest.err conftest.$ac_ext
+echo "$as_me:$LINENO: result: $ac_header_preproc" >&5
+echo "${ECHO_T}$ac_header_preproc" >&6
+
+# So? What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in
+ yes:no: )
+ { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5
+echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5
+echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;}
+ ac_header_preproc=yes
+ ;;
+ no:yes:* )
+ { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5
+echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5
+echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5
+echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5
+echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5
+echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5
+echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;}
+ (
+ cat <<\_ASBOX
+## ----------------------------------- ##
+## Report this to axel@liljencrantz.se ##
+## ----------------------------------- ##
+_ASBOX
+ ) |
+ sed "s/^/$as_me: WARNING: /" >&2
+ ;;
+esac
+echo "$as_me:$LINENO: checking for $ac_header" >&5
+echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6
+if eval "test \"\${$as_ac_Header+set}\" = set"; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ eval "$as_ac_Header=\$ac_header_preproc"
+fi
+echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5
+echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6
+
+fi
+if test `eval echo '${'$as_ac_Header'}'` = yes; then
+ cat >>confdefs.h <<_ACEOF
+#define `echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+
+
+# Check if we have ncurses, and use it rather than curses if possible.
+
+for ac_header in ncurses.h
+do
+as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh`
+if eval "test \"\${$as_ac_Header+set}\" = set"; then
+ echo "$as_me:$LINENO: checking for $ac_header" >&5
+echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6
+if eval "test \"\${$as_ac_Header+set}\" = set"; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+fi
+echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5
+echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6
+else
+ # Is the header compilable?
+echo "$as_me:$LINENO: checking $ac_header usability" >&5
+echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+$ac_includes_default
+#include <$ac_header>
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_header_compiler=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_header_compiler=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+echo "$as_me:$LINENO: result: $ac_header_compiler" >&5
+echo "${ECHO_T}$ac_header_compiler" >&6
+
+# Is the header present?
+echo "$as_me:$LINENO: checking $ac_header presence" >&5
+echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <$ac_header>
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+ (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } >/dev/null; then
+ if test -s conftest.err; then
+ ac_cpp_err=$ac_c_preproc_warn_flag
+ ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+ else
+ ac_cpp_err=
+ fi
+else
+ ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+ ac_header_preproc=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_header_preproc=no
+fi
+rm -f conftest.err conftest.$ac_ext
+echo "$as_me:$LINENO: result: $ac_header_preproc" >&5
+echo "${ECHO_T}$ac_header_preproc" >&6
+
+# So? What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in
+ yes:no: )
+ { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5
+echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5
+echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;}
+ ac_header_preproc=yes
+ ;;
+ no:yes:* )
+ { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5
+echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5
+echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5
+echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5
+echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5
+echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;}
+ { echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5
+echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;}
+ (
+ cat <<\_ASBOX
+## ----------------------------------- ##
+## Report this to axel@liljencrantz.se ##
+## ----------------------------------- ##
+_ASBOX
+ ) |
+ sed "s/^/$as_me: WARNING: /" >&2
+ ;;
+esac
+echo "$as_me:$LINENO: checking for $ac_header" >&5
+echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6
+if eval "test \"\${$as_ac_Header+set}\" = set"; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ eval "$as_ac_Header=\$ac_header_preproc"
+fi
+echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5
+echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6
+
+fi
+if test `eval echo '${'$as_ac_Header'}'` = yes; then
+ cat >>confdefs.h <<_ACEOF
+#define `echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+ CURSESLIB=ncurses
+ cat >>confdefs.h <<\_ACEOF
+#define HAVE_NCURSES_H 1
+_ACEOF
+
+else
+ CURSESLIB=curses
+
+fi
+
+done
+
+
+#Force use of ncurses if it is avialable via NetBSD pkgsrc. This is an
+#ugly kludge to force NetBSD to use ncurses, since NetBSDs own version
+#does not properly support terminfo.
+echo "$as_me:$LINENO: checking for /usr/pkg/include/ncurses.h" >&5
+echo $ECHO_N "checking for /usr/pkg/include/ncurses.h... $ECHO_C" >&6
+if test "${ac_cv_file__usr_pkg_include_ncurses_h+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ test "$cross_compiling" = yes &&
+ { { echo "$as_me:$LINENO: error: cannot check for file existence when cross compiling" >&5
+echo "$as_me: error: cannot check for file existence when cross compiling" >&2;}
+ { (exit 1); exit 1; }; }
+if test -r "/usr/pkg/include/ncurses.h"; then
+ ac_cv_file__usr_pkg_include_ncurses_h=yes
+else
+ ac_cv_file__usr_pkg_include_ncurses_h=no
+fi
+fi
+echo "$as_me:$LINENO: result: $ac_cv_file__usr_pkg_include_ncurses_h" >&5
+echo "${ECHO_T}$ac_cv_file__usr_pkg_include_ncurses_h" >&6
+if test $ac_cv_file__usr_pkg_include_ncurses_h = yes; then
+ CURSESLIB=ncurses
+ cat >>confdefs.h <<\_ACEOF
+#define HAVE_NCURSES_H 1
+_ACEOF
+
+fi
+
+
+ ac_config_files="$ac_config_files Makefile fish.spec doc_src/fish.1 doc_src/Doxyfile init/fish"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems. If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, don't put newlines in cache variables' values.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+{
+ (set) 2>&1 |
+ case `(ac_space=' '; set | grep ac_space) 2>&1` in
+ *ac_space=\ *)
+ # `set' does not quote correctly, so add quotes (double-quote
+ # substitution turns \\\\ into \\, and sed turns \\ into \).
+ sed -n \
+ "s/'/'\\\\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+ ;;
+ *)
+ # `set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n \
+ "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p"
+ ;;
+ esac;
+} |
+ sed '
+ t clear
+ : clear
+ s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+ t end
+ /^ac_cv_env/!s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+ : end' >>confcache
+if diff $cache_file confcache >/dev/null 2>&1; then :; else
+ if test -w $cache_file; then
+ test "x$cache_file" != "x/dev/null" && echo "updating cache $cache_file"
+ cat confcache >$cache_file
+ else
+ echo "not updating unwritable cache $cache_file"
+ fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# VPATH may cause trouble with some makes, so we remove $(srcdir),
+# ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=/{
+s/:*\$(srcdir):*/:/;
+s/:*\${srcdir}:*/:/;
+s/:*@srcdir@:*/:/;
+s/^\([^=]*=[ ]*\):*/\1/;
+s/:*$//;
+s/^[^=]*=[ ]*$//;
+}'
+fi
+
+DEFS=-DHAVE_CONFIG_H
+
+ac_libobjs=
+ac_ltlibobjs=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+ # 1. Remove the extension, and $U if already installed.
+ ac_i=`echo "$ac_i" |
+ sed 's/\$U\././;s/\.o$//;s/\.obj$//'`
+ # 2. Add them.
+ ac_libobjs="$ac_libobjs $ac_i\$U.$ac_objext"
+ ac_ltlibobjs="$ac_ltlibobjs $ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+
+: ${CONFIG_STATUS=./config.status}
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5
+echo "$as_me: creating $CONFIG_STATUS" >&6;}
+cat >$CONFIG_STATUS <<_ACEOF
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+SHELL=\${CONFIG_SHELL-$SHELL}
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+## --------------------- ##
+## M4sh Initialization. ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+ set -o posix
+fi
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# Support unset when possible.
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+ as_unset=unset
+else
+ as_unset=false
+fi
+
+
+# Work around bugs in pre-3.0 UWIN ksh.
+$as_unset ENV MAIL MAILPATH
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+for as_var in \
+ LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+ LC_TELEPHONE LC_TIME
+do
+ if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then
+ eval $as_var=C; export $as_var
+ else
+ $as_unset $as_var
+ fi
+done
+
+# Required to use basename.
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename /) >/dev/null 2>&1 && test "X`basename / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+
+# Name of the executable.
+as_me=`$as_basename "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)$' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+ /^X\/\(\/\/\)$/{ s//\1/; q; }
+ /^X\/\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ echo "#! /bin/sh" >conf$$.sh
+ echo "exit 0" >>conf$$.sh
+ chmod +x conf$$.sh
+ if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+ PATH_SEPARATOR=';'
+ else
+ PATH_SEPARATOR=:
+ fi
+ rm -f conf$$.sh
+fi
+
+
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" || {
+ # Find who we are. Look in the path if we contain no path at all
+ # relative or not.
+ case $0 in
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+ ;;
+ esac
+ # We did not find ourselves, most probably we were run as `sh COMMAND'
+ # in which case we are not to be found in the path.
+ if test "x$as_myself" = x; then
+ as_myself=$0
+ fi
+ if test ! -f "$as_myself"; then
+ { { echo "$as_me:$LINENO: error: cannot find myself; rerun with an absolute path" >&5
+echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2;}
+ { (exit 1); exit 1; }; }
+ fi
+ case $CONFIG_SHELL in
+ '')
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for as_base in sh bash ksh sh5; do
+ case $as_dir in
+ /*)
+ if ("$as_dir/$as_base" -c '
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then
+ $as_unset BASH_ENV || test "${BASH_ENV+set}" != set || { BASH_ENV=; export BASH_ENV; }
+ $as_unset ENV || test "${ENV+set}" != set || { ENV=; export ENV; }
+ CONFIG_SHELL=$as_dir/$as_base
+ export CONFIG_SHELL
+ exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+ fi;;
+ esac
+ done
+done
+;;
+ esac
+
+ # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+ # uniformly replaced by the line number. The first 'sed' inserts a
+ # line-number line before each line; the second 'sed' does the real
+ # work. The second script uses 'N' to pair each line-number line
+ # with the numbered line, and appends trailing '-' during
+ # substitution so that $LINENO is not a special case at line end.
+ # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+ # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-)
+ sed '=' <$as_myself |
+ sed '
+ N
+ s,$,-,
+ : loop
+ s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+ t loop
+ s,-$,,
+ s,^['$as_cr_digits']*\n,,
+ ' >$as_me.lineno &&
+ chmod +x $as_me.lineno ||
+ { { echo "$as_me:$LINENO: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&5
+echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2;}
+ { (exit 1); exit 1; }; }
+
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensible to this).
+ . ./$as_me.lineno
+ # Exit status is that of the last command.
+ exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+ *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T=' ' ;;
+ *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+ *) ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+ # We could just check for DJGPP; but this test a) works b) is more generic
+ # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+ if test -f conf$$.exe; then
+ # Don't use ln at all; we don't have any links
+ as_ln_s='cp -p'
+ else
+ as_ln_s='ln -s'
+ fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+else
+ as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p=:
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS=" $as_nl"
+
+# CDPATH.
+$as_unset CDPATH
+
+exec 6>&1
+
+# Open the log real soon, to keep \$[0] and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling. Logging --version etc. is OK.
+exec 5>>config.log
+{
+ echo
+ sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+} >&5
+cat >&5 <<_CSEOF
+
+This file was extended by fish $as_me 1.14.0, which was
+generated by GNU Autoconf 2.59. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+ CONFIG_HEADERS = $CONFIG_HEADERS
+ CONFIG_LINKS = $CONFIG_LINKS
+ CONFIG_COMMANDS = $CONFIG_COMMANDS
+ $ $0 $@
+
+_CSEOF
+echo "on `(hostname || uname -n) 2>/dev/null | sed 1q`" >&5
+echo >&5
+_ACEOF
+
+# Files that config.status was made for.
+if test -n "$ac_config_files"; then
+ echo "config_files=\"$ac_config_files\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_headers"; then
+ echo "config_headers=\"$ac_config_headers\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_links"; then
+ echo "config_links=\"$ac_config_links\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_commands"; then
+ echo "config_commands=\"$ac_config_commands\"" >>$CONFIG_STATUS
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+ac_cs_usage="\
+\`$as_me' instantiates files from templates according to the
+current configuration.
+
+Usage: $0 [OPTIONS] [FILE]...
+
+ -h, --help print this help, then exit
+ -V, --version print version number, then exit
+ -q, --quiet do not print progress messages
+ -d, --debug don't remove temporary files
+ --recheck update $as_me by reconfiguring in the same conditions
+ --file=FILE[:TEMPLATE]
+ instantiate the configuration file FILE
+ --header=FILE[:TEMPLATE]
+ instantiate the configuration header FILE
+
+Configuration files:
+$config_files
+
+Configuration headers:
+$config_headers
+
+Report bugs to <bug-autoconf@gnu.org>."
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+ac_cs_version="\\
+fish config.status 1.14.0
+configured by $0, generated by GNU Autoconf 2.59,
+ with options \\"`echo "$ac_configure_args" | sed 's/[\\""\`\$]/\\\\&/g'`\\"
+
+Copyright (C) 2003 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+srcdir=$srcdir
+INSTALL="$INSTALL"
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+# If no file are specified by the user, then we need to provide default
+# value. By we need to know if files were specified by the user.
+ac_need_defaults=:
+while test $# != 0
+do
+ case $1 in
+ --*=*)
+ ac_option=`expr "x$1" : 'x\([^=]*\)='`
+ ac_optarg=`expr "x$1" : 'x[^=]*=\(.*\)'`
+ ac_shift=:
+ ;;
+ -*)
+ ac_option=$1
+ ac_optarg=$2
+ ac_shift=shift
+ ;;
+ *) # This is not an option, so the user has probably given explicit
+ # arguments.
+ ac_option=$1
+ ac_need_defaults=false;;
+ esac
+
+ case $ac_option in
+ # Handling of the options.
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ ac_cs_recheck=: ;;
+ --version | --vers* | -V )
+ echo "$ac_cs_version"; exit 0 ;;
+ --he | --h)
+ # Conflict between --help and --header
+ { { echo "$as_me:$LINENO: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&2;}
+ { (exit 1); exit 1; }; };;
+ --help | --hel | -h )
+ echo "$ac_cs_usage"; exit 0 ;;
+ --debug | --d* | -d )
+ debug=: ;;
+ --file | --fil | --fi | --f )
+ $ac_shift
+ CONFIG_FILES="$CONFIG_FILES $ac_optarg"
+ ac_need_defaults=false;;
+ --header | --heade | --head | --hea )
+ $ac_shift
+ CONFIG_HEADERS="$CONFIG_HEADERS $ac_optarg"
+ ac_need_defaults=false;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil | --si | --s)
+ ac_cs_silent=: ;;
+
+ # This is an error.
+ -*) { { echo "$as_me:$LINENO: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&2;}
+ { (exit 1); exit 1; }; } ;;
+
+ *) ac_config_targets="$ac_config_targets $1" ;;
+
+ esac
+ shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+ exec 6>/dev/null
+ ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+if \$ac_cs_recheck; then
+ echo "running $SHELL $0 " $ac_configure_args \$ac_configure_extra_args " --no-create --no-recursion" >&6
+ exec $SHELL $0 $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+fi
+
+_ACEOF
+
+
+
+
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+for ac_config_target in $ac_config_targets
+do
+ case "$ac_config_target" in
+ # Handling of arguments.
+ "Makefile" ) CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+ "fish.spec" ) CONFIG_FILES="$CONFIG_FILES fish.spec" ;;
+ "doc_src/fish.1" ) CONFIG_FILES="$CONFIG_FILES doc_src/fish.1" ;;
+ "doc_src/Doxyfile" ) CONFIG_FILES="$CONFIG_FILES doc_src/Doxyfile" ;;
+ "init/fish" ) CONFIG_FILES="$CONFIG_FILES init/fish" ;;
+ "config.h" ) CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
+ *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
+echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used. Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+ test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+ test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
+fi
+
+# Have a temporary directory for convenience. Make it in the build tree
+# simply because there is no reason to put it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Create a temporary directory, and hook for its removal unless debugging.
+$debug ||
+{
+ trap 'exit_status=$?; rm -rf $tmp && exit $exit_status' 0
+ trap '{ (exit 1); exit 1; }' 1 2 13 15
+}
+
+# Create a (secure) tmp directory for tmp files.
+
+{
+ tmp=`(umask 077 && mktemp -d -q "./confstatXXXXXX") 2>/dev/null` &&
+ test -n "$tmp" && test -d "$tmp"
+} ||
+{
+ tmp=./confstat$$-$RANDOM
+ (umask 077 && mkdir $tmp)
+} ||
+{
+ echo "$me: cannot create a temporary directory in ." >&2
+ { (exit 1); exit 1; }
+}
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+
+#
+# CONFIG_FILES section.
+#
+
+# No need to generate the scripts if there are no CONFIG_FILES.
+# This happens for instance when ./config.status config.h
+if test -n "\$CONFIG_FILES"; then
+ # Protect against being on the right side of a sed subst in config.status.
+ sed 's/,@/@@/; s/@,/@@/; s/,;t t\$/@;t t/; /@;t t\$/s/[\\\\&,]/\\\\&/g;
+ s/@@/,@/; s/@@/@,/; s/@;t t\$/,;t t/' >\$tmp/subs.sed <<\\CEOF
+s,@SHELL@,$SHELL,;t t
+s,@PATH_SEPARATOR@,$PATH_SEPARATOR,;t t
+s,@PACKAGE_NAME@,$PACKAGE_NAME,;t t
+s,@PACKAGE_TARNAME@,$PACKAGE_TARNAME,;t t
+s,@PACKAGE_VERSION@,$PACKAGE_VERSION,;t t
+s,@PACKAGE_STRING@,$PACKAGE_STRING,;t t
+s,@PACKAGE_BUGREPORT@,$PACKAGE_BUGREPORT,;t t
+s,@exec_prefix@,$exec_prefix,;t t
+s,@prefix@,$prefix,;t t
+s,@program_transform_name@,$program_transform_name,;t t
+s,@bindir@,$bindir,;t t
+s,@sbindir@,$sbindir,;t t
+s,@libexecdir@,$libexecdir,;t t
+s,@datadir@,$datadir,;t t
+s,@sysconfdir@,$sysconfdir,;t t
+s,@sharedstatedir@,$sharedstatedir,;t t
+s,@localstatedir@,$localstatedir,;t t
+s,@libdir@,$libdir,;t t
+s,@includedir@,$includedir,;t t
+s,@oldincludedir@,$oldincludedir,;t t
+s,@infodir@,$infodir,;t t
+s,@mandir@,$mandir,;t t
+s,@build_alias@,$build_alias,;t t
+s,@host_alias@,$host_alias,;t t
+s,@target_alias@,$target_alias,;t t
+s,@DEFS@,$DEFS,;t t
+s,@ECHO_C@,$ECHO_C,;t t
+s,@ECHO_N@,$ECHO_N,;t t
+s,@ECHO_T@,$ECHO_T,;t t
+s,@LIBS@,$LIBS,;t t
+s,@build@,$build,;t t
+s,@build_cpu@,$build_cpu,;t t
+s,@build_vendor@,$build_vendor,;t t
+s,@build_os@,$build_os,;t t
+s,@host@,$host,;t t
+s,@host_cpu@,$host_cpu,;t t
+s,@host_vendor@,$host_vendor,;t t
+s,@host_os@,$host_os,;t t
+s,@target@,$target,;t t
+s,@target_cpu@,$target_cpu,;t t
+s,@target_vendor@,$target_vendor,;t t
+s,@target_os@,$target_os,;t t
+s,@CC@,$CC,;t t
+s,@CFLAGS@,$CFLAGS,;t t
+s,@LDFLAGS@,$LDFLAGS,;t t
+s,@CPPFLAGS@,$CPPFLAGS,;t t
+s,@ac_ct_CC@,$ac_ct_CC,;t t
+s,@EXEEXT@,$EXEEXT,;t t
+s,@OBJEXT@,$OBJEXT,;t t
+s,@CPP@,$CPP,;t t
+s,@INSTALL_PROGRAM@,$INSTALL_PROGRAM,;t t
+s,@INSTALL_SCRIPT@,$INSTALL_SCRIPT,;t t
+s,@INSTALL_DATA@,$INSTALL_DATA,;t t
+s,@XSEL@,$XSEL,;t t
+s,@XSEL_MAN@,$XSEL_MAN,;t t
+s,@XSEL_MAN_PATH@,$XSEL_MAN_PATH,;t t
+s,@PREFIX@,$PREFIX,;t t
+s,@fishdir@,$fishdir,;t t
+s,@fishfile@,$fishfile,;t t
+s,@fishinputfile@,$fishinputfile,;t t
+s,@docdir@,$docdir,;t t
+s,@LIBDIR@,$LIBDIR,;t t
+s,@INCLUDEDIR@,$INCLUDEDIR,;t t
+s,@EGREP@,$EGREP,;t t
+s,@CURSESLIB@,$CURSESLIB,;t t
+s,@LIBOBJS@,$LIBOBJS,;t t
+s,@LTLIBOBJS@,$LTLIBOBJS,;t t
+CEOF
+
+_ACEOF
+
+ cat >>$CONFIG_STATUS <<\_ACEOF
+ # Split the substitutions into bite-sized pieces for seds with
+ # small command number limits, like on Digital OSF/1 and HP-UX.
+ ac_max_sed_lines=48
+ ac_sed_frag=1 # Number of current file.
+ ac_beg=1 # First line for current file.
+ ac_end=$ac_max_sed_lines # Line after last line for current file.
+ ac_more_lines=:
+ ac_sed_cmds=
+ while $ac_more_lines; do
+ if test $ac_beg -gt 1; then
+ sed "1,${ac_beg}d; ${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+ else
+ sed "${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+ fi
+ if test ! -s $tmp/subs.frag; then
+ ac_more_lines=false
+ else
+ # The purpose of the label and of the branching condition is to
+ # speed up the sed processing (if there are no `@' at all, there
+ # is no need to browse any of the substitutions).
+ # These are the two extra sed commands mentioned above.
+ (echo ':t
+ /@[a-zA-Z_][a-zA-Z_0-9]*@/!b' && cat $tmp/subs.frag) >$tmp/subs-$ac_sed_frag.sed
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds="sed -f $tmp/subs-$ac_sed_frag.sed"
+ else
+ ac_sed_cmds="$ac_sed_cmds | sed -f $tmp/subs-$ac_sed_frag.sed"
+ fi
+ ac_sed_frag=`expr $ac_sed_frag + 1`
+ ac_beg=$ac_end
+ ac_end=`expr $ac_end + $ac_max_sed_lines`
+ fi
+ done
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds=cat
+ fi
+fi # test -n "$CONFIG_FILES"
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+for ac_file in : $CONFIG_FILES; do test "x$ac_file" = x: && continue
+ # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+ case $ac_file in
+ - | *:- | *:-:* ) # input from stdin
+ cat >$tmp/stdin
+ ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ *:* ) ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ * ) ac_file_in=$ac_file.in ;;
+ esac
+
+ # Compute @srcdir@, @top_srcdir@, and @INSTALL@ for subdirectories.
+ ac_dir=`(dirname "$ac_file") 2>/dev/null ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$ac_file" : 'X\(//\)[^/]' \| \
+ X"$ac_file" : 'X\(//\)$' \| \
+ X"$ac_file" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$ac_file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ { if $as_mkdir_p; then
+ mkdir -p "$ac_dir"
+ else
+ as_dir="$ac_dir"
+ as_dirs=
+ while test ! -d "$as_dir"; do
+ as_dirs="$as_dir $as_dirs"
+ as_dir=`(dirname "$as_dir") 2>/dev/null ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ done
+ test ! -n "$as_dirs" || mkdir $as_dirs
+ fi || { { echo "$as_me:$LINENO: error: cannot create directory \"$ac_dir\"" >&5
+echo "$as_me: error: cannot create directory \"$ac_dir\"" >&2;}
+ { (exit 1); exit 1; }; }; }
+
+ ac_builddir=.
+
+if test "$ac_dir" != .; then
+ ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+ ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+ .) # No --srcdir option. We are building in place.
+ ac_srcdir=.
+ if test -z "$ac_top_builddir"; then
+ ac_top_srcdir=.
+ else
+ ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+ fi ;;
+ [\\/]* | ?:[\\/]* ) # Absolute path.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir ;;
+ *) # Relative path.
+ ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+
+# Do not use `cd foo && pwd` to compute absolute paths, because
+# the directories may not exist.
+case `pwd` in
+.) ac_abs_builddir="$ac_dir";;
+*)
+ case "$ac_dir" in
+ .) ac_abs_builddir=`pwd`;;
+ [\\/]* | ?:[\\/]* ) ac_abs_builddir="$ac_dir";;
+ *) ac_abs_builddir=`pwd`/"$ac_dir";;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_builddir=${ac_top_builddir}.;;
+*)
+ case ${ac_top_builddir}. in
+ .) ac_abs_top_builddir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_top_builddir=${ac_top_builddir}.;;
+ *) ac_abs_top_builddir=$ac_abs_builddir/${ac_top_builddir}.;;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_srcdir=$ac_srcdir;;
+*)
+ case $ac_srcdir in
+ .) ac_abs_srcdir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_srcdir=$ac_srcdir;;
+ *) ac_abs_srcdir=$ac_abs_builddir/$ac_srcdir;;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_srcdir=$ac_top_srcdir;;
+*)
+ case $ac_top_srcdir in
+ .) ac_abs_top_srcdir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_top_srcdir=$ac_top_srcdir;;
+ *) ac_abs_top_srcdir=$ac_abs_builddir/$ac_top_srcdir;;
+ esac;;
+esac
+
+
+ case $INSTALL in
+ [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+ *) ac_INSTALL=$ac_top_builddir$INSTALL ;;
+ esac
+
+ if test x"$ac_file" != x-; then
+ { echo "$as_me:$LINENO: creating $ac_file" >&5
+echo "$as_me: creating $ac_file" >&6;}
+ rm -f "$ac_file"
+ fi
+ # Let's still pretend it is `configure' which instantiates (i.e., don't
+ # use $as_me), people would be surprised to read:
+ # /* config.h. Generated by config.status. */
+ if test x"$ac_file" = x-; then
+ configure_input=
+ else
+ configure_input="$ac_file. "
+ fi
+ configure_input=$configure_input"Generated from `echo $ac_file_in |
+ sed 's,.*/,,'` by configure."
+
+ # First look for the input files in the build tree, otherwise in the
+ # src tree.
+ ac_file_inputs=`IFS=:
+ for f in $ac_file_in; do
+ case $f in
+ -) echo $tmp/stdin ;;
+ [\\/$]*)
+ # Absolute (can't be DOS-style, as IFS=:)
+ test -f "$f" || { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ echo "$f";;
+ *) # Relative
+ if test -f "$f"; then
+ # Build tree
+ echo "$f"
+ elif test -f "$srcdir/$f"; then
+ # Source tree
+ echo "$srcdir/$f"
+ else
+ # /dev/null tree
+ { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ fi;;
+ esac
+ done` || { (exit 1); exit 1; }
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+ sed "$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s,@configure_input@,$configure_input,;t t
+s,@srcdir@,$ac_srcdir,;t t
+s,@abs_srcdir@,$ac_abs_srcdir,;t t
+s,@top_srcdir@,$ac_top_srcdir,;t t
+s,@abs_top_srcdir@,$ac_abs_top_srcdir,;t t
+s,@builddir@,$ac_builddir,;t t
+s,@abs_builddir@,$ac_abs_builddir,;t t
+s,@top_builddir@,$ac_top_builddir,;t t
+s,@abs_top_builddir@,$ac_abs_top_builddir,;t t
+s,@INSTALL@,$ac_INSTALL,;t t
+" $ac_file_inputs | (eval "$ac_sed_cmds") >$tmp/out
+ rm -f $tmp/stdin
+ if test x"$ac_file" != x-; then
+ mv $tmp/out $ac_file
+ else
+ cat $tmp/out
+ rm -f $tmp/out
+ fi
+
+done
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+#
+# CONFIG_HEADER section.
+#
+
+# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where
+# NAME is the cpp macro being defined and VALUE is the value it is being given.
+#
+# ac_d sets the value in "#define NAME VALUE" lines.
+ac_dA='s,^\([ ]*\)#\([ ]*define[ ][ ]*\)'
+ac_dB='[ ].*$,\1#\2'
+ac_dC=' '
+ac_dD=',;t'
+# ac_u turns "#undef NAME" without trailing blanks into "#define NAME VALUE".
+ac_uA='s,^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)'
+ac_uB='$,\1#\2define\3'
+ac_uC=' '
+ac_uD=',;t'
+
+for ac_file in : $CONFIG_HEADERS; do test "x$ac_file" = x: && continue
+ # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+ case $ac_file in
+ - | *:- | *:-:* ) # input from stdin
+ cat >$tmp/stdin
+ ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ *:* ) ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ * ) ac_file_in=$ac_file.in ;;
+ esac
+
+ test x"$ac_file" != x- && { echo "$as_me:$LINENO: creating $ac_file" >&5
+echo "$as_me: creating $ac_file" >&6;}
+
+ # First look for the input files in the build tree, otherwise in the
+ # src tree.
+ ac_file_inputs=`IFS=:
+ for f in $ac_file_in; do
+ case $f in
+ -) echo $tmp/stdin ;;
+ [\\/$]*)
+ # Absolute (can't be DOS-style, as IFS=:)
+ test -f "$f" || { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ # Do quote $f, to prevent DOS paths from being IFS'd.
+ echo "$f";;
+ *) # Relative
+ if test -f "$f"; then
+ # Build tree
+ echo "$f"
+ elif test -f "$srcdir/$f"; then
+ # Source tree
+ echo "$srcdir/$f"
+ else
+ # /dev/null tree
+ { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ fi;;
+ esac
+ done` || { (exit 1); exit 1; }
+ # Remove the trailing spaces.
+ sed 's/[ ]*$//' $ac_file_inputs >$tmp/in
+
+_ACEOF
+
+# Transform confdefs.h into two sed scripts, `conftest.defines' and
+# `conftest.undefs', that substitutes the proper values into
+# config.h.in to produce config.h. The first handles `#define'
+# templates, and the second `#undef' templates.
+# And first: Protect against being on the right side of a sed subst in
+# config.status. Protect against being in an unquoted here document
+# in config.status.
+rm -f conftest.defines conftest.undefs
+# Using a here document instead of a string reduces the quoting nightmare.
+# Putting comments in sed scripts is not portable.
+#
+# `end' is used to avoid that the second main sed command (meant for
+# 0-ary CPP macros) applies to n-ary macro definitions.
+# See the Autoconf documentation for `clear'.
+cat >confdef2sed.sed <<\_ACEOF
+s/[\\&,]/\\&/g
+s,[\\$`],\\&,g
+t clear
+: clear
+s,^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*\)\(([^)]*)\)[ ]*\(.*\)$,${ac_dA}\1${ac_dB}\1\2${ac_dC}\3${ac_dD},gp
+t end
+s,^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)$,${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD},gp
+: end
+_ACEOF
+# If some macros were called several times there might be several times
+# the same #defines, which is useless. Nevertheless, we may not want to
+# sort them, since we want the *last* AC-DEFINE to be honored.
+uniq confdefs.h | sed -n -f confdef2sed.sed >conftest.defines
+sed 's/ac_d/ac_u/g' conftest.defines >conftest.undefs
+rm -f confdef2sed.sed
+
+# This sed command replaces #undef with comments. This is necessary, for
+# example, in the case of _POSIX_SOURCE, which is predefined and required
+# on some systems where configure will not decide to define it.
+cat >>conftest.undefs <<\_ACEOF
+s,^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*,/* & */,
+_ACEOF
+
+# Break up conftest.defines because some shells have a limit on the size
+# of here documents, and old seds have small limits too (100 cmds).
+echo ' # Handle all the #define templates only if necessary.' >>$CONFIG_STATUS
+echo ' if grep "^[ ]*#[ ]*define" $tmp/in >/dev/null; then' >>$CONFIG_STATUS
+echo ' # If there are no defines, we may have an empty if/fi' >>$CONFIG_STATUS
+echo ' :' >>$CONFIG_STATUS
+rm -f conftest.tail
+while grep . conftest.defines >/dev/null
+do
+ # Write a limited-size here document to $tmp/defines.sed.
+ echo ' cat >$tmp/defines.sed <<CEOF' >>$CONFIG_STATUS
+ # Speed up: don't consider the non `#define' lines.
+ echo '/^[ ]*#[ ]*define/!b' >>$CONFIG_STATUS
+ # Work around the forget-to-reset-the-flag bug.
+ echo 't clr' >>$CONFIG_STATUS
+ echo ': clr' >>$CONFIG_STATUS
+ sed ${ac_max_here_lines}q conftest.defines >>$CONFIG_STATUS
+ echo 'CEOF
+ sed -f $tmp/defines.sed $tmp/in >$tmp/out
+ rm -f $tmp/in
+ mv $tmp/out $tmp/in
+' >>$CONFIG_STATUS
+ sed 1,${ac_max_here_lines}d conftest.defines >conftest.tail
+ rm -f conftest.defines
+ mv conftest.tail conftest.defines
+done
+rm -f conftest.defines
+echo ' fi # grep' >>$CONFIG_STATUS
+echo >>$CONFIG_STATUS
+
+# Break up conftest.undefs because some shells have a limit on the size
+# of here documents, and old seds have small limits too (100 cmds).
+echo ' # Handle all the #undef templates' >>$CONFIG_STATUS
+rm -f conftest.tail
+while grep . conftest.undefs >/dev/null
+do
+ # Write a limited-size here document to $tmp/undefs.sed.
+ echo ' cat >$tmp/undefs.sed <<CEOF' >>$CONFIG_STATUS
+ # Speed up: don't consider the non `#undef'
+ echo '/^[ ]*#[ ]*undef/!b' >>$CONFIG_STATUS
+ # Work around the forget-to-reset-the-flag bug.
+ echo 't clr' >>$CONFIG_STATUS
+ echo ': clr' >>$CONFIG_STATUS
+ sed ${ac_max_here_lines}q conftest.undefs >>$CONFIG_STATUS
+ echo 'CEOF
+ sed -f $tmp/undefs.sed $tmp/in >$tmp/out
+ rm -f $tmp/in
+ mv $tmp/out $tmp/in
+' >>$CONFIG_STATUS
+ sed 1,${ac_max_here_lines}d conftest.undefs >conftest.tail
+ rm -f conftest.undefs
+ mv conftest.tail conftest.undefs
+done
+rm -f conftest.undefs
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+ # Let's still pretend it is `configure' which instantiates (i.e., don't
+ # use $as_me), people would be surprised to read:
+ # /* config.h. Generated by config.status. */
+ if test x"$ac_file" = x-; then
+ echo "/* Generated by configure. */" >$tmp/config.h
+ else
+ echo "/* $ac_file. Generated by configure. */" >$tmp/config.h
+ fi
+ cat $tmp/in >>$tmp/config.h
+ rm -f $tmp/in
+ if test x"$ac_file" != x-; then
+ if diff $ac_file $tmp/config.h >/dev/null 2>&1; then
+ { echo "$as_me:$LINENO: $ac_file is unchanged" >&5
+echo "$as_me: $ac_file is unchanged" >&6;}
+ else
+ ac_dir=`(dirname "$ac_file") 2>/dev/null ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$ac_file" : 'X\(//\)[^/]' \| \
+ X"$ac_file" : 'X\(//\)$' \| \
+ X"$ac_file" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$ac_file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ { if $as_mkdir_p; then
+ mkdir -p "$ac_dir"
+ else
+ as_dir="$ac_dir"
+ as_dirs=
+ while test ! -d "$as_dir"; do
+ as_dirs="$as_dir $as_dirs"
+ as_dir=`(dirname "$as_dir") 2>/dev/null ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ done
+ test ! -n "$as_dirs" || mkdir $as_dirs
+ fi || { { echo "$as_me:$LINENO: error: cannot create directory \"$ac_dir\"" >&5
+echo "$as_me: error: cannot create directory \"$ac_dir\"" >&2;}
+ { (exit 1); exit 1; }; }; }
+
+ rm -f $ac_file
+ mv $tmp/config.h $ac_file
+ fi
+ else
+ cat $tmp/config.h
+ rm -f $tmp/config.h
+ fi
+done
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+{ (exit 0); exit 0; }
+_ACEOF
+chmod +x $CONFIG_STATUS
+ac_clean_files=$ac_clean_files_save
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded. So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status. When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+ ac_cs_success=:
+ ac_config_status_args=
+ test "$silent" = yes &&
+ ac_config_status_args="$ac_config_status_args --quiet"
+ exec 5>/dev/null
+ $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+ exec 5>>config.log
+ # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+ # would make configure fail if this is the last instruction.
+ $ac_cs_success || { (exit 1); exit 1; }
+fi
+
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 00000000..c2798e9b
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,90 @@
+# Process this file with autoconf to produce a configure script.
+AC_INIT(fish,1.14.0,axel@liljencrantz.se)
+
+AC_CANONICAL_TARGET
+
+if test $target_cpu = powerpc; then
+ AC_DEFINE([TPUTS_KLUDGE],[1],[Evil kludge to get Power based machines to work])
+fi
+
+AC_DEFINE_UNQUOTED([CPU],[L"$target_cpu"],[CPU type])
+
+AC_CONFIG_HEADERS(config.h)
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_INSTALL
+#AC_ISC_POSIX
+#AC_PROG_MAKE_SET
+
+# Optionally drop xsel
+AC_ARG_WITH( xsel,
+ AC_HELP_STRING([--without-xsel],
+ [do not build the xsel program needed for X clipboard integration]),
+ [xsel=$withval],
+ [xsel=with_xsel] )
+
+if [[ "$xsel" = "with_xsel" ]]; then
+ AC_SUBST( XSEL,[xsel-0.9.6/xsel])
+ AC_SUBST( XSEL_MAN,[xsel.1x])
+ AC_SUBST( XSEL_MAN_PATH,[xsel-0.9.6/xsel.1x])
+else
+ AC_SUBST( XSEL,[ ])
+ AC_SUBST( XSEL_MAN,[ ])
+ AC_SUBST( XSEL_MAN_PATH,[ ])
+fi
+
+if [[ "$prefix" = NONE ]]; then
+ AC_DEFINE_UNQUOTED( [PREFIX], L"/usr/local", [Installation directory])
+ AC_SUBST( PREFIX, /usr/local)
+ AC_SUBST(sysconfdir,[/etc])
+ export prefix=/usr/local
+else
+ AC_DEFINE_UNQUOTED( [PREFIX], L"$prefix", [Installation directory])
+ AC_SUBST( PREFIX, [$prefix])
+ AC_SUBST(sysconfdir,[/etc])
+fi
+
+if echo $prefix | grep \^$HOME >/dev/null; then
+ AC_SUBST(sysconfdir,[$HOME])
+ AC_SUBST(fishdir,[/.fish.d])
+ AC_SUBST(fishfile,[/.fish])
+ AC_SUBST(fishinputfile,[/.fish_inputrc])
+ AC_MSG_NOTICE(["Install in $HOME"])
+else
+ AC_SUBST(fishdir,[/fish.d])
+ AC_SUBST(fishfile,[/fish])
+ AC_SUBST(fishinputfile,[/fish_inputrc])
+fi
+
+AC_ARG_VAR( [docdir], [Documentation direcotry] )
+
+if test -z $docdir; then
+ AC_SUBST(docdir,[$datadir/doc/fish])
+fi
+
+AC_DEFINE_UNQUOTED( DOCDIR, [L"$(eval echo $docdir)"], [Documentation directory] )
+
+# See if Linux procfs is present
+AC_CHECK_FILES([/proc/self/stat])
+
+# See if NetBSD pkgsrc is installed
+AC_CHECK_FILE([/usr/pkg/lib],[AC_SUBST(LIBDIR,[-L/usr/pkg/lib\ -R/usr/pkg/lib])])
+AC_CHECK_FILE([/usr/pkg/include],[AC_SUBST(INCLUDEDIR,[-I/usr/pkg/include])])
+
+AC_CHECK_FUNCS( [wprintf futimes wcwidth wcswidth] )
+AC_CHECK_HEADERS([getopt.h termio.h])
+
+
+
+# Check if we have ncurses, and use it rather than curses if possible.
+AC_CHECK_HEADERS([ncurses.h],[AC_SUBST(CURSESLIB,[ncurses]) AC_DEFINE(HAVE_NCURSES_H)],[AC_SUBST(CURSESLIB,[curses])])
+
+#Force use of ncurses if it is avialable via NetBSD pkgsrc. This is an
+#ugly kludge to force NetBSD to use ncurses, since NetBSDs own version
+#does not properly support terminfo.
+AC_CHECK_FILE([/usr/pkg/include/ncurses.h],[AC_SUBST(CURSESLIB,[ncurses]) AC_DEFINE(HAVE_NCURSES_H)])
+
+AC_CONFIG_FILES([Makefile fish.spec doc_src/fish.1 doc_src/Doxyfile init/fish])
+AC_OUTPUT
diff --git a/count.c b/count.c
new file mode 100644
index 00000000..f57b8048
--- /dev/null
+++ b/count.c
@@ -0,0 +1,22 @@
+/** \file count.c
+ The length command, used for determining the number of items in an
+ environment variable array.
+*/
+#include <stdlib.h>
+#include <stdio.h>
+
+/**
+ The main function. Does nothing but return the number of arguments.
+
+ This command, unlike all other fish commands, does not feature a -h
+ or --help option. This is because we want to avoid errors on arrays
+ that have -h or --help as entries, which is very common when
+ parsing options, etc. For this reason, the main fish binary does a
+ check and prints help usage if -h or --help is explicitly given to
+ the command, but not if it is the contents of a variable.
+*/
+int main( int argc, char **argv )
+{
+ printf( "%d\n", argc-1 );
+ return argc==1;
+}
diff --git a/env.c b/env.c
new file mode 100644
index 00000000..5a5ce979
--- /dev/null
+++ b/env.c
@@ -0,0 +1,763 @@
+/** \file env.c
+ Functions for setting and getting environment variables.
+*/
+
+#include <stdlib.h>
+#include <wchar.h>
+#include <string.h>
+#include <stdio.h>
+#include <locale.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+
+#include "config.h"
+#include "util.h"
+#include "wutil.h"
+#include "proc.h"
+#include "common.h"
+#include "env.h"
+#include "sanity.h"
+#include "expand.h"
+#include "history.h"
+#include "reader.h"
+#include "parser.h"
+#include "env_universal.h"
+#include "env_universal.h"
+
+
+/**
+ Command used to start fishd
+*/
+#define FISHD_CMD L"if which fishd >/dev/null ^/dev/null; fishd ^/tmp/fish.%s.log; end"
+
+/**
+ At init, we read all the environment variables from this array
+*/
+extern char **environ;
+
+static int c1=0;
+
+/**
+ Struct representing one level in the function variable stack
+*/
+typedef struct env_node
+{
+ /**
+ Variable table
+ */
+ hash_table_t env;
+ /**
+ Does this node imply a new variable scope? If yes, all
+ non-global variables below this one in the stack are
+ invisible. If new_scope is set for the global variable node,
+ the universe will explode.
+ */
+ int new_scope;
+ /**
+ Does this node contain any variables which are exported to subshells
+ */
+ int export;
+
+ /**
+ Pointer to next level
+ */
+ struct env_node *next;
+}
+env_node_t;
+
+/**
+ A variable entry. Stores the value of a variable and whether it
+ should be exported. Obviously, it needs to be allocated large
+ enough to fit the value string.
+*/
+typedef struct var_entry
+{
+ int export; /**< Whether the variable should be exported */
+ wchar_t val[0]; /**< The value of the variable */
+}
+var_entry_t;
+
+/**
+ Top node on the function stack
+*/
+static env_node_t *top=0;
+
+/**
+ Bottom node on the function stack
+*/
+static env_node_t *global_env = 0;
+
+
+/**
+ Table for global variables
+*/
+static hash_table_t *global;
+
+/**
+ Table of variables that may not be set using the set command.
+*/
+static hash_table_t env_read_only;
+
+/**
+ Exported variable array used by execv
+*/
+static char **export_arr=0;
+
+/**
+ Flag for checking if we need to regenerate the exported variable
+ array
+*/
+static int has_changed = 1;
+
+/**
+ Number of variables marked for export. The actual number of
+ variables actually exported may be lower because of variable
+ scoping rules.
+*/
+static int export_count=0;
+
+
+/**
+ Free hash key and hash value
+*/
+static void clear_hash_entry( const void *key, const void *data )
+{
+ var_entry_t *entry = (var_entry_t *)data;
+ if( entry->export )
+ has_changed = 1;
+
+ free( (void *)key );
+ free( (void *)data );
+}
+
+/**
+ This stringbuffer is used to store the value of dynamically
+ generated variables, such as history.
+*/
+static string_buffer_t dyn_var;
+
+/**
+ Variable used by env_get_names to communicate auxiliary information
+ to add_key_to_hash
+*/
+static int get_names_show_exported;
+/**
+ Variable used by env_get_names to communicate auxiliary information
+ to add_key_to_hash
+*/
+static int get_names_show_unexported;
+
+/**
+ When fishd isn't started, this function is provided to
+ env_universal as a callback, it tries to start upå fishd. It's
+ implementation is a bit of a hack, since it just calls a bit of
+ shellscript, and the shell is not properly initialized ad this
+ point. Should be changed to deferr the evaluation until fish has
+ been properly initialized.
+*/
+static void start_fishd()
+{
+ string_buffer_t cmd;
+ struct passwd *pw;
+
+ sb_init( &cmd );
+ pw = getpwuid(getuid());
+
+ debug( 3, L"Spawning new copy of fishd" );
+
+ if( !pw )
+ {
+ debug( 0, L"Could not get user information" );
+ return;
+ }
+
+ sb_printf( &cmd, FISHD_CMD, pw->pw_name );
+
+ eval( (wchar_t *)cmd.buff,
+ 0,
+ TOP );
+ sb_destroy( &cmd );
+}
+
+void env_init()
+{
+ char **p;
+
+ sb_init( &dyn_var );
+
+ /*
+ These variables can not be altered directly by the user
+ */
+ hash_init( &env_read_only, &hash_wcs_func, &hash_wcs_cmp );
+
+ hash_put( &env_read_only, L"status", L"" );
+ hash_put( &env_read_only, L"history", L"" );
+ hash_put( &env_read_only, L"_", L"" );
+ hash_put( &env_read_only, L"LINES", L"" );
+ hash_put( &env_read_only, L"COLUMNS", L"" );
+ hash_put( &env_read_only, L"PWD", L"" );
+
+ /*
+ HOME should be writeable by root, since this is often a
+ convenient way to install software.
+ */
+ if( getuid() != 0 )
+ hash_put( &env_read_only, L"HOME", L"" );
+
+ top = malloc( sizeof(env_node_t) );
+ top->next = 0;
+ top->new_scope = 0;
+ top->export=0;
+ hash_init( &top->env, &hash_wcs_func, &hash_wcs_cmp );
+ global_env = top;
+ global = &top->env;
+
+ /*
+ Import environment variables
+ */
+ for( p=environ; *p; p++ )
+ {
+ wchar_t *key, *val;
+ wchar_t *pos;
+
+ key = str2wcs(*p);
+
+ if( !key )
+ continue;
+
+ val = wcschr( key, L'=' );
+
+ if( val == 0 )
+ env_set( key, L"", ENV_EXPORT );
+ else
+ {
+ *val = L'\0';
+ val++;
+ pos=val;
+ while( *pos )
+ {
+ if( *pos == L':' )
+ *pos = ARRAY_SEP;
+ pos++;
+ }
+// fwprintf( stderr, L"Set $%ls to %ls\n", key, val );
+
+ env_set( key, val, ENV_EXPORT | ENV_GLOBAL );
+ }
+ free(key);
+ }
+
+ env_universal_init( env_get( L"FISHD_SOKET_DIR"), env_get(L"USER"), &start_fishd );
+
+}
+
+void env_destroy()
+{
+ char **ptr;
+
+ env_universal_destroy();
+// fwprintf( stderr, L"Filled %d exported vars\n", c1 );
+
+ sb_destroy( &dyn_var );
+
+ while( &top->env != global )
+ env_pop();
+
+ hash_destroy( &env_read_only );
+
+ hash_foreach( global, &clear_hash_entry );
+ hash_destroy( global );
+ free( top );
+
+ if( export_arr != 0 )
+ {
+ for( ptr = export_arr; *ptr; ptr++ )
+ free( *ptr );
+
+ free( export_arr );
+ }
+}
+
+/**
+ Find the scope hashtable containing the variable with the specified
+ key
+*/
+static env_node_t *env_get_node( const wchar_t *key )
+{
+ var_entry_t* res;
+ env_node_t *env = top;
+
+
+ while( env != 0 )
+ {
+ res = (var_entry_t *) hash_get( &env->env,
+ key );
+ if( res != 0 )
+ {
+ return env;
+ }
+
+ if( env->new_scope )
+ env = global_env;
+ else
+ env = env->next;
+ }
+
+ return 0;
+}
+
+void env_set( const wchar_t *key,
+ const wchar_t *val,
+ int var_mode )
+{
+ int free_val = 0;
+ var_entry_t *entry;
+ env_node_t *node;
+ int has_changed_old = has_changed;
+ int has_changed_new = 0;
+ var_entry_t *e=0;
+
+
+ if( (var_mode & ENV_USER ) &&
+ hash_get( &env_read_only, key ) )
+ {
+ return;
+ }
+
+ if( wcscmp(key, L"LANG" )==0 )
+ {
+ fish_setlocale(LC_ALL,val);
+ }
+
+ if( var_mode & ENV_UNIVERSAL )
+ {
+ env_universal_set( key, val );
+ return;
+ }
+
+ if( val == 0 )
+ {
+ wchar_t *prev_val;
+ free_val = 1;
+ prev_val = env_get( key );
+ val = wcsdup( prev_val?prev_val:L"" );
+ }
+
+ node = env_get_node( key );
+ if( &node->env != 0 )
+ {
+ e = (var_entry_t *) hash_get( &node->env,
+ key );
+
+ if( e->export )
+ has_changed_new = 1;
+
+ }
+
+ if( (var_mode & ENV_LOCAL) ||
+ (var_mode & ENV_GLOBAL) )
+ {
+ node = ( var_mode & ENV_GLOBAL )?global_env:top;
+ }
+ else
+ {
+ if( node )
+ {
+ if( !(var_mode & ENV_EXPORT ) &&
+ !(var_mode & ENV_UNEXPORT ) )
+ {
+ var_mode = e->export?ENV_EXPORT:0;
+ }
+ }
+ else
+ {
+ if( env_universal_get( key ) )
+ {
+ env_universal_set( key, val );
+ return;
+ }
+ else
+ {
+ node = top;
+ }
+ }
+ }
+
+// env_remove( key, 0 );
+ void *k, *v;
+ hash_remove( &node->env, key, (const void **)&k, (const void **)&v );
+ free( k );
+ free( v );
+
+ entry = malloc( sizeof( var_entry_t ) +
+ sizeof(wchar_t )*(wcslen(val)+1));
+
+ if( var_mode & ENV_EXPORT)
+ {
+ entry->export = 1;
+ export_count++;
+ has_changed_new = 1;
+ }
+ else
+ entry->export = 0;
+
+ wcscpy( entry->val, val );
+
+ hash_put( &node->env, wcsdup(key), entry );
+
+ if( entry->export )
+ {
+ node->export=1;
+ }
+
+ if( free_val )
+ free((void *)val);
+
+// if( has_changed_new && !has_changed_old )
+// fwprintf( stderr, L"Reexport after setting %ls to %ls\n", key, val );
+
+ has_changed = has_changed_old | has_changed_new;
+
+}
+
+/**
+ Attempt to remove/free the specified key/value pair from the
+ specified hash table.
+*/
+static int try_remove( env_node_t *n,
+ const wchar_t *key )
+{
+ wchar_t *old_key, *old_val;
+ if( n == 0 )
+ return 0;
+
+ hash_remove( &n->env,
+ key,
+ (const void **)&old_key,
+ (const void **)&old_val );
+ if( old_key != 0 )
+ {
+ var_entry_t * v = (var_entry_t *)old_val;
+ if( v->export )
+ {
+ export_count --;
+ has_changed = 1;
+ }
+
+ free(old_key);
+ free(old_val);
+ return 1;
+ }
+
+ if( n->new_scope )
+ return try_remove( global_env, key );
+ else
+ return try_remove( n->next, key );
+}
+
+
+void env_remove( const wchar_t *key, int var_mode )
+{
+ if( (var_mode & ENV_USER ) &&
+ hash_get( &env_read_only, key ) )
+ {
+ return;
+ }
+
+ if( !try_remove( top, key ) )
+ {
+ env_universal_remove( key );
+ }
+}
+
+
+wchar_t *env_get( const wchar_t *key )
+{
+ var_entry_t *res;
+ env_node_t *env = top;
+
+ if( wcscmp( key, L"history" ) == 0 )
+ {
+ wchar_t *current;
+ int i;
+ int add_current=0;
+ sb_clear( &dyn_var );
+
+ current = reader_get_buffer();
+ if( current && wcslen( current ) )
+ {
+ add_current=1;
+ sb_append( &dyn_var, current );
+ }
+
+ for( i=add_current; i<8; i++ )
+ {
+ wchar_t *next = history_get( i-add_current );
+ if( !next )
+ {
+ debug( 1, L"No history at idx %d\n", i );
+ break;
+ }
+
+ if( i!=0)
+ sb_append( &dyn_var, ARRAY_SEP_STR );
+ sb_append( &dyn_var, next );
+ }
+ return (wchar_t *)dyn_var.buff;
+ }
+
+ while( env != 0 )
+ {
+ res = (var_entry_t *) hash_get( &env->env,
+ key );
+ if( res != 0 )
+ {
+ return res->val;
+ }
+
+ if( env->new_scope )
+ env = global_env;
+ else
+ env = env->next;
+ }
+ return env_universal_get( key );
+}
+
+static int local_scope_exports( env_node_t *n )
+{
+
+ if( n==global_env )
+ return 0;
+
+ if( n->export )
+ return 1;
+
+ if( n->new_scope )
+ return 0;
+
+ return local_scope_exports( n->next );
+}
+
+void env_push( int new_scope )
+{
+ env_node_t *node = malloc( sizeof(env_node_t) );
+ node->next = top;
+ node->export=0;
+ hash_init( &node->env, &hash_wcs_func, &hash_wcs_cmp );
+ node->new_scope=new_scope;
+ if( new_scope )
+ {
+ has_changed = local_scope_exports(top);
+ }
+ top = node;
+
+}
+
+/*static int scope_count( env_node_t *n )
+{
+ if( n == global_env )
+ return 0;
+ return( scope_count( n->next) + 1 );
+}
+*/
+
+void env_pop()
+{
+ if( &top->env != global )
+ {
+ env_node_t *killme = top;
+
+ if( killme->new_scope )
+ {
+ has_changed = killme->export || local_scope_exports( killme->next );
+ }
+
+ top = top->next;
+ hash_foreach( &killme->env, &clear_hash_entry );
+ hash_destroy( &killme->env );
+ free( killme );
+
+ }
+ else
+ {
+ debug( 0,
+ L"Tried to pop empty environment stack." );
+ sanity_lose();
+ }
+}
+
+/**
+ Recreate the table of global variables used by execv
+*/
+static void fill_arr( const void *key, const void *val, void *aux )
+{
+ var_entry_t *val_entry = (var_entry_t *)val;
+ if( val_entry->export )
+ {
+
+ c1++;
+
+ wchar_t *wcs_val = wcsdup( val_entry->val );
+ wchar_t *pos = wcs_val;
+
+ int *idx_ptr = (int *)aux;
+ char *key_str = wcs2str((wchar_t *)key);
+
+ char *val_str;
+ char *woot;
+
+ while( *pos )
+ {
+ if( *pos == ARRAY_SEP )
+ *pos = L':';
+ pos++;
+ }
+
+ val_str = wcs2str( wcs_val );
+ free( wcs_val );
+
+ woot = malloc( sizeof(char)*( strlen(key_str) +
+ strlen(val_str) + 2) );
+
+ strcpy( woot, key_str );
+ strcat( woot, "=" );
+ strcat( woot, val_str );
+ export_arr[*idx_ptr] = woot;
+ (*idx_ptr)++;
+
+ free( key_str );
+ free( val_str );
+ }
+}
+
+
+/**
+ Function used with hash_foreach to insert keys of one table into
+ another
+*/
+static void add_key_to_hash( const void *key,
+ const void *data,
+ void *aux )
+{
+ var_entry_t *e = (var_entry_t *)data;
+ if( ( e->export && get_names_show_exported) ||
+ ( !e->export && get_names_show_unexported) )
+ hash_put( (hash_table_t *)aux, key, 0 );
+}
+static void add_universal_key_to_hash( const void *key,
+ const void *data,
+ void *aux )
+{
+ hash_put( (hash_table_t *)aux, key, 0 );
+}
+
+void env_get_names( array_list_t *l, int flags )
+{
+ int show_local = flags & ENV_LOCAL;
+ int show_global = flags & ENV_GLOBAL;
+ int show_universal = flags & ENV_UNIVERSAL;
+
+ hash_table_t names;
+ env_node_t *n=top;
+
+ get_names_show_exported =
+ flags & ENV_EXPORT|| (!(flags & ENV_UNEXPORT));
+ get_names_show_unexported =
+ flags & ENV_UNEXPORT|| (!(flags & ENV_EXPORT));
+
+ if( !show_local && !show_global && !show_universal )
+ {
+ show_local =show_universal = show_global=1;
+ }
+
+ hash_init( &names, &hash_wcs_func, &hash_wcs_cmp );
+
+ if( show_local )
+ {
+ while( n )
+ {
+ if( n == global_env )
+ break;
+
+ hash_foreach2( &n->env,
+ add_key_to_hash,
+ &names );
+
+ if( n->new_scope )
+ break;
+ else
+ n = n->next;
+
+ }
+ }
+
+ if( show_global )
+ {
+ hash_foreach2( &global_env->env,
+ add_key_to_hash,
+ &names );
+ if( get_names_show_unexported )
+ al_push( l, L"history" );
+ }
+
+ if( show_universal )
+ {
+ if( get_names_show_unexported )
+ hash_foreach2( &env_universal_var,
+ add_universal_key_to_hash,
+ &names );
+ }
+
+ hash_get_keys( &names, l );
+ hash_destroy( &names );
+}
+
+
+char **env_export_arr()
+{
+ if( has_changed )
+ {
+ int pos=0;
+ char **ptr;
+ env_node_t *n=top;
+
+ if( export_arr != 0 )
+ {
+ for( ptr = export_arr; *ptr; ptr++ )
+ free( *ptr );
+ }
+
+ export_arr = realloc( export_arr,
+ sizeof(char *)*(export_count + 1) );
+
+ while( n )
+ {
+ hash_foreach2( &n->env, &fill_arr, &pos );
+
+ if( n->new_scope )
+ n = global_env;
+ else
+ n = n->next;
+
+ }
+ export_arr[pos]=0;
+ has_changed=0;
+ }
+ return export_arr;
+}
diff --git a/env.h b/env.h
new file mode 100644
index 00000000..23b9fbf0
--- /dev/null
+++ b/env.h
@@ -0,0 +1,98 @@
+/** \file env.h
+ Prototypes for functions for setting and getting environment variables.
+*/
+
+/**
+ Flag for local (to the current block) variable
+*/
+#define ENV_LOCAL 1
+
+/**
+ Flag for exported (to commands) variable
+*/
+#define ENV_EXPORT 2
+
+/**
+ Flag for unexported variable
+*/
+#define ENV_UNEXPORT 16
+
+/**
+ Flag for global variable
+*/
+#define ENV_GLOBAL 4
+
+/**
+ Flag for variable update request from the user. All variable
+ changes that are made directly by the user, such as those from the
+ 'set' builtin must have this flag set.
+*/
+#define ENV_USER 8
+
+/**
+ Flag for universal variable
+*/
+#define ENV_UNIVERSAL 32
+
+/**
+ Initialize environment variable data
+*/
+void env_init();
+
+/**
+ Destroy environment variable data
+*/
+void env_destroy();
+
+
+/**
+ Set the value of the environment variable whose name matches key to val.
+
+ Memory policy: All keys and values are copied, the parameters can and should be freed by the caller afterwards
+
+ \param key The key
+ \param val The value
+ \param mode The type of the variable. Can be any combination of ENV_GLOBAL, ENV_LOCAL, ENV_EXPORT and ENV_USER. If mode is zero, the current variable space is searched and the current mode is used. If no current variable with the same name is found, ENV_LOCAL is assumed.
+
+*/
+
+void env_set( const wchar_t *key,
+ const wchar_t *val,
+ int mode );
+
+
+/**
+ Return the value of the variable with the specified name.
+ Returns 0 if the key does not exist.
+ The returned string should not be modified or freed.
+*/
+wchar_t *env_get( const wchar_t *key );
+
+/**
+ Remove environemnt variable
+
+ \param key The name of the variable to remove
+ \param mode should be ENV_USER if this is a remove request from the user, 0 otherwise. If this is a user request, read-only variables can not be removed.
+*/
+void env_remove( const wchar_t *key, int mode );
+
+/**
+ Push the variable stack. Used for implementing local variables for functions and for-loops.
+*/
+void env_push( int new_scope );
+
+/**
+ Pop the variable stack. Used for implementing local variables for functions and for-loops.
+*/
+void env_pop();
+
+/**
+ Returns an array containing all exported variables in a format suitable for execv.
+*/
+char **env_export_arr();
+
+/**
+ Insert all variable names into l. These are not copies of the strings and should not be freed after use.
+
+*/
+void env_get_names( array_list_t *l, int flags );
diff --git a/env_universal.c b/env_universal.c
new file mode 100644
index 00000000..b10d307d
--- /dev/null
+++ b/env_universal.c
@@ -0,0 +1,316 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <strings.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <pwd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <term.h>
+#include <signal.h>
+
+#include "util.h"
+#include "common.h"
+#include "wutil.h"
+#include "env_universal_common.h"
+#include "env_universal.h"
+
+/**
+ Maximum number of times to try to get a new fishd socket
+*/
+
+#define RECONNECT_COUNT 32
+
+
+connection_t env_universal_server;
+
+/**
+ Set to 1 after initialization has been performed
+*/
+static int init = 0;
+
+/**
+ The number of attempts to start fishd
+*/
+static int get_socket_count = 0;
+
+wchar_t * path;
+wchar_t *user;
+void (*start_fishd)();
+
+static int barrier_reply = 0;
+
+static void barrier();
+
+
+/**
+ Get a socket for reading from the server
+*/
+static int get_socket( int fork_ok )
+{
+ int s, len;
+ struct sockaddr_un local;
+
+ char *name;
+ wchar_t *wdir;
+ wchar_t *wuname;
+ char *dir =0, *uname=0;
+
+ get_socket_count++;
+ wdir = path;
+ wuname = user;
+
+ if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ {
+ wperror(L"socket");
+ return -1;
+ }
+
+ if( wdir )
+ dir = wcs2str(wdir );
+ else
+ dir = strdup("/tmp");
+
+ if( wuname )
+ uname = wcs2str(wuname );
+ else
+ {
+ struct passwd *pw;
+ pw = getpwuid( getuid() );
+ uname = strdup( pw->pw_name );
+ }
+
+ name = malloc( strlen(dir) +
+ strlen(uname) +
+ strlen(SOCK_FILENAME) +
+ 2 );
+
+ strcpy( name, dir );
+ strcat( name, "/" );
+ strcat( name, SOCK_FILENAME );
+ strcat( name, uname );
+
+ free( dir );
+ free( uname );
+
+ debug( 3, L"Connect to socket %s at fd %2", name, s );
+
+ local.sun_family = AF_UNIX;
+ strcpy(local.sun_path, name );
+ free( name );
+ len = strlen(local.sun_path) + sizeof(local.sun_family);
+
+ if( connect( s, (struct sockaddr *)&local, len) == -1 )
+ {
+ close( s );
+ if( fork_ok )
+ {
+ debug( 2, L"Could not connect to socket %d, starting fishd", s );
+ if( start_fishd )
+ start_fishd();
+ return get_socket( 0 );
+ }
+
+ debug( 3, L"Could not connect to socket %d, already tried forking, giving up", s );
+ return -1;
+ }
+
+ if( fcntl( s, F_SETFL, O_NONBLOCK ) != 0 )
+ {
+ wperror( L"fcntl" );
+ close( s );
+
+ return -1;
+ }
+
+ debug( 3, L"Connected to fd %d", s );
+
+ return s;
+}
+
+static void callback( int type, const wchar_t *name, const wchar_t *val )
+{
+ if( type == BARRIER_REPLY )
+ {
+ debug( 3, L"Got barrier reply" );
+
+ barrier_reply = 1;
+ }
+
+}
+
+
+void env_universal_init( wchar_t * p, wchar_t *u, void (*sf)() )
+{
+ debug( 2, L"env_universal_init()" );
+ path=p;
+ user=u;
+ start_fishd=sf;
+ env_universal_server.fd = -1;
+ env_universal_server.killme = 0;
+ env_universal_server.fd = get_socket(1);
+ memset (&env_universal_server.wstate, '\0', sizeof (mbstate_t));
+ q_init( &env_universal_server.unsent );
+ env_universal_common_init( &callback );
+ sb_init( &env_universal_server.input );
+ env_universal_read_all();
+ init = 1;
+ if( env_universal_server.fd >= 0 )
+ {
+ barrier();
+ }
+ debug( 2, L"end env_universal_init()" );
+}
+
+void env_universal_destroy()
+{
+ /*
+ Go into blocking mode and send all data before exiting
+ */
+ if( env_universal_server.fd >= 0 )
+ {
+ if( fcntl( env_universal_server.fd, F_SETFL, 0 ) != 0 )
+ {
+ wperror( L"fcntl" );
+ }
+ try_send_all( &env_universal_server );
+ }
+ close( env_universal_server.fd );
+ env_universal_server.fd =-1;
+ q_destroy( &env_universal_server.unsent );
+ sb_destroy( &env_universal_server.input );
+ env_universal_common_destroy();
+ init = 0;
+}
+
+
+/**
+ Read all available messages from the server.
+*/
+int env_universal_read_all()
+{
+ if( !init)
+ return 0;
+
+ if( env_universal_server.fd == -1 )
+ {
+
+ if( get_socket_count >= RECONNECT_COUNT )
+ return 0;
+
+ debug( 2, L"Get new fishd connection" );
+
+ init = 0;
+ env_universal_server.fd = get_socket(1);
+ init = 1;
+
+ if( env_universal_server.fd >= 0 )
+ barrier();
+ }
+
+ if( env_universal_server.fd != -1 )
+ {
+ read_message( &env_universal_server );
+ if( env_universal_server.killme )
+ {
+ debug( 2, L"Lost connection to universal variable server." );
+ close( env_universal_server.fd );
+ env_universal_server.fd = -1;
+ env_universal_server.killme=0;
+ sb_clear( &env_universal_server.input );
+
+ env_universal_read_all();
+ }
+ return 1;
+ }
+ else
+ {
+ debug( 2, L"No connection to universal variable server" );
+ return 0;
+ }
+}
+
+wchar_t *env_universal_get( const wchar_t *name )
+{
+ if( !init)
+ return 0;
+
+ debug( 2, L"env_universal_get( %ls )", name );
+ barrier();
+
+ if( !name )
+ return 0;
+
+ return (wchar_t *)hash_get( &env_universal_var, name );
+}
+
+static void barrier()
+{
+ message_t *msg;
+ fd_set fds;
+
+ barrier_reply = 0;
+
+ msg= create_message( BARRIER, 0, 0);
+ msg->count=1;
+ q_put( &env_universal_server.unsent, msg );
+
+ debug( 3, L"Create barrier" );
+ while( 1 )
+ {
+ try_send_all( &env_universal_server );
+ if( q_empty( &env_universal_server.unsent ) )
+ break;
+ FD_ZERO( &fds );
+ FD_SET( env_universal_server.fd, &fds );
+ select( env_universal_server.fd+1, 0, &fds, 0, 0 );
+ }
+
+ debug( 3, L"Sent barrier request" );
+ while( !barrier_reply )
+ {
+ FD_ZERO( &fds );
+ FD_SET( env_universal_server.fd, &fds );
+ select( env_universal_server.fd+1, &fds, 0, 0, 0 );
+ env_universal_read_all();
+ }
+ debug( 3, L"End barrier" );
+
+}
+
+
+void env_universal_set( const wchar_t *name, const wchar_t *value )
+{
+ message_t *msg;
+
+ if( !init )
+ return;
+
+ debug( 2, L"env_universal_set( %ls, %ls )", name, value );
+
+ msg= create_message( SET, name, value);
+ msg->count=1;
+ q_put( &env_universal_server.unsent, msg );
+ barrier();
+
+
+}
+
+void env_universal_remove( const wchar_t *name )
+{
+ message_t *msg;
+ if( !init )
+ return;
+
+ debug( 2,
+ L"env_universal_remove( %ls )",
+ name );
+
+ msg= create_message( ERASE, name, 0);
+ msg->count=1;
+ q_put( &env_universal_server.unsent, msg );
+ barrier();
+}
diff --git a/env_universal.h b/env_universal.h
new file mode 100644
index 00000000..6705febd
--- /dev/null
+++ b/env_universal.h
@@ -0,0 +1,39 @@
+/** \file env_universl.h
+ Universal variable client library
+*/
+
+#ifndef ENV_UNIVERSAL_HH
+#define ENV_UNIVERSAL_HH
+
+#include "env_universal_common.h"
+
+/**
+ Data about the universal variable server.
+*/
+extern connection_t env_universal_server;
+
+/**
+ Initialize the envuni library
+*/
+void env_universal_init();
+/*
+ Free memory used by envuni
+*/
+void env_universal_destroy();
+
+/**
+ Get the value of a universal variable
+*/
+wchar_t *env_universal_get( const wchar_t *name );
+/**
+ Set the value of a universal variable
+*/
+void env_universal_set( const wchar_t *name, const wchar_t *val );
+/**
+ Erase a universal variable
+*/
+void env_universal_remove( const wchar_t *name );
+
+int env_universal_read_all();
+
+#endif
diff --git a/env_universal_common.c b/env_universal_common.c
new file mode 100644
index 00000000..3f28ade3
--- /dev/null
+++ b/env_universal_common.c
@@ -0,0 +1,403 @@
+/**
+ \file env_universal_common.c
+
+ The utility library for universal variables. Used both by the
+ client library and by the daemon.
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <strings.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <wctype.h>
+
+#include <errno.h>
+#include <locale.h>
+#include <dirent.h>
+#include <signal.h>
+#include <sys/stat.h>
+
+#include "util.h"
+#include "common.h"
+#include "wutil.h"
+#include "env_universal_common.h"
+
+/**
+ Non-wide version of the set command
+*/
+#define SET_MBS "SET"
+
+/**
+ Non-wide version of the erase command
+*/
+#define ERASE_MBS "ERASE"
+
+#define BARRIER_MBS "BARRIER"
+#define BARRIER_REPLY_MBS "BARRIER_REPLY"
+
+/**
+ Error message
+*/
+#define PARSE_ERR L"Unable to parse universal variable message: '%ls'"
+
+static void parse_message( wchar_t *msg, connection_t *src );
+
+/**
+ The table of all universal variables
+*/
+hash_table_t env_universal_var;
+
+void (*callback)(int type, const wchar_t *key, const wchar_t *val );
+
+
+
+void env_universal_common_init(void (*cb)(int type, const wchar_t *key, const wchar_t *val ) )
+{
+ debug( 2, L"Init env_universal_common" );
+ callback = cb;
+ hash_init( &env_universal_var, &hash_wcs_func, &hash_wcs_cmp );
+}
+
+static void erase( const void *key,
+ const void *data )
+{
+ free( (void *)key );
+ free( (void *)data );
+}
+
+
+void env_universal_common_destroy()
+{
+ hash_foreach( &env_universal_var, &erase );
+ hash_destroy( &env_universal_var );
+}
+
+
+void read_message( connection_t *src )
+{
+ while( 1 )
+ {
+ char b;
+ int read_res = read( src->fd, &b, 1 );
+ wchar_t res=0;
+
+ if( read_res < 0 )
+ {
+ if( errno != EAGAIN &&
+ errno != EINTR )
+ {
+ debug( 2, L"Read error on fd %d, set killme flag", src->fd );
+ wperror( L"read" );
+ src->killme = 1;
+ }
+ return;
+ }
+ if( read_res == 0 )
+ {
+ src->killme = 1;
+ debug( 3, L"Fd %d has reached eof, set killme flag", src->fd );
+ if( src->input.used > 0 )
+ {
+ debug( 1,
+ L"Universal variable connection closed while reading command. Partial command recieved: '%ls'",
+ (wchar_t *)src->input.buff );
+ }
+ return;
+ }
+
+ int sz = mbrtowc( &res, &b, 1, &src->wstate );
+
+ if( sz == -1 )
+ {
+ debug( 1, L"Error while reading universal variable after '%ls'", (wchar_t *)src->input.buff );
+ wperror( L"mbrtowc" );
+ }
+ else if( sz > 0 )
+ {
+ if( res == L'\n' )
+ {
+ parse_message( (wchar_t *)src->input.buff, src );
+ sb_clear( &src->input );
+ memset (&src->wstate, '\0', sizeof (mbstate_t));
+ }
+ else
+ {
+ sb_printf( &src->input, L"%lc", res );
+ }
+ }
+ }
+}
+
+static void remove_entry( wchar_t *name )
+{
+ void *k, *v;
+ hash_remove( &env_universal_var,
+ name,
+ (const void **)&k,
+ (const void **)&v );
+ free( k );
+ free( v );
+}
+
+static int match( const wchar_t *msg, const wchar_t *cmd )
+{
+ size_t len = wcslen( cmd );
+ if( wcsncasecmp( msg, cmd, len ) != 0 )
+ return 0;
+ if( msg[len] && msg[len]!= L' ' && msg[len] != L'\t' )
+ return 0;
+
+ return 1;
+}
+
+
+static void parse_message( wchar_t *msg, connection_t *src )
+{
+ debug( 2, L"parse_message( %ls );", msg );
+
+ if( msg[0] == L'#' )
+ return;
+
+ if( match( msg, SET_STR ) )
+ {
+ wchar_t *name, *val, *tmp;
+
+ name = msg+wcslen(SET_STR);
+ while( wcschr( L"\t ", *name ) )
+ name++;
+
+ tmp = wcschr( name, L':' );
+ if( tmp )
+ {
+ wchar_t *key =malloc( sizeof( wchar_t)*(tmp-name+1));
+ memcpy( key, name, sizeof( wchar_t)*(tmp-name));
+ key[tmp-name]=0;
+
+ val = tmp+1;
+
+ val = unescape( wcsdup(val), 0 );
+
+ remove_entry( key );
+
+ hash_put( &env_universal_var, key, val );
+
+ if( callback )
+ {
+ callback( SET, key, val );
+ }
+ }
+ else
+ {
+ debug( 1, PARSE_ERR, msg );
+ }
+ }
+ else if( match( msg, ERASE_STR ) )
+ {
+ wchar_t *name, *val, *tmp;
+
+ name = msg+wcslen(ERASE_STR);
+ while( wcschr( L"\t ", *name ) )
+ name++;
+
+ tmp = name;
+ while( iswalnum( *tmp ) || *tmp == L'_')
+ tmp++;
+
+ *tmp = 0;
+
+ if( !wcslen( name ) )
+ {
+ debug( 1, PARSE_ERR, msg );
+ }
+
+ remove_entry( name );
+
+ if( callback )
+ {
+ callback( ERASE, name, 0 );
+ }
+ }
+ else if( match( msg, BARRIER_STR) )
+ {
+ message_t *msg = create_message( BARRIER_REPLY, 0, 0 );
+ msg->count = 1;
+ q_put( &src->unsent, msg );
+ try_send_all( src );
+ }
+ else if( match( msg, BARRIER_REPLY_STR ) )
+ {
+ if( callback )
+ {
+ callback( BARRIER_REPLY, 0, 0 );
+ }
+ }
+ else
+ {
+ debug( 1, PARSE_ERR, msg );
+ }
+}
+
+int try_send( message_t *msg, int fd )
+{
+
+ int res = write( fd, msg->body, strlen(msg->body) );
+
+ if( res == -1 )
+ {
+ switch( errno )
+ {
+ case EAGAIN:
+ return 0;
+
+ default:
+ debug( 1,
+ L"Error while sending message to fd %d. Closing connection",
+ fd );
+ wperror( L"write" );
+
+ return -1;
+ }
+ }
+ msg->count--;
+
+ if( !msg->count )
+ {
+ free( msg );
+ }
+ return 1;
+}
+
+void try_send_all( connection_t *c )
+{
+ debug( 2,
+ L"Send all updates to connection on fd %d",
+ c->fd );
+ while( !q_empty( &c->unsent) )
+ {
+
+ switch( try_send( (message_t *)q_peek( &c->unsent), c->fd ) )
+ {
+ case 1:
+ q_get( &c->unsent);
+ break;
+
+ case 0:
+ return;
+
+ case -1:
+ c->killme = 1;
+ return;
+ }
+ }
+}
+
+message_t *create_message( int type,
+ const wchar_t *key_in,
+ const wchar_t *val_in )
+{
+ message_t *msg=0;
+
+ char *key=0;
+ size_t sz;
+
+ if( key_in )
+ {
+ key = wcs2str(key_in);
+ if( !key )
+ {
+ debug( 0,
+ L"Could not convert %ls to narrow character string",
+ key_in );
+ return 0;
+ }
+ }
+
+
+ switch( type )
+ {
+ case SET:
+ {
+ if( !val_in )
+ {
+ val_in=L"";
+ }
+
+ wchar_t *esc = escape(wcsdup(val_in),1);
+ if( !esc )
+ break;
+
+ char *val = wcs2str(esc );
+ free(esc);
+
+
+ sz = strlen(SET_MBS) + strlen(key) + strlen(val) + 4;
+ msg = malloc( sizeof( message_t ) + sz );
+
+ if( !msg )
+ die_mem();
+
+ strcpy( msg->body, SET_MBS " " );
+ strcat( msg->body, key );
+ strcat( msg->body, ":" );
+ strcat( msg->body, val );
+ strcat( msg->body, "\n" );
+
+ free( val );
+
+ break;
+ }
+
+ case ERASE:
+ {
+ sz = strlen(ERASE_MBS) + strlen(key) + 3;
+ msg = malloc( sizeof( message_t ) + sz );
+
+ if( !msg )
+ die_mem();
+
+ strcpy( msg->body, ERASE_MBS " " );
+ strcat( msg->body, key );
+ strcat( msg->body, "\n" );
+ break;
+ }
+
+ case BARRIER:
+ {
+ msg = malloc( sizeof( message_t ) +
+ strlen( BARRIER_MBS ) +2);
+ if( !msg )
+ die_mem();
+ strcpy( msg->body, BARRIER_MBS "\n" );
+ break;
+ }
+
+ case BARRIER_REPLY:
+ {
+ msg = malloc( sizeof( message_t ) +
+ strlen( BARRIER_REPLY_MBS ) +2);
+ if( !msg )
+ die_mem();
+ strcpy( msg->body, BARRIER_REPLY_MBS "\n" );
+ break;
+ }
+
+ default:
+ {
+ debug( 0, L"create_message: Unknown message type" );
+ }
+ }
+
+ free( key );
+
+ if( msg )
+ msg->count=0;
+ return msg;
+}
diff --git a/env_universal_common.h b/env_universal_common.h
new file mode 100644
index 00000000..3b53273b
--- /dev/null
+++ b/env_universal_common.h
@@ -0,0 +1,110 @@
+#ifndef ENV_UNIVERSAL_COMMON_HH
+#define ENV_UNIVERSAL_COMMON_HH
+
+
+/**
+ The set command
+*/
+#define SET_STR L"SET"
+
+/**
+ The erase command
+*/
+#define ERASE_STR L"ERASE"
+
+#define BARRIER_STR L"BARRIER"
+#define BARRIER_REPLY_STR L"BARRIER_REPLY"
+
+
+/**
+ The filename to use for univeral variables. The username is appended
+*/
+#define SOCK_FILENAME "fishd.socket."
+
+/**
+ The different types of commands that can be sent between client/server
+*/
+enum
+{
+ SET,
+ ERASE,
+ BARRIER,
+ BARRIER_REPLY,
+}
+ ;
+
+/**
+ The table of universal variables
+*/
+extern hash_table_t env_universal_var;
+
+/**
+ This struct represents a connection between a universal variable server/client
+*/
+typedef struct connection
+{
+ /**
+ The file descriptor this socket lives on
+ */
+ int fd;
+ /**
+ Queue of onsent messages
+ */
+ queue_t unsent;
+ /**
+ Set to one when this connection should be killed
+ */
+ int killme;
+ /**
+ The state used for character conversions
+ */
+ mbstate_t wstate;
+ /**
+ The input string. Input from the socket goes here. When a
+ newline is encountered, the buffer is parsed and cleared.
+ */
+ string_buffer_t input;
+
+ /**
+ Link to the next connection
+ */
+ struct connection *next;
+}
+ connection_t;
+
+/**
+ A struct representing a message to be sent between client and server
+*/
+typedef struct
+{
+ int count;
+ char body[0];
+}
+ message_t;
+
+/**
+ Read all available messages on this connection
+*/
+void read_message( connection_t * );
+
+/**
+ Send as many messages as possible without blocking to the connection
+*/
+void try_send_all( connection_t *c );
+
+/**
+ Create a messge with the specified properties
+*/
+message_t *create_message( int type, const wchar_t *key, const wchar_t *val );
+
+/**
+ Init the library
+*/
+void env_universal_common_init(void (*cb)(int type, const wchar_t *key, const wchar_t *val ) );
+
+/**
+ Destroy library data
+*/
+void env_universal_common_destroy();
+
+#endif
diff --git a/exec.c b/exec.c
new file mode 100644
index 00000000..e15ec9b0
--- /dev/null
+++ b/exec.c
@@ -0,0 +1,1285 @@
+/** \file exec.c
+ Functions for executing a program.
+
+ Some of the code in this file is based on code from the Glibc
+ manual, though I the changes performed have been massive.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <wchar.h>
+#include <string.h>
+#include <limits.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <dirent.h>
+
+#include "config.h"
+#include "util.h"
+#include "common.h"
+#include "wutil.h"
+#include "proc.h"
+#include "exec.h"
+#include "parser.h"
+#include "builtin.h"
+#include "function.h"
+#include "env.h"
+#include "wildcard.h"
+#include "sanity.h"
+#include "expand.h"
+#include "env_universal.h"
+
+/**
+ Prototype for the getpgid library function. The prototype for this
+ function seems to be missing in glibc, at least I've not found any
+ combination of #includes, macros and compiler switches that will
+ include it.
+*/
+pid_t getpgid( pid_t pid );
+
+/**
+ file descriptor redirection error message
+*/
+#define FD_ERROR L"An error occurred while redirecting file descriptor %d"
+/**
+ file redirection error message
+*/
+#define FILE_ERROR L"An error occurred while redirecting file '%ls'"
+/**
+ pipe redirection error message
+*/
+#define PIPE_ERROR L"An error occurred while setting up pipe"
+/**
+ fork error message
+*/
+#define FORK_ERROR L"Could not create child process - exiting"
+
+/**
+ Make sure the fd used by this redirection is not used by i.e. a pipe.
+*/
+void free_fd( io_data_t *io, int fd )
+{
+ if( !io )
+ return;
+
+ if( io->io_mode == IO_PIPE )
+ {
+ int i;
+ for( i=0; i<2; i++ )
+ {
+ if(io->pipe_fd[i] == fd )
+ {
+ while(1)
+ {
+ if( (io->pipe_fd[i] = dup(fd)) == -1)
+ {
+ if( errno != EINTR )
+ {
+ debug( 1,
+ FD_ERROR,
+ fd );
+ wperror( L"dup" );
+ exit(1);
+ }
+ }
+ else
+ break;
+ }
+ }
+ }
+ }
+}
+
+
+static void handle_child_io( io_data_t *io )
+{
+
+ if( env_universal_server.fd >= 0 )
+ close( env_universal_server.fd );
+
+ for( ; io; io=io->next )
+ {
+ int tmp;
+
+ if( io->io_mode == IO_FD && io->fd == io->old_fd )
+ {
+ continue;
+ }
+
+ if( io->fd > 2 )
+ {
+ /*
+ Make sure the fd used by this redirection is not used by i.e. a pipe.
+ */
+ free_fd( io, io->fd );
+ }
+
+
+ if( close(io->fd) == -1 )
+ {
+/* debug( 1,
+ FD_ERROR,
+ io->fd );
+ wperror( L"close" );
+ exit(1);*/
+ }
+
+ switch( io->io_mode )
+ {
+ case IO_CLOSE:
+ break;
+ case IO_FILE:
+ if( (tmp=wopen( io->filename, io->flags, 0666 ))==-1 )
+ {
+ debug( 1,
+ FILE_ERROR,
+ io->filename );
+
+ wperror( L"open" );
+ exit(1);
+ }
+ else if( tmp != io->fd)
+ {
+ if(dup2( tmp, io->fd ) == -1 )
+ {
+ debug( 1,
+ FD_ERROR,
+ io->fd );
+ wperror( L"dup2" );
+ exit(1);
+ }
+ if( close( tmp ) == -1 )
+ {
+ debug( 1,
+ FD_ERROR,
+ io->fd );
+ wperror( L"close" );
+ exit(1);
+ }
+ }
+ break;
+ case IO_FD:
+/* debug( 3, L"Redirect fd %d in process %ls (%d) from fd %d",
+ io->fd,
+ p->actual_cmd,
+ p->pid,
+ io->old_fd );
+*/
+ if( dup2( io->old_fd, io->fd ) == -1 )
+ {
+ debug( 1,
+ FD_ERROR,
+ io->fd );
+ wperror( L"dup2" );
+ exit(1);
+ }
+ break;
+
+ case IO_BUFFER:
+ case IO_PIPE:
+ {
+
+/* debug( 3, L"Pipe fd %d in process %ls (%d) (Through fd %d)",
+ io->fd,
+ p->actual_cmd,
+ p->pid,
+ io->pipe_fd[io->fd] );
+*/
+ int fd_to_dup = io->fd;//io->io_mode==IO_BUFFER?1:(io->fd?1:0);
+
+ if( dup2( io->pipe_fd[fd_to_dup], io->fd ) == -1 )
+ {
+ debug( 1, PIPE_ERROR );
+ wperror( L"dup2" );
+ exit(1);
+ }
+
+ if( fd_to_dup )
+ {
+ close( io->pipe_fd[0]);
+ close( io->pipe_fd[1]);
+ }
+ else
+ close( io->pipe_fd[fd_to_dup] );
+
+/*
+ if( close( io[i].pipe_fd[ io->fd ] ) == -1 )
+ {
+ wperror( L"close" );
+ exit(1);
+ }
+*/
+/* if( close( io[i].pipe_fd[1] ) == -1 )
+ {
+ wperror( L"close" );
+ exit(1);
+ }
+*/
+
+/* fprintf( stderr, "fd %d points to fd %d\n", i, io[i].fd[i>0?1:0] );*/
+ break;
+ }
+
+ }
+ }
+}
+
+
+static void setup_child_process( job_t *j )
+{
+ if( is_interactive && !is_subshell && !is_block)
+ {
+ pid_t pid;
+ /*
+ Put the process into the process group and give the process group
+ the terminal, if appropriate.
+ This has to be done both by the shell and in the individual
+ child processes because of potential race conditions.
+ */
+ pid = getpid ();
+ if (j->pgid == 0)
+ j->pgid = pid;
+
+ /* Wait till shell puts os in our own group */
+ while( getpgrp() != j->pgid )
+ sleep(0);
+
+ /* Wait till shell gives us stdin */
+ if ( j->fg )
+ {
+ while( tcgetpgrp( 0 ) != j->pgid )
+ sleep(0);
+ }
+ }
+
+ /* Set the handling for job control signals back to the default. */
+ signal (SIGINT, SIG_DFL);
+ signal (SIGQUIT, SIG_DFL);
+ signal (SIGTSTP, SIG_DFL);
+ signal (SIGTTIN, SIG_DFL);
+ signal (SIGTTOU, SIG_DFL);
+ signal (SIGCHLD, SIG_DFL);
+
+ sigset_t chldset;
+ sigemptyset( &chldset );
+ sigaddset( &chldset, SIGCHLD );
+ sigprocmask(SIG_UNBLOCK, &chldset, 0);
+
+ handle_child_io( j->io );
+}
+
+
+
+/**
+ This function is executed by the child process created by a call to
+ fork(). This function begins by updating FDs as specified by the io
+ parameter. If the command requested for this process is not a
+ builtin, execv is then called with the appropriate parameters. If
+ the command is a builtin, the contents of out and err are printed.
+*/
+static void launch_process( process_t *p )
+{
+
+ /* Set the standard input/output channels of the new process. */
+
+ execve (wcs2str(p->actual_cmd), wcsv2strv( (const wchar_t **) p->argv), env_export_arr() );
+ debug( 0,
+ L"Failed to execute process %ls",
+ p->actual_cmd );
+ wperror( L"execve" );
+ exit(1);
+}
+
+
+/**
+ Check if the IO redirection chains contains redirections for the specified file descriptor
+*/
+
+static int has_fd( io_data_t *d, int fd )
+{
+ return io_get( d, fd ) != 0;
+}
+
+
+/**
+ Read from descriptors until they are empty.
+*/
+static void read_all( io_data_t *d )
+{
+
+//io_data_t *d, *prev=0;
+
+/*for( d = io; d; d=d->next )
+ {*/
+
+ if( d->io_mode == IO_BUFFER )
+ {
+
+ if( fcntl( d->pipe_fd[0], F_SETFL, 0 ) )
+ {
+ wperror( L"fcntl" );
+ return;
+ }
+
+ debug( 3, L"read_all: blocking read on fd %d", d->pipe_fd[0] );
+
+ while(1)
+ {
+ char b[4096];
+ int l;
+ l=read_blocked( d->pipe_fd[0], b, 4096 );
+ if( l==0 )
+ {
+
+/*if( prev )
+ {
+ prev->next = d->next;
+ }
+ else
+ {
+ j->io=d->next;
+ }
+ removed = true;*/
+ break;
+
+
+ }
+ else if( l<0 )
+ {
+ debug( 1,
+ L"An error occured while reading output from code block on fd %d", d->pipe_fd[0] );
+ wperror( L"read_all" );
+ break;
+ }
+ else
+ {
+ b_append( d->out_buffer, b, l );
+ }
+ }
+ }
+
+//if( !removed )
+//prev=d;
+//}
+
+//if( current_block->io )
+//fwprintf( stderr, L"read_all ended %ls\n", j->command );
+}
+io_data_t *exec_make_io_buffer()
+{
+ io_data_t *buffer_redirect = malloc( sizeof( io_data_t ));
+
+ buffer_redirect->io_mode=IO_BUFFER;
+ buffer_redirect->next=0;
+ buffer_redirect->out_buffer= malloc( sizeof(buffer_t));
+ b_init( buffer_redirect->out_buffer );
+ buffer_redirect->fd=1;
+
+
+ if( pipe( buffer_redirect->pipe_fd ) == -1 )
+ {
+ debug( 1, PIPE_ERROR );
+ wperror (L"pipe");
+ free( buffer_redirect->out_buffer );
+ free( buffer_redirect );
+ return 0;
+ }
+ else if( fcntl( buffer_redirect->pipe_fd[0], F_SETFL, O_NONBLOCK ) )
+ {
+ debug( 1, PIPE_ERROR );
+ wperror( L"fcntl" );
+ free( buffer_redirect->out_buffer );
+ free( buffer_redirect );
+ return 0;
+ }
+ return buffer_redirect;
+}
+
+void exec_free_io_buffer( io_data_t *io_buffer )
+{
+ if( close( io_buffer->pipe_fd[0] ) == -1)
+ {
+ debug( 1, PIPE_ERROR );
+ wperror( L"close" );
+
+ }
+
+ /*
+ Dont free fd for writing. This should already be free'd before calling read_all on the buffer
+ */
+// close( io_buffer->pipe_fd[1] );
+
+ b_destroy( io_buffer->out_buffer );
+
+ free( io_buffer->out_buffer );
+ free( io_buffer );
+}
+
+
+/**
+ Make a copy of the specified io redirection chain, but change file redirection into fd redirection.
+*/
+
+static io_data_t *io_transmogrify( io_data_t * in )
+{
+ io_data_t *out;
+
+ if( !in )
+ return 0;
+
+ out = malloc( sizeof( io_data_t ) );
+ if( !out )
+ die_mem();
+
+ out->fd = in->fd;
+ out->io_mode = IO_FD;
+ out->close_old = 1;
+
+ switch( in->io_mode )
+ {
+ /*
+ These redirections don't need transmogrification. They can be passed through.
+ */
+ case IO_FD:
+ case IO_CLOSE:
+ case IO_BUFFER:
+ case IO_PIPE:
+ {
+ memcpy( out, in, sizeof(io_data_t));
+ break;
+ }
+
+ /*
+ Transmogrify file redirections
+ */
+ case IO_FILE:
+ {
+ int fd;
+
+ if( (fd=wopen( in->filename, in->flags, 0666 ))==-1 )
+ {
+ debug( 1,
+ FILE_ERROR,
+ in->filename );
+
+ wperror( L"open" );
+ exit(1);
+ }
+
+ out->old_fd = fd;
+/*
+ fwprintf( stderr,
+ L"Replacing call to redirect %d to file '%ls' with call to redirect to fd %d\n",
+ in->fd,
+ in->filename,
+ fd );
+*/
+ break;
+ }
+ }
+
+ out->next = io_transmogrify( in->next );
+
+ return out;
+}
+
+/**
+ Free a transmogrified io chain.
+*/
+static void io_untransmogrify( io_data_t * in, io_data_t *out )
+{
+ if( !in )
+ return;
+ io_untransmogrify( in->next, out->next );
+ switch( in->io_mode )
+ {
+ case IO_FILE:
+ if( close( out->old_fd ) == -1 )
+ {
+ debug( 1,
+ FILE_ERROR,
+ in->filename );
+ wperror( L"close" );
+ }
+ break;
+ }
+ free(out);
+}
+
+
+/**
+ Morph an io redirection chain into redirections suitable for
+ passing to eval, call eval, and clean up morphed redirections.
+
+
+ \param def the code to evaluate
+ \param block_type the type of block to push on evaluation
+ \param io the io redirections to be performed on this block
+*/
+
+static int internal_exec_helper( const wchar_t *def,
+ int block_type,
+ io_data_t *io )
+{
+ int res=0;
+ io_data_t *io_internal = io_transmogrify( io );
+ int is_block_old=is_block;
+ is_block=1;
+
+ eval( def, io_internal, block_type );
+/*
+ io_data_t *buff = io_get( io, 1 );
+ if( buff && buff->io_mode == IO_BUFFER )
+ fwprintf( stderr, L"block %ls produced %d bytes of output\n",
+ def,
+ buff->out_buffer->used );
+*/
+ io_untransmogrify( io, io_internal );
+ job_do_notification();
+ is_block=is_block_old;
+ return res;
+}
+
+/*
+static void io_print( io_data_t *io )
+{
+ if( !io )
+ {
+ fwprintf( stderr, L"\n" );
+ return;
+ }
+
+
+ fwprintf( stderr, L"IO fd %d, type ",
+ io->fd );
+ switch( io->io_mode )
+ {
+ case IO_PIPE:
+ fwprintf(stderr, L"PIPE, data %d\n", io->pipe_fd[io->fd?1:0] );
+ break;
+
+ case IO_FD:
+ fwprintf(stderr, L"FD, copy %d\n", io->old_fd );
+ break;
+
+ case IO_BUFFER:
+ fwprintf( stderr, L"BUFFER\n" );
+ break;
+
+ default:
+ fwprintf( stderr, L"OTHER\n" );
+ }
+
+ io_print( io->next );
+
+}
+*/
+
+static int handle_new_child( job_t *j, process_t *p )
+{
+ /*
+ If we do not output to stdout, builtin IO will not appear.
+ I have no idea why...
+ */
+ //fwprintf( stdout, L"" );
+
+ if(is_interactive && !is_subshell && !is_block)
+ {
+ int new_pgid=0;
+
+ if (!j->pgid)
+ {
+ new_pgid=1;
+ j->pgid = p->pid;
+ }
+
+ if( setpgid (p->pid, j->pgid) )
+ {
+ if( getpgid( p->pid) != j->pgid )
+ {
+ debug( 1,
+ L"Could not send process %d from group %d to group %d",
+ p->pid,
+ getpgid( p->pid),
+ j->pgid );
+ wperror( L"setpgid" );
+ }
+
+ }
+
+ if( j->fg )
+ {
+ while( 1)
+ {
+ if( tcsetpgrp (0, j->pgid) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not send job %d ('%ls')to foreground",
+ j->job_id,
+ j->command );
+ wperror( L"tcsetpgrp" );
+ return -1;
+ }
+ }
+ else
+ break;
+ }
+ }
+
+ if( j->fg && new_pgid)
+ {
+ while( 1 )
+ {
+ if( tcsetpgrp (0, j->pgid) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not send job %d ('%ls')to foreground",
+ j->job_id,
+ j->command );
+ wperror( L"tcsetpgrp" );
+ return -1;
+ }
+ }
+ else
+ break;
+ }
+ }
+
+ }
+ else
+ {
+ j->pgid = getpid();
+ }
+ return 0;
+
+}
+
+
+
+void exec( job_t *j )
+{
+ process_t *p;
+ pid_t pid;
+ int mypipe[2];
+ sigset_t chldset;
+ sigemptyset( &chldset );
+ sigaddset( &chldset, SIGCHLD );
+ int skip_fork;
+
+ /* This call is used so the global environment variable array is regenerated, if needed, before the fork. That way, we avoid a lot of duplicate work where EVERY child would need to generate it */
+ env_export_arr();
+
+ io_data_t pipe_read, pipe_write;
+ io_data_t *tmp;
+
+ io_data_t *io_buffer =0;
+
+// if( j->job_id > 3 )
+
+ debug( 2, L"Exec job %ls with id %d", j->command, j->job_id );
+
+ if( j->first_process->type==INTERNAL_EXEC )
+ {
+ /*
+ Do a regular launch - but without forking first...
+ */
+ setup_child_process( j );
+ launch_process( j->first_process );
+ /*
+ launch_process _never_ returns...
+ */
+ }
+
+
+ pipe_read.fd=0;
+ pipe_write.fd=1;
+ pipe_read.io_mode=IO_PIPE;
+ pipe_read.pipe_fd[0] = -1;
+ pipe_write.io_mode=IO_PIPE;
+ pipe_read.next=0;
+ pipe_write.next=0;
+
+ //fwprintf( stderr, L"Run command %ls\n", j->command );
+
+ if( block_io )
+ {
+// fwprintf( stderr, L"Before\n" );
+// io_print( j->io );
+
+ if( j->io )
+ j->io = io_add( io_duplicate(block_io), j->io );
+ else
+ j->io=io_duplicate(block_io);
+
+// fwprintf( stderr, L"After\n" );
+// io_print( j->io );
+
+ }
+
+/*
+ if true;
+ read foo; read bar; read baz;
+ end <foo.txt
+*/
+
+ j->io = io_add( j->io, &pipe_write );
+
+ for (p = j->first_process; p; p = p->next)
+ {
+ mypipe[1]=-1;
+ skip_fork=0;
+
+ /*
+ Set up fd:s that will be used in the pipe
+ */
+// fwprintf( stderr, L"Create process %ls\n", p->actual_cmd );
+
+ if( p == j->first_process->next )
+ {
+ j->io = io_add( j->io, &pipe_read );
+ }
+
+ if (p->next)
+ {
+ if (pipe( mypipe ) == -1)
+ {
+ debug( 1, PIPE_ERROR );
+ wperror (L"pipe");
+ exit (1);
+ }
+
+/* fwprintf( stderr,
+ L"Make pipe from %ls to %ls using fds %d and %d\n",
+ p->actual_cmd,
+ p->next->actual_cmd,
+ mypipe[0],
+ mypipe[1] );
+*/
+ memcpy( pipe_write.pipe_fd, mypipe, sizeof(int)*2);
+ }
+ else
+ {
+ /*
+ This is the last element of the pipeline.
+ */
+
+ /*
+ Remove the io redirection for pipe output.
+ */
+ j->io = io_remove( j->io, &pipe_write );
+
+ }
+
+ switch( p->type )
+ {
+ case INTERNAL_FUNCTION:
+ {
+ wchar_t **arg;
+ int i;
+ string_buffer_t sb;
+
+ const wchar_t * def = function_get_definition( p->argv[0] );
+// fwprintf( stderr, L"run function %ls\n", argv[0] );
+ if( def == 0 )
+ {
+ debug( 0, L"Unknown function %ls", p->argv[0] );
+ break;
+ }
+ parser_push_block( FUNCTION_CALL );
+
+ if( builtin_count_args(p->argv)>1 )
+ {
+ sb_init( &sb );
+
+ for( i=1,arg = p->argv+1; *arg; i++, arg++ )
+ {
+ if( i != 1 )
+ sb_append( &sb, ARRAY_SEP_STR );
+ sb_append( &sb, *arg );
+ }
+
+ env_set( L"argv", (wchar_t *)sb.buff, ENV_LOCAL );
+ sb_destroy( &sb );
+ }
+ parser_forbid_function( p->argv[0] );
+
+ if( p->next )
+ {
+// fwprintf( stderr, L"Function %ls\n", def );
+ io_buffer = exec_make_io_buffer();
+ j->io = io_add( j->io, io_buffer );
+ }
+
+ internal_exec_helper( def, TOP, j->io );
+
+ parser_allow_function();
+ parser_pop_block();
+
+ break;
+ }
+
+ case INTERNAL_BLOCK:
+ {
+ if( p->next )
+ {
+// fwprintf( stderr, L"Block %ls\n", p->argv[0] );
+ io_buffer = exec_make_io_buffer();
+ j->io = io_add( j->io, io_buffer );
+ }
+
+ internal_exec_helper( p->argv[0], TOP, j->io );
+ break;
+
+ }
+
+ case INTERNAL_BUILTIN:
+ {
+ int builtin_stdin=0;
+ int fg;
+ int close_stdin=0;
+
+ if( p == j->first_process )
+ {
+ io_data_t *in = io_get( j->io, 0 );
+ if( in )
+ {
+ switch( in->io_mode )
+ {
+
+ case IO_FD:
+ {
+ builtin_stdin = in->old_fd;
+/* fwprintf( stderr,
+ L"Input redirection for builtin '%ls' from fd %d\n",
+ p->argv[0],
+ in->old_fd );
+*/
+ break;
+ }
+ case IO_PIPE:
+ {
+ builtin_stdin = in->pipe_fd[0];
+ break;
+ }
+
+ case IO_FILE:
+ {
+ int new_fd;
+/*
+ fwprintf( stderr,
+ L"Input redirection for builtin from file %ls\n",
+ in->filename);
+*/
+ new_fd=wopen( in->filename, in->flags, 0666 );
+ if( new_fd == -1 )
+ {
+ wperror( L"wopen" );
+ }
+ else
+ {
+ builtin_stdin = new_fd;
+
+ close_stdin = 1;
+ }
+
+ break;
+ }
+
+ default:
+ {
+ debug( 1,
+ L"Unknown input redirection type %d",
+ in->io_mode);
+ break;
+ }
+
+ }
+ }
+ }
+ else
+ {
+ builtin_stdin = pipe_read.pipe_fd[0];
+ }
+
+ builtin_push_io( builtin_stdin );
+
+ /*
+ Since this may be the foreground job, and since a
+ builtin may execute another foreground job, we need to
+ pretend to suspend this job while running the builtin.
+ */
+
+ builtin_out_redirect = has_fd( j->io, 1 );
+ builtin_err_redirect = has_fd( j->io, 2 );
+ fg = j->fg;
+ j->fg = 0;
+ p->status = builtin_run( p->argv );
+
+ /*
+ Restore the fg flag, which is temporarily set to
+ false during builtin execution so as not to confuse
+ some job-handling builtins.
+ */
+ j->fg = fg;
+
+
+/* if( is_interactive )
+ fwprintf( stderr, L"Builtin '%ls' finished\n", j->command );
+*/
+ if( close_stdin )
+ {
+// fwprintf( stderr, L"Close builtin_stdin\n" );
+ close( builtin_stdin );
+ }
+ break;
+ }
+ }
+
+
+ switch( p->type )
+ {
+ case INTERNAL_BLOCK:
+ case INTERNAL_FUNCTION:
+ {
+ int status = proc_get_last_status();
+
+ /*
+ Handle output from a block or function. This usually
+ means do nothing, but in the case of pipes, we have
+ to buffer such io, since otherwisethe internal pipe
+ buffer might overflow.
+ */
+ if( !io_buffer)
+ {
+ p->completed = 1;
+ break;
+ }
+
+ j->io = io_remove( j->io, io_buffer );
+
+ if( close( io_buffer->pipe_fd[1] ) == -1 )
+ {
+ debug( 1, PIPE_ERROR );
+ wperror( L"close" );
+
+ }
+
+ debug( 3, L"read_all on block '%ls'", p->argv[0] );
+
+ read_all( io_buffer );
+
+ if( io_buffer->out_buffer->used != 0 )
+ {
+
+ /*Temporary signal block for SIGCHLD */
+ sigprocmask(SIG_BLOCK, &chldset, 0);
+ pid = fork ();
+ if (pid == 0)
+ {
+ /*
+ This is the child process. Write out the contents of the pipeline.
+ */
+ p->pid = getpid();
+ setup_child_process( j );
+ write( io_buffer->fd,
+ io_buffer->out_buffer->buff,
+ io_buffer->out_buffer->used );
+ exit( status );
+ }
+ else if (pid < 0)
+ {
+ /* The fork failed. */
+ debug( 0, FORK_ERROR );
+ wperror (L"fork");
+ exit (1);
+ }
+ else
+ {
+ /*
+ This is the parent process. Store away
+ information on the child, and possibly fice
+ it control over the terminal.
+ */
+ p->pid = pid;
+ if( handle_new_child( j, p ) )
+ return;
+ }
+
+ }
+
+ exec_free_io_buffer( io_buffer );
+
+ io_buffer=0;
+ break;
+
+ }
+
+ case INTERNAL_BUILTIN:
+ {
+ int skip_fork=0;
+
+ /*
+ If a builtin didn't produce any output, and it is not inside a pipeline, there is no need to fork
+ */
+ skip_fork =
+ ( !sb_out->used ) &&
+ ( !sb_err->used ) &&
+ ( !p->next );
+
+ /*
+ If the output of a builtin is to be sent to an internal
+ buffer, there is no need to fork. This helps out the
+ performance quite a bit in complex completion code.
+ */
+
+ io_data_t *io = io_get( j->io, 1 );
+ int buffer_stdout = io && io->io_mode == IO_BUFFER;
+
+ if( ( !sb_err->used ) &&
+ ( !p->next ) &&
+ ( sb_out->used ) &&
+ ( buffer_stdout ) )
+ {
+// fwprintf( stderr, L"Skip fork of %ls\n", j->command );
+ char *res = wcs2str( (wchar_t *)sb_out->buff );
+ b_append( io->out_buffer, res, strlen( res ) );
+ skip_fork = 1;
+ free( res );
+ }
+
+ if( skip_fork )
+ {
+ p->completed=1;
+ if( p->next == 0 )
+ {
+ debug( 3, L"Set status of %ls to %d using short circut", j->command, p->status );
+
+ proc_set_last_status( p->status );
+ proc_set_last_status( j->negate?(p->status?0:1):p->status );
+ }
+ break;
+
+ }
+
+ /*Temporary signal block for SIGCHLD */
+ sigprocmask(SIG_BLOCK, &chldset, 0);
+ pid = fork ();
+ if (pid == 0)
+ {
+ /*
+ This is the child process.
+ */
+ p->pid = getpid();
+ setup_child_process( j );
+ if( sb_out->used )
+ fwprintf( stdout, L"%ls", sb_out->buff );
+ if( sb_err->used )
+ fwprintf( stderr, L"%ls", sb_err->buff );
+
+ exit( p->status );
+
+ }
+ else if (pid < 0)
+ {
+ /* The fork failed. */
+ debug( 0, FORK_ERROR );
+ wperror (L"fork");
+ exit (1);
+ }
+ else
+ {
+ /*
+ This is the parent process. Store away
+ information on the child, and possibly fice
+ it control over the terminal.
+ */
+ p->pid = pid;
+
+ if( handle_new_child( j, p ) )
+ return;
+ }
+
+ break;
+ }
+
+ case EXTERNAL:
+ {
+ /*Temporary signal block for SIGCHLD */
+ sigprocmask(SIG_BLOCK, &chldset, 0);
+
+// fwprintf( stderr,
+// L"fork on %ls\n", j->command );
+ pid = fork ();
+ if (pid == 0)
+ {
+ /*
+ This is the child process.
+ */
+ p->pid = getpid();
+ setup_child_process( j );
+ launch_process( p );
+
+ /*
+ launch_process _never_ returns...
+ */
+ }
+ else if (pid < 0)
+ {
+ /* The fork failed. */
+ debug( 0, FORK_ERROR );
+ wperror (L"fork");
+ exit (1);
+ }
+ else
+ {
+ /*
+ This is the parent process. Store away
+ information on the child, and possibly fice
+ it control over the terminal.
+ */
+ p->pid = pid;
+
+ if( handle_new_child( j, p ) )
+ return;
+
+ }
+ break;
+ }
+
+ }
+
+
+ if(p->type == INTERNAL_BUILTIN)
+ builtin_pop_io();
+
+ sigprocmask(SIG_UNBLOCK, &chldset, 0);
+
+ /*
+ Close the pipe the current process uses to read from the previous process_t
+ */
+ if( pipe_read.pipe_fd[0] >= 0 )
+ close( pipe_read.pipe_fd[0] );
+ /*
+ Set up the pipe the next process uses to read from the current process_t
+ */
+ if( p->next )
+ pipe_read.pipe_fd[0] = mypipe[0];
+
+ /*
+ If there is a next process, close the output end of the
+ pipe (the child subprocess already has a copy of the pipe).
+ */
+ if( p->next )
+ {
+ if( close(mypipe[1]) != 0 )
+ {
+ debug( 1, PIPE_ERROR );
+ wperror( L"close" );
+ }
+ }
+ }
+
+// fwprintf( stderr, L"Job is constructedk\n" );
+
+ j->io = io_remove( j->io, &pipe_read );
+ j->io = io_remove( j->io, &pipe_write );
+
+ for( tmp = block_io; tmp; tmp=tmp->next )
+ j->io = io_remove( j->io, tmp );
+
+
+// assert( !job_is_stopped(j));
+ j->constructed = 1;
+// ggg( j->command, j->io );
+
+ if( !j->fg )
+ {
+ proc_last_bg_pid = j->pgid;
+ }
+
+ job_continue (j, 0);
+
+ debug( 3, L"End of exec()" );
+
+}
+
+int exec_subshell( const wchar_t *cmd,
+ array_list_t *l )
+{
+ char *begin, *end;
+ char z=0;
+ int prev_subshell = is_subshell;
+ int status, prev_status;
+ io_data_t *io_buffer;
+
+ if( !cmd )
+ {
+ debug( 1, L"Sent null command to subshell" );
+ return 0;
+ }
+
+ is_subshell=1;
+ io_buffer= exec_make_io_buffer();
+
+ prev_status = proc_get_last_status();
+
+ eval( cmd, io_buffer, SUBST );
+ close( io_buffer->pipe_fd[1] );
+
+ debug( 4, L"read_all on cmdsub '%ls'", cmd );
+ read_all( io_buffer );
+
+ status = proc_get_last_status();
+ proc_set_last_status( prev_status );
+
+ is_subshell = prev_subshell;
+
+ b_append( io_buffer->out_buffer, &z, 1 );
+
+ begin=end=io_buffer->out_buffer->buff;
+
+ if( l )
+ {
+ while( 1 )
+ {
+ switch( *end )
+ {
+ case 0:
+
+ if( begin != end )
+ {
+ wchar_t *el = str2wcs( begin );
+ if( el )
+ al_push( l, el );
+ }
+ exec_free_io_buffer( io_buffer );
+
+ return status;
+
+ case '\n':
+ {
+ wchar_t *el;
+ *end=0;
+ el = str2wcs( begin );
+ al_push( l, el );
+ begin = end+1;
+ break;
+ }
+ }
+ end++;
+ }
+ }
+
+ exec_free_io_buffer( io_buffer );
+ return status;
+}
+
diff --git a/exec.h b/exec.h
new file mode 100644
index 00000000..d40de335
--- /dev/null
+++ b/exec.h
@@ -0,0 +1,43 @@
+/** \file exec.h
+ Prototypes for functions for executing a program
+*/
+
+/**
+ Execute the processes specified by j.
+
+ I've put a fair bit of work into making builtins behave like other
+ programs as far as pipes are concerned. Unlike i.e. bash, builtins
+ can pipe to other builtins with arbitrary amounts of data, and so
+ on. To do this, after a builtin is run in the real process, it
+ forks and a dummy process is created, responsible for writing the
+ output of the builtin. This is surprisingly cheap on my computer,
+ probably because of the marvels of copy on write forking.
+
+ This rule is short circuted in the case where a builtin does not
+ output to a pipe and does in fact not output anything. The speed
+ improvement from this optimization is not noticable on a normal
+ computer/OS in regular use, but the promiscous amounts of forking
+ that resulted was responsible for a huge slowdown when using
+ Valgrind as well as when doing complex command-specific
+ completions.
+
+
+*/
+void exec( job_t *j );
+
+/**
+ Evaluate the expression cmd in a subshell, add the outputs into the
+ list l. On return, the status flag as returned bu \c
+ proc_gfet_last_status will not be changed.
+
+ \param cmd the command to execute
+ \param l The list to insert output into.If \c l is zero, the output will be discarded.
+
+ \return the status of the last job to exit, or -1 if en error was encountered.
+*/
+int exec_subshell( const wchar_t *cmd,
+ array_list_t *l );
+
+void exec_free_io_buffer( io_data_t *io_buffer );
+io_data_t *exec_make_io_buffer();
+
diff --git a/expand.c b/expand.c
new file mode 100644
index 00000000..93461de2
--- /dev/null
+++ b/expand.c
@@ -0,0 +1,1518 @@
+/**\file expand.c
+
+String expansion functions. These functions perform several kinds of
+parameter expansion.
+
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <string.h>
+#include <wctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <signal.h>
+
+#ifdef SunOS
+#include <procfs.h>
+#endif
+
+#include "config.h"
+#include "util.h"
+#include "common.h"
+#include "wutil.h"
+#include "env.h"
+#include "proc.h"
+#include "parser.h"
+#include "expand.h"
+#include "wildcard.h"
+#include "exec.h"
+#include "tokenizer.h"
+#include "complete.h"
+
+/**
+ Description for child process
+*/
+#define COMPLETE_CHILD_PROCESS_DESC COMPLETE_SEP_STR L"Child process"
+
+/**
+ Description for non-child process
+*/
+#define COMPLETE_PROCESS_DESC COMPLETE_SEP_STR L"Process"
+
+/**
+ Description for long job
+*/
+#define COMPLETE_JOB_DESC COMPLETE_SEP_STR L"Job"
+
+/**
+ Description for short job. The job command is concatenated
+*/
+#define COMPLETE_JOB_DESC_VAL COMPLETE_SEP_STR, L"Job: "
+
+/**
+ Description for the shells own pid
+*/
+#define COMPLETE_SELF_DESC COMPLETE_SEP_STR L"Shell process"
+
+/**
+ Description for the shells own pid
+*/
+#define COMPLETE_LAST_DESC COMPLETE_SEP_STR L"Last background job"
+
+/**
+ String in process expansion denoting ourself
+*/
+#define SELF_STR L"self"
+
+/**
+ String in process expansion denoting last background job
+*/
+#define LAST_STR L"last"
+
+/**
+ Return the environment variable value for the string starting at in
+*/
+static wchar_t *expand_var( wchar_t *in )
+{
+ if( !in )
+ return 0;
+ return (in[0] == VARIABLE_EXPAND )? env_get( expand_var(in+1) ) : env_get( in );
+}
+
+void expand_variable_array( const wchar_t *val, array_list_t *out )
+{
+ if( val )
+ {
+ wchar_t *cpy = wcsdup( val );
+ wchar_t *pos, *start;
+
+ if( !cpy )
+ {
+ die_mem();
+
+ }
+
+ for( start=pos=cpy; *pos; pos++ )
+ {
+ if( *pos == ARRAY_SEP )
+ {
+ *pos=0;
+ al_push( out, wcsdup(start) );
+ start=pos+1;
+ }
+ }
+ al_push( out, wcsdup(start) );
+
+ free(cpy);
+ }
+}
+
+wchar_t *expand_escape( wchar_t *in,
+ int escape_all )
+{
+ return escape( in, escape_all );
+}
+
+
+/**
+ Test if the specified string does not contain character which can
+ not be used inside a quoted string.
+*/
+static int is_quotable( wchar_t *str )
+{
+ switch( *str )
+ {
+ case 0:
+ return 1;
+
+ case L'\n':
+ case L'\t':
+ case L'\r':
+ case L'\b':
+ case L'\e':
+ return 0;
+
+ default:
+ return is_quotable(str+1);
+ }
+ return 0;
+
+}
+
+wchar_t *expand_escape_variable( const wchar_t *in )
+{
+
+ array_list_t l;
+ string_buffer_t buff;
+
+ al_init( &l );
+ expand_variable_array( in, &l );
+ sb_init( &buff );
+
+ switch( al_get_count( &l) )
+ {
+ case 0:
+ sb_append( &buff, L"\'\'");
+ break;
+
+ case 1:
+ {
+ wchar_t *el = (wchar_t *)al_get( &l, 0 );
+
+ if( wcschr( el, L' ' ) && is_quotable( el ) )
+ {
+ sb_append2( &buff,
+ L"\'",
+ el,
+ L"\'",
+ 0 );
+ }
+ else
+ {
+ wchar_t *val = expand_escape( wcsdup(el), 1 );
+ sb_append( &buff, val );
+ free( val );
+ }
+ free( el );
+ break;
+ }
+ default:
+ {
+ int j;
+ for( j=0; j<al_get_count( &l ); j++ )
+ {
+ wchar_t *el = (wchar_t *)al_get( &l, j );
+ if( j )
+ sb_append( &buff, L" " );
+
+ if( is_quotable( el ) )
+ {
+ sb_append2( &buff,
+ L"\'",
+ el,
+ L"\'",
+ 0 );
+ }
+ else
+ {
+ wchar_t *val = expand_escape( wcsdup(el), 1 );
+ sb_append( &buff, val );
+ free( val );
+ }
+
+ free( el );
+ }
+ }
+ }
+ al_destroy( &l );
+
+ return (wchar_t *)buff.buff;
+
+}
+
+/**
+ Tests if all characters in the string are numeric
+*/
+static int isnumeric( const char *n )
+{
+ if( *n == '\0' )
+ return 1;
+ if( *n < '0' || *n > '9' )
+ return 0;
+ return isnumeric( n+1 );
+}
+
+/**
+ Tests if all characters in the wide string are numeric
+*/
+static int iswnumeric( const wchar_t *n )
+{
+ if( *n == L'\0' )
+ return 1;
+ if( *n < L'0' || *n > L'9' )
+ return 0;
+ return iswnumeric( n+1 );
+}
+
+/**
+ See if the process described by \c proc matches the commandline \c
+ cmd
+*/
+static int match_pid( const wchar_t *cmd,
+ const wchar_t *proc,
+ int flags )
+{
+ /* Test for direct match */
+
+ if( wcsncmp( cmd, proc, wcslen( proc ) ) == 0 )
+ return 1;
+
+ if( flags & ACCEPT_INCOMPLETE )
+ return 0;
+
+ /*
+ Test if the commandline is a path to the command, if so we try
+ to match against only the command part
+ */
+ wchar_t *first_token = tok_first( cmd );
+ if( first_token == 0 )
+ return 0;
+
+ wchar_t *start=0;
+ wchar_t prev=0;
+ wchar_t *p;
+
+ /*
+ This should be done by basename(), if it wasn't for the fact
+ that is does not accept wide strings
+ */
+ for( p=first_token; *p; p++ )
+ {
+ if( *p == L'/' && prev != L'\\' )
+ start = p;
+ prev = *p;
+ }
+ if( start )
+ {
+
+ if( wcsncmp( start+1, proc, wcslen( proc ) ) == 0 )
+ {
+ free( first_token );
+ return 1;
+ }
+ }
+ free( first_token );
+
+ return 0;
+
+}
+
+/**
+ Searches for a job with the specified job id, or a job or process
+ which has the string \c proc as a prefix of its commandline.
+
+ If accept_incomplete is true, the remaining string for any matches are inserted.
+
+ If accept_incomplete is false, any job matching the specified
+ string is matched, and the job pgid is returned. If no job
+ matches, all child processes are searched. If no child processes
+ match, and <tt>fish</tt> can understand the contents of the /proc
+ filesystem, all the users processes are searched for matches.
+*/
+
+static int find_process( const wchar_t *proc,
+ int flags,
+ array_list_t *out )
+{
+ DIR *dir;
+ struct dirent *next;
+ char *pdir_name;
+ char *pfile_name;
+ wchar_t *cmd=0;
+ int sz=0;
+ int found = 0;
+ wchar_t *result;
+
+ job_t *j;
+
+
+
+ if( iswnumeric(proc) || (wcslen(proc)==0) )
+ {
+ /*
+ This is a numeric job string, like '%2'
+ */
+
+ // fwprintf( stderr, L"Numeric\n\n\n" );
+
+ if( flags & ACCEPT_INCOMPLETE )
+ {
+ for( j=first_job; j != 0; j=j->next )
+ {
+ wchar_t jid[16];
+ if( j->command == 0 )
+ continue;
+
+ swprintf( jid, 16, L"%d", j->job_id );
+// fwprintf( stderr, L"Jid %ls\n", jid );
+
+ if( wcsncmp( proc, jid, wcslen(proc ) )==0 )
+ {
+ al_push( out,
+ wcsdupcat2( jid+wcslen(proc),
+ COMPLETE_JOB_DESC_VAL, j->command,
+ 0 ) );
+
+
+ }
+ }
+
+ }
+ else
+ {
+
+ int jid = wcstol( proc, 0, 10 );
+
+ j = job_get( jid );
+ if( (j != 0) && (j->command != 0 ) )
+ {
+
+ {
+ result = malloc(sizeof(wchar_t)*16 );
+ swprintf( result, 16, L"%d", j->pgid );
+ //fwprintf( stderr, L"pid %d %ls\n", j->pgid, result );
+ al_push( out, result );
+ found = 1;
+ }
+ }
+ }
+ }
+ if( found )
+ return 1;
+
+ for( j=first_job; j != 0; j=j->next )
+ {
+// fwprintf( stderr, L"..." );
+ if( j->command == 0 )
+ continue;
+
+ // fwprintf( stderr, L"match '%ls' '%ls'\n\n\n", j->command, proc );
+
+ if( match_pid( j->command, proc, flags ) )
+ {
+ if( flags & ACCEPT_INCOMPLETE )
+ {
+ wchar_t *res = wcsdupcat( j->command + wcslen(proc),
+ COMPLETE_JOB_DESC );
+// fwprintf( stderr, L"Woot %ls\n", res );
+
+ al_push( out, res );
+ }
+ else
+ {
+ result = malloc(sizeof(wchar_t)*16 );
+ swprintf( result, 16, L"%d", j->pgid );
+ al_push( out, result );
+ found = 1;
+ }
+ }
+ }
+
+ if( found )
+ {
+ return 1;
+ }
+
+ for( j=first_job; j; j=j->next )
+ {
+ process_t *p;
+ if( j->command == 0 )
+ continue;
+ for( p=j->first_process; p; p=p->next )
+ {
+
+// fwprintf( stderr, L"..." );
+ if( p->actual_cmd == 0 )
+ continue;
+
+ // fwprintf( stderr, L"match '%ls' '%ls'\n\n\n", j->command, proc );
+
+ if( match_pid( p->actual_cmd, proc, flags ) )
+ {
+ if( flags & ACCEPT_INCOMPLETE )
+ {
+ wchar_t *res = wcsdupcat( p->actual_cmd + wcslen(proc),
+ COMPLETE_CHILD_PROCESS_DESC );
+ al_push( out, res );
+ }
+ else
+ {
+ result = malloc(sizeof(wchar_t)*16 );
+ swprintf( result, 16, L"%d", p->pid );
+ al_push( out, result );
+ found = 1;
+ }
+ }
+ }
+ }
+
+ if( found )
+ {
+ return 1;
+ }
+
+ if( !(dir = opendir( "/proc" )))
+ {
+ /*
+ This system does not have a /proc filesystem.
+ */
+ return 1;
+ }
+
+ pdir_name = malloc( 256 );
+ pfile_name = malloc( 64 );
+ strcpy( pdir_name, "/proc/" );
+
+ while( (next=readdir(dir))!=0 )
+ {
+ char *name = next->d_name;
+ struct stat buf;
+
+ if( !isnumeric( name ) )
+ continue;
+
+ strcpy( pdir_name + 6, name );
+ if( stat( pdir_name, &buf ) )
+ {
+ continue;
+ }
+ if( buf.st_uid != getuid() )
+ {
+ continue;
+ }
+ strcpy( pfile_name, pdir_name );
+ strcat( pfile_name, "/cmdline" );
+
+ if( !stat( pfile_name, &buf ) )
+ {
+ /*
+ the 'cmdline' file exists, it should contain the commandline
+ */
+ FILE *cmdfile;
+
+ if((cmdfile=fopen( pfile_name, "r" ))==0)
+ {
+ wperror( L"fopen" );
+ continue;
+ }
+
+ fgetws2( &cmd, &sz, cmdfile );
+
+ fclose( cmdfile );
+ }
+ else
+ {
+#ifdef SunOS
+ strcpy( pfile_name, pdir_name );
+ strcat( pfile_name, "/psinfo" );
+ if( !stat( pfile_name, &buf ) )
+ {
+ psinfo_t info;
+
+ FILE *psfile;
+
+ if((psfile=fopen( pfile_name, "r" ))==0)
+ {
+ wperror( L"fopen" );
+ continue;
+ }
+
+ if( fread( &info, sizeof(info), 1, psfile ) )
+ {
+ if( cmd != 0 )
+ free( cmd );
+ cmd = str2wcs( info.pr_fname );
+ }
+ fclose( psfile );
+ }
+ else
+#endif
+ {
+ if( cmd != 0 )
+ {
+ *cmd = 0;
+ }
+ }
+ }
+
+ if( cmd != 0 )
+ {
+ if( match_pid( cmd, proc, flags ) )
+ {
+ if( flags & ACCEPT_INCOMPLETE )
+ {
+ wchar_t *res = wcsdupcat( cmd + wcslen(proc),
+ COMPLETE_PROCESS_DESC );
+ if( res )
+ al_push( out, res );
+ }
+ else
+ {
+ wchar_t *res = str2wcs(name);
+ if( res )
+ al_push( out, str2wcs( name ) );
+ }
+ }
+ }
+ }
+
+ if( cmd != 0 )
+ free( cmd );
+
+ free( pdir_name );
+ free( pfile_name );
+
+ closedir( dir );
+
+ return 1;
+}
+
+/**
+ Process id expansion
+*/
+static int expand_pid( wchar_t *in,
+ int flags,
+ array_list_t *out )
+{
+ if( *in != PROCESS_EXPAND )
+ {
+ al_push( out, in );
+ return 1;
+ }
+
+ if( flags & ACCEPT_INCOMPLETE )
+ {
+ if( wcsncmp( in+1, SELF_STR, wcslen(in+1) )==0 )
+ {
+ wchar_t *res = wcsdupcat( SELF_STR+wcslen(in+1), COMPLETE_SELF_DESC );
+ al_push( out, res );
+ }
+ else if( wcsncmp( in+1, LAST_STR, wcslen(in+1) )==0 )
+ {
+ wchar_t *res = wcsdupcat( LAST_STR+wcslen(in+1), COMPLETE_LAST_DESC );
+ al_push( out, res );
+ }
+ }
+ else
+ {
+ if( wcscmp( (in+1), SELF_STR )==0 )
+ {
+ wchar_t *str= malloc( sizeof(wchar_t)*32);
+ free(in);
+ swprintf( str, 32, L"%d", getpid() );
+ al_push( out, str );
+
+ return 1;
+ }
+ if( wcscmp( (in+1), LAST_STR )==0 )
+ {
+ wchar_t *str;
+
+ if( proc_last_bg_pid > 0 )
+ {
+ str = malloc( sizeof(wchar_t)*32);
+ free(in);
+ swprintf( str, 32, L"%d", proc_last_bg_pid );
+ al_push( out, str );
+ }
+
+ return 1;
+ }
+ }
+
+// fwprintf( stderr, L"expand_pid() %ls\n", in );
+ int prev = al_get_count( out );
+ if( !find_process( in+1, flags, out ) )
+ return 0;
+
+ if( prev == al_get_count( out ) )
+ {
+// fwprintf( stderr, L"no match\n" );
+
+ if( flags & ACCEPT_INCOMPLETE )
+ free( in );
+ else
+ {
+ *in = L'%';
+// fwprintf( stderr, L"return %ls\n", in );
+ al_push( out, in );
+ }
+ }
+ else
+ {
+// fwprintf( stderr, L"match\n" );
+ free( in );
+ }
+
+ return 1;
+}
+
+
+/**
+ Expand all environment variables in the string *ptr.
+*/
+static int expand_variables( wchar_t *in, array_list_t *out )
+{
+ wchar_t c;
+ wchar_t prev_char=0;
+ int i, j;
+ int is_ok= 1;
+ int empty=0;
+
+
+ for( i=wcslen(in)-1; (i>=0) && is_ok && !empty; i-- )
+ {
+ c = in[i];
+ if( c == VARIABLE_EXPAND )
+ {
+ int start_pos = i+1;
+ int stop_pos;
+ int var_len, new_len;
+ wchar_t *var_name;
+ wchar_t * var_val;
+ wchar_t * new_in;
+ array_list_t l;
+
+
+
+// fwprintf( stderr, L"Expand %ls\n", in );
+
+
+// while (in[stop_pos]==VARIABLE_EXPAND)
+// stop_pos++;
+
+ stop_pos = start_pos;
+
+ while( 1 )
+ {
+ if( !(in[stop_pos ]) )
+ break;
+ if( !( iswalnum( in[stop_pos] ) ||
+ (wcschr(L"_", in[stop_pos])!= 0) ) )
+ break;
+
+ stop_pos++;
+ }
+
+/* printf( "Stop for '%c'\n", in[stop_pos]);*/
+
+ var_len = stop_pos - start_pos;
+
+ if( !(var_name = malloc( sizeof(wchar_t)*(var_len+1) )))
+ {
+ die_mem();
+ }
+ else
+ {
+ wcsncpy( var_name, &in[start_pos], var_len );
+ var_name[var_len]='\0';
+/* printf( "Variable name is %s, len is %d\n", var_name, var_len );*/
+ wchar_t *var_val_orig = expand_var( var_name );
+
+ if( var_val_orig && (var_val = wcsdup( var_val_orig) ) )
+ {
+ int all_vars=1;
+ array_list_t idx;
+ al_init( &idx );
+ al_init( &l );
+
+ if( in[stop_pos] == L'[' )
+ {
+ wchar_t *end;
+
+ all_vars = 0;
+
+ stop_pos++;
+ while( 1 )
+ {
+ int tmp;
+
+ while( iswspace(in[stop_pos]) || (in[stop_pos]==INTERNAL_SEPARATOR))
+ stop_pos++;
+
+
+ if( in[stop_pos] == L']' )
+ {
+ stop_pos++;
+ break;
+ }
+
+ errno=0;
+ tmp = wcstol( &in[stop_pos], &end, 10 );
+ if( ( errno ) || ( end == &in[stop_pos] ) )
+ {
+ error( SYNTAX_ERROR,
+ L"Expected integer or \']\'",
+ -1 );
+ is_ok = 0;
+ break;
+ }
+ al_push( &idx, (void *)tmp );
+ stop_pos = end-in;
+ }
+ }
+
+ if( is_ok )
+ {
+ expand_variable_array( var_val, &l );
+ if( !all_vars )
+ {
+ int j;
+ for( j=0; j<al_get_count( &idx ); j++)
+ {
+ int tmp = (int)al_get( &idx, j );
+ if( tmp < 1 || tmp > al_get_count( &l ) )
+ {
+ error( SYNTAX_ERROR, L"Array index out of bounds", -1 );
+ is_ok=0;
+ al_truncate( &idx, j );
+ break;
+ }
+ else
+ {
+ /* Move string from list l to list idx */
+ al_set( &idx, j, al_get( &l, tmp-1 ) );
+ al_set( &l, tmp-1, 0 );
+ }
+ }
+ /* Free remaining strings in list l and truncate it */
+ al_foreach( &l, (void (*)(const void *))&free );
+ al_truncate( &l, 0 );
+ /* Add items from list idx back to list l */
+ al_push_all( &l, &idx );
+ }
+ free( var_val );
+ }
+
+ for( j=0; j<al_get_count( &l); j++ )
+ {
+ wchar_t *next = (wchar_t *)al_get( &l, j );
+
+ if( is_ok )
+ {
+
+ new_len = wcslen(in) - (stop_pos-start_pos+1) + wcslen( next) +2;
+
+ if( !(new_in = malloc( sizeof(wchar_t)*new_len )))
+ {
+ error( OOM, L"Out of memory", -1 );
+ is_ok = 0;
+ }
+ else
+ {
+
+ wcsncpy( new_in, in, start_pos-1 );
+
+ if(start_pos>1 && new_in[start_pos-2]!=VARIABLE_EXPAND)
+ {
+ new_in[start_pos-1]=INTERNAL_SEPARATOR;
+ new_in[start_pos]=L'\0';
+ }
+ else
+ new_in[start_pos-1]=L'\0';
+
+ wcscat( new_in, next );
+ wcscat( new_in, &in[stop_pos] );
+
+// fwprintf( stderr, L"New value %ls\n", new_in );
+ is_ok &= expand_variables( new_in, out );
+ }
+ }
+ free( next );
+ }
+ al_destroy( &l );
+ al_destroy( &idx );
+ free(in);
+ free(var_name );
+ return is_ok;
+ }
+ else
+ {
+ empty = 1;
+ }
+
+ free(var_name );
+ }
+ }
+ prev_char = c;
+ }
+
+ if( !empty )
+ {
+ al_push( out, in );
+ }
+ else
+ {
+ free( in );
+ }
+
+ return is_ok;
+}
+
+/**
+ Perform bracket expansion
+*/
+static int expand_brackets( wchar_t *in, int flags, array_list_t *out )
+{
+ wchar_t *pos;
+ int syntax_error=0;
+ int bracket_count=0;
+
+ wchar_t *bracket_begin=0, *bracket_end=0;
+ wchar_t *last_sep=0;
+
+ wchar_t *item_begin;
+ int len1, len2, tot_len;
+
+// fwprintf( stderr, L"expand %ls\n", in );
+
+
+ for( pos=in;
+ (!bracket_end) && (*pos) && !syntax_error;
+ pos++ )
+ {
+ switch( *pos )
+ {
+ case BRACKET_BEGIN:
+ {
+ if(( bracket_count == 0)&&(bracket_begin==0))
+ bracket_begin = pos;
+
+ bracket_count++;
+ break;
+
+ }
+ case BRACKET_END:
+ {
+ bracket_count--;
+ if( (bracket_count == 0) && (bracket_end == 0) )
+ bracket_end = pos;
+
+ if( bracket_count < 0 )
+ {
+ syntax_error = 1;
+ }
+ break;
+ }
+ case BRACKET_SEP:
+ {
+ if( bracket_count == 1 )
+ last_sep = pos;
+ }
+ }
+ }
+
+ if( bracket_count > 0 )
+ {
+ if( !(flags & ACCEPT_INCOMPLETE) )
+ syntax_error = 1;
+ else
+ {
+
+ string_buffer_t mod;
+ sb_init( &mod );
+ if( last_sep )
+ {
+ sb_append_substring( &mod, in, bracket_begin-in+1 );
+ sb_append( &mod, last_sep+1 );
+ sb_append_char( &mod, BRACKET_END );
+ }
+ else
+ {
+ sb_append( &mod, in );
+ sb_append_char( &mod, BRACKET_END );
+ }
+
+
+
+
+ return expand_brackets( (wchar_t*)mod.buff, 1, out );
+ }
+ }
+
+ if( syntax_error )
+ {
+ error( SYNTAX_ERROR, L"Mismatched brackets", -1 );
+ return 0;
+ }
+
+ if( bracket_begin == 0 )
+ {
+ al_push( out, in );
+ return 1;
+ }
+
+ len1 = (bracket_begin-in);
+ len2 = wcslen(bracket_end)-1;
+ tot_len = len1+len2;
+ item_begin = bracket_begin+1;
+ for( pos=(bracket_begin+1); 1; pos++ )
+ {
+ if( bracket_count == 0 )
+ {
+ if( (*pos == BRACKET_SEP) || (pos==bracket_end) )
+ {
+ wchar_t *whole_item;
+ int item_len = pos-item_begin;
+
+ whole_item = malloc( sizeof(wchar_t)*(tot_len + item_len + 1) );
+ wcsncpy( whole_item, in, len1 );
+ wcsncpy( whole_item+len1, item_begin, item_len );
+ wcscpy( whole_item+len1+item_len, bracket_end+1 );
+
+ expand_brackets( whole_item, flags, out );
+
+ item_begin = pos+1;
+ if( pos == bracket_end )
+ break;
+ }
+ }
+
+ if( *pos == BRACKET_BEGIN )
+ {
+ bracket_count++;
+ }
+
+ if( *pos == BRACKET_END )
+ {
+ bracket_count--;
+ }
+ }
+ free(in);
+ return 1;
+}
+
+int expand_locate_subshell( wchar_t *in,
+ wchar_t **begin,
+ wchar_t **end,
+ int allow_incomplete )
+{
+ wchar_t *pos;
+ wchar_t prev=0;
+ int syntax_error=0;
+ int paran_count=0;
+
+ wchar_t *paran_begin=0, *paran_end=0;
+
+ for( pos=in; *pos; pos++ )
+ {
+ if( prev != '\\' )
+ {
+ if( wcschr( L"\'\"", *pos ) )
+ {
+ wchar_t *end = quote_end( pos );
+ if( end && *end)
+ {
+ pos=end;
+ }
+ else
+ break;
+ }
+ else
+ {
+ if( *pos == '(' )
+ {
+ if(( paran_count == 0)&&(paran_begin==0))
+ paran_begin = pos;
+
+ paran_count++;
+ }
+ else if( *pos == ')' )
+ {
+ paran_count--;
+ if( (paran_count == 0) && (paran_end == 0) )
+ {
+ paran_end = pos;
+ break;
+ }
+
+ if( paran_count < 0 )
+ {
+ syntax_error = 1;
+ break;
+ }
+ }
+ }
+
+ }
+
+ prev = *pos;
+ }
+
+ syntax_error |= (paran_count < 0 );
+ syntax_error |= ((paran_count>0)&&(!allow_incomplete));
+
+ if( syntax_error )
+ {
+ return -1;
+ }
+
+ if( paran_begin == 0 )
+ {
+ return 0;
+ }
+
+ *begin = paran_begin;
+ *end = paran_count?in+wcslen(in):paran_end;
+
+ return 1;
+
+}
+
+/**
+ Perform subshell expansion
+*/
+static int expand_subshell( wchar_t *in, array_list_t *out )
+{
+ wchar_t *paran_begin=0, *paran_end=0;
+ int len1, len2;
+ wchar_t prev=0;
+ wchar_t *subcmd;
+ array_list_t sub_res, tail_expand;
+ int i, j;
+ wchar_t *item_begin;
+
+ switch( expand_locate_subshell(in,
+ &paran_begin,
+ &paran_end,
+ 0 ) )
+ {
+ case -1:
+ error( SYNTAX_ERROR, L"Mismatched parans", -1 );
+ return 0;
+ case 0:
+ al_push( out, in );
+ return 1;
+ case 1:
+
+ break;
+
+ }
+
+
+
+ len1 = (paran_begin-in);
+ len2 = wcslen(paran_end)-1;
+ prev=0;
+ item_begin = paran_begin+1;
+
+ al_init( &sub_res );
+ if( !(subcmd = malloc( sizeof(wchar_t)*(paran_end-paran_begin) )))
+ {
+ al_destroy( &sub_res );
+ return 0;
+ }
+
+ wcsncpy( subcmd, paran_begin+1, paran_end-paran_begin-1 );
+ subcmd[ paran_end-paran_begin-1]=0;
+
+ if( exec_subshell( subcmd, &sub_res)==-1 )
+ {
+ al_foreach( &sub_res, (void (*)(const void *))&free );
+ al_destroy( &sub_res );
+ free( subcmd );
+ return 0;
+ }
+
+ al_init( &tail_expand );
+ expand_subshell( wcsdup(paran_end+1), &tail_expand );
+
+ for( i=0; i<al_get_count( &sub_res ); i++ )
+ {
+ wchar_t *sub_item;
+ sub_item = (wchar_t *)al_get( &sub_res, i );
+ sub_item = expand_escape( sub_item, 1 );
+ int item_len = wcslen( sub_item );
+
+ for( j=0; j<al_get_count( &tail_expand ); j++ )
+ {
+ string_buffer_t whole_item;
+ wchar_t *tail_item = (wchar_t *)al_get( &tail_expand, j );
+
+ sb_init( &whole_item );
+
+ sb_append_substring( &whole_item, in, len1 );
+ sb_append_char( &whole_item, INTERNAL_SEPARATOR );
+ sb_append_substring( &whole_item, sub_item, item_len );
+ sb_append_char( &whole_item, INTERNAL_SEPARATOR );
+ sb_append( &whole_item, tail_item );
+
+ al_push( out, whole_item.buff );
+ }
+
+ free( sub_item );
+ }
+ free(in);
+
+ al_destroy( &sub_res );
+
+ al_foreach( &tail_expand, (void (*)(const void *))&free );
+ al_destroy( &tail_expand );
+
+ free( subcmd );
+ return 1;
+}
+
+
+wchar_t *expand_backslash( wchar_t * in, int escape_special )
+{
+ return unescape( in, escape_special );
+}
+
+/**
+ Attempts tilde expansion. Of the string specified. If tilde
+ expansion is performed, the argument is freed and a new string is
+ allocated in its place. Horrible call signature. Should be
+ altered. Fugly!
+*/
+static int tilde_expand( wchar_t **ptr )
+{
+ wchar_t *in = *ptr;
+
+ if( in[0] == HOME_DIRECTORY )
+ {
+ int tilde_error = 0;
+ wchar_t *home=0;
+ wchar_t *new_in;
+ wchar_t *old_in;
+
+// fwprintf( stderr, L"Tilde expand ~%ls\n", (*ptr)+1 );
+ if( in[1] == '/' || in[1] == '\0' )
+ {
+ /* Current users home directory */
+ struct passwd *userinfo;
+
+ home = env_get( L"HOME" );
+ if( home )
+ home = wcsdup(home);
+ else
+ home = wcsdup(L"");
+
+ if( !home )
+ {
+ *in = L'~';
+ tilde_error = 1;
+ }
+
+ old_in = &in[1];
+ }
+ else
+ {
+ /* Some other users home directory */
+ wchar_t *name;
+ wchar_t *name_end = wcschr( in, L'/' );
+ if( name_end == 0 )
+ {
+ name_end = in+wcslen( in );
+ }
+ name = wcsndup( in+1, name_end-in-1 );
+ old_in = name_end;
+
+ char *name_str = wcs2str( name );
+
+ struct passwd *userinfo =
+ getpwnam( name_str );
+
+ if( userinfo == 0 )
+ {
+ tilde_error = 1;
+ *in = L'~';
+ }
+ else
+ {
+ home = str2wcs(userinfo->pw_dir);
+ if( !home )
+ {
+ *in = L'~';
+ tilde_error = 1;
+ }
+ }
+
+ free( name_str );
+ free(name);
+ }
+
+ if( !tilde_error )
+ {
+ new_in = wcsdupcat( home, old_in );
+ free( in );
+ in = new_in;
+ free(home);
+ *ptr = in;
+ }
+
+ }
+ return 1;
+}
+
+wchar_t *expand_tilde(wchar_t *in)
+{
+ if( in[0] == L'~' )
+ {
+ in[0] = HOME_DIRECTORY;
+ tilde_expand( &in );
+ return in;
+ }
+ return in;
+}
+
+/**
+ Remove any internal separators. Also optionally convert wildcard characters to
+ regular equivalents. This is done to support EXPAN_SKIP_WILDCARDS.
+*/
+static void remove_internal_separator( const void *s, int conv )
+{
+ wchar_t *in = (wchar_t *)s;
+ wchar_t *out=in;
+
+// int changed=0;
+
+ while( *in )
+ {
+ switch( *in )
+ {
+ case INTERNAL_SEPARATOR:
+ in++;
+ break;
+
+ case ANY_CHAR:
+ in++;
+ *out++ = conv?L'?':ANY_CHAR;
+ break;
+
+ case ANY_STRING:
+ in++;
+ *out++ = conv?L'*':ANY_STRING;
+ break;
+
+ default:
+ *out++ = *in++;
+ }
+ }
+ *out=0;
+/* if( changed )
+ {
+ fwprintf( stderr, L" -> %ls\n", s );
+ }
+*/
+}
+
+
+/**
+ The real expansion function. All other expansion functions are wrappers to this one.
+*/
+int expand_string( wchar_t *str,
+ array_list_t *end_out,
+ int flags )
+{
+ array_list_t list1, list2;
+ array_list_t *in, *out;
+
+ int i;
+ int subshell_ok = 1;
+
+ al_init( &list1 );
+ al_init( &list2 );
+
+ if( EXPAND_SKIP_SUBSHELL & flags )
+ {
+ wchar_t *pos = str;
+
+ while( 1 )
+ {
+ pos = wcschr( pos, L'(' );
+ if( pos == 0 )
+ break;
+
+ if( (pos == str) || ( *(pos-1) != L'\\' ) )
+ {
+ error( SUBSHELL_ERROR, L"Subshells not allowed", -1 );
+ al_destroy( &list1 );
+ al_destroy( &list2 );
+ return 0;
+ }
+ pos++;
+ }
+ al_push( &list1, str );
+ }
+ else
+ {
+ subshell_ok = expand_subshell( str, &list1 );
+ }
+
+ if( !subshell_ok )
+ {
+ al_destroy( &list1 );
+ return 0;
+ }
+ else
+ {
+ in = &list1;
+ out = &list2;
+
+ for( i=0; i<al_get_count( in ); i++ )
+ {
+ wchar_t *next = expand_backslash((wchar_t *)al_get( in, i ),
+ 1);
+
+ if( EXPAND_SKIP_VARIABLES & flags )
+ {
+ wchar_t *tmp;
+ for( tmp=next; *tmp; tmp++ )
+ if( *tmp == VARIABLE_EXPAND )
+ *tmp = L'$';
+ al_push( out, next );
+
+ }
+ else
+ {
+ if(!expand_variables( next, out ))
+ {
+ al_destroy( in );
+ al_destroy( out );
+ return 0;
+ }
+ }
+ }
+
+ al_truncate( in, 0 );
+
+ in = &list2;
+ out = &list1;
+
+ for( i=0; i<al_get_count( in ); i++ )
+ {
+ wchar_t *next = (wchar_t *)al_get( in, i );
+ if( !expand_brackets( next, flags, out ))
+ {
+ al_destroy( in );
+ al_destroy( out );
+ return 0;
+ }
+ }
+ al_truncate( in, 0 );
+
+ in = &list1;
+ out = &list2;
+
+ for( i=0; i<al_get_count( in ); i++ )
+ {
+ wchar_t *next = (wchar_t *)al_get( in, i );
+ if(!tilde_expand( &next ))
+ {
+ al_destroy( in );
+ al_destroy( out );
+ return 0;
+ }
+
+ if( flags & ACCEPT_INCOMPLETE )
+ {
+ if( *next == PROCESS_EXPAND )
+ {
+ expand_pid( next, flags, end_out );
+ al_destroy( in );
+ al_destroy( out );
+ return 1;
+ }
+ else
+ al_push( out, next );
+ }
+ else
+ {
+ if( !expand_pid( next, flags, out ) )
+ {
+ al_destroy( in );
+ al_destroy( out );
+ return 0;
+ }
+ }
+ }
+ al_truncate( in, 0 );
+
+ in = &list2;
+ out = &list1;
+
+ for( i=0; i<al_get_count( in ); i++ )
+ {
+ wchar_t *next = (wchar_t *)al_get( in, i );
+ int wc_res;
+
+ remove_internal_separator( next, EXPAND_SKIP_WILDCARDS & flags );
+ if( ((flags & ACCEPT_INCOMPLETE) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||
+ wildcard_has( next, 1 ) )
+ {
+
+ if( next[0] == '/' )
+ {
+ wc_res = wildcard_expand( &next[1], L"/",flags, out );
+ }
+ else
+ {
+ wc_res = wildcard_expand( next, L"", flags, out );
+ }
+ free( next );
+ switch( wc_res )
+ {
+ case 0:
+ if( !(flags & ACCEPT_INCOMPLETE) )
+ {
+ break;
+ }
+ case 1:
+ sort_list( out );
+ al_push_all( end_out, out );
+ al_truncate( out, 0 );
+ break;
+
+ default:
+ fwprintf( stderr, L"error\n" );
+ /*al_destroy( &list1 );*/
+ /*al_destroy( &list2 );*/
+ /*return 0;*/
+ }
+ }
+ else
+ {
+ if( flags & ACCEPT_INCOMPLETE)
+ free( next );
+ else
+ al_push( end_out, next );
+ }
+ }
+ al_destroy( in );
+ al_destroy( out );
+ }
+
+ return 1;
+}
+
+
+wchar_t *expand_one( wchar_t *string, int flags )
+{
+ array_list_t l;
+ int res;
+ wchar_t *one;
+
+ al_init( &l );
+ res = expand_string( string, &l, flags );
+ if( !res )
+ {
+ free( string );
+ one = 0;
+ }
+ else
+ {
+ if( al_get_count( &l ) != 1 )
+ {
+ one=0;
+ }
+ else
+ {
+ one = (wchar_t *)al_get( &l, 0 );
+ al_set( &l, 0, 0 );
+ }
+ }
+
+ al_foreach( &l, (void(*)(const void *))&free );
+ al_destroy( &l );
+ return one;
+}
+
diff --git a/expand.h b/expand.h
new file mode 100644
index 00000000..51938948
--- /dev/null
+++ b/expand.h
@@ -0,0 +1,170 @@
+/**\file expand.h
+
+ Prototypes for string expantion functions. These functions perform
+ several kinds of parameter expantion. There are a lot of issues
+ with regards to memory allocation. Overall, these functions would
+ benefit from using a more clever memory allocation scheme, perhaps
+ an evil combination of talloc, string buffers and reference
+ counting.
+
+*/
+
+/**
+ Flag specifying that subshell expantion should be skipped
+*/
+#define EXPAND_SKIP_SUBSHELL 1
+
+/**
+ Flag specifying that variable expantion should be skipped
+*/
+#define EXPAND_SKIP_VARIABLES 2
+
+/**
+ Flag specifying that wildcard expantion should be skipped
+*/
+#define EXPAND_SKIP_WILDCARDS 4
+
+/**
+ Incomplete matches in the last segment are ok (for tab
+ completion). An incomplete match is a wildcard that matches a
+ prefix of the filename. If accept_incomplete is true, only the
+ remainder of the string is returned.
+*/
+#define ACCEPT_INCOMPLETE 8
+
+/**
+ Only match files that are executable by the current user. Only applicable together with ACCEPT_INCOMPLETE.
+*/
+
+#define EXECUTABLES_ONLY 16
+
+/**
+ Only match directories. Only applicable together with ACCEPT_INCOMPLETE.
+*/
+
+#define DIRECTORIES_ONLY 32
+
+/** Character represeting a home directory */
+#define HOME_DIRECTORY 0xfffffffc
+
+/** Character represeting process expantion */
+#define PROCESS_EXPAND 0xfffffffb
+
+/** Character representing variable expantion */
+#define VARIABLE_EXPAND 0xfffffffa
+
+/** Character representing the start of a bracket expantion */
+#define BRACKET_BEGIN 0xfffffff9
+
+/** Character representing the end of a bracket expantion */
+#define BRACKET_END 0xfffffff8
+
+/** Character representing separation between two bracket elements */
+#define BRACKET_SEP 0xfffffff7
+
+/** Character for separating two array elements. We use 30, i.e. the ascii record separator since that seems logical. */
+#define ARRAY_SEP 0x1e
+
+/** String containing the character for separating two array elements */
+#define ARRAY_SEP_STR L"\x1e"
+
+/**
+ Separate subtokens in a token with this character.
+*/
+#define INTERNAL_SEPARATOR 0xfffffff0
+
+
+
+/**
+ Perform various forms of expansion on in, such as tilde expansion
+ (~USER becomes the users home directory), variable expansion
+ ($VAR_NAME becomes the value of the environment variable VAR_NAME),
+ subshell expantion and wildcard expansion. The results are inserted
+ into the list out.
+
+ If the parameter does not need expansion, it is copied into the list
+ out. If expansion is performed, the original parameter is freed and
+ newly allocated strings are inserted into the list out.
+
+ \param in The parameter to expand
+ \param flag Specifies if any expantion pass should be skipped. Legal values are any combination of EXPAND_SKIP_SUBSHELL EXPAND_SKIP_VARIABLES and EXPAND_SKIP_WILDCARDS
+ \param out The list to which the result will be appended.
+*/
+int expand_string( wchar_t *in, array_list_t *out, int flag );
+
+/**
+ expand_one is identical to expand_string, except it will fail if in
+ expands to more than one string. This is used for expanding command
+ names.
+
+ \param in The parameter to expand
+ \param flag Specifies if any expantion pass should be skipped. Legal values are any combination of EXPAND_SKIP_SUBSHELL EXPAND_SKIP_VARIABLES and EXPAND_SKIP_WILDCARDS
+ \return The expanded parameter, or 0 on failiure
+*/
+wchar_t *expand_one( wchar_t *in, int flag );
+
+/**
+ Replace special characters with escape sequences. Newline is
+ replaced with \n, etc.
+
+ \param in The string to be escaped
+ \param escape_all Whether all characters wich hold special meaning in fish (Pipe, semicolon, etc,) should be escaped, or only unprintable characters
+ \return The escaped string, or 0 if there is not enough memory
+*/
+wchar_t *expand_escape( wchar_t *in, int escape_all );
+
+/**
+ Convert the variable value to a human readable form, i.e. escape things, handle arrays, etc. Suitable for pretty-printing.
+*/
+wchar_t *expand_escape_variable( const wchar_t *in );
+
+
+/**
+ Perform tilde expantion and nothing else on the specified string.
+
+ If tilde expantion is needed, the original string is freed and a
+ new string, allocated using malloc, is returned.
+*/
+wchar_t *expand_tilde(wchar_t *in);
+
+/**
+ Locate the last subshell in the specified string.
+
+ \param in the string to search for subshells
+ \param begin the starting paranthesis of the subshell
+ \param end the ending paranthesis of the subshell
+ \param flags set this variable to ACCEPT_INCOMPLETE if in tab_completion mode
+ \return -1 on syntax error, 0 if no subshells exist and 1 on sucess
+*/
+int expand_locate_subshell( wchar_t *in,
+ wchar_t **begin,
+ wchar_t **end,
+ int flags );
+
+
+/**
+ Expand backslashed escapes and substitute them with their unescaped
+ counterparts. Also optionally change the wildcards, the tilde
+ character and a few more into constants which are defined to be
+ outside of the valid character space, but still inside the valid
+ space for a wchar_t. This assumes that a wchar_t is at least 32
+ bits long AND that the characterset is UCS4 or some other 31-bit
+ character set.
+
+ Since removing the escape sequences can never lengthen the string,
+ the specified string is modified instead of allocating a new one.
+
+*/
+wchar_t *expand_backslash( wchar_t * in, int escape_special );
+
+
+/**
+ Tokenize the specified string into the specified array_list_t.
+ Each new element is allocated using malloc and must be freed by the
+ caller.
+
+ \param val the input string. The contents of this string is not changed.
+ \param out the list in which to place the elements.
+*/
+void expand_variable_array( const wchar_t *val, array_list_t *out );
+
diff --git a/fish.spec.in b/fish.spec.in
new file mode 100644
index 00000000..7815d30e
--- /dev/null
+++ b/fish.spec.in
@@ -0,0 +1,110 @@
+Summary: A friendly interactive shell
+Name: @PACKAGE_NAME@
+
+Version: @PACKAGE_VERSION@
+Release: 0%{?dist}
+
+License: GPL
+Group: System Environment/Shells
+URL: http://roo.no-ip.org/fish/
+
+Source0: http://roo.no-ip.org/%{name}/files/%{version}/%{name}-%{version}.tar.bz2
+
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+BuildRequires: doxygen ncurses-devel xorg-x11-devel
+
+%description
+fish is a shell geared towards interactive use. It's features are
+focused on user friendlieness and discoverability. The language syntax
+is simple but incompatible with other shell languages.
+
+%prep
+%setup -q
+
+%build
+
+%configure docdir=%_datadir/doc/%{name}-%{version}
+make %{?_smp_mflags}
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make install DESTDIR="$RPM_BUILD_ROOT"
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post
+if ! grep %_bindir/fish %_sysconfdir/shells >/dev/null; then
+ echo %_bindir/fish >>%_sysconfdir/shells
+fi
+
+%postun
+if [ "$1" = 0 ]; then
+ grep -v %_bindir/fish %_sysconfdir/shells >%_sysconfdir/fish.tmp
+ mv %_sysconfdir/fish.tmp %_sysconfdir/shells
+fi
+
+%files
+%defattr(-,root,root,-)
+%doc %_datadir/doc/%{name}-%{version}
+%_mandir/man1/fish.1*
+%_mandir/man1/xsel.1x*
+%_mandir/man1/mimedb.1*
+%_mandir/man1/set_color.1*
+%_mandir/man1/tokenize.1*
+%_mandir/man1/count.1*
+%attr(0755,root,root) %_bindir/fish
+%attr(0755,root,root) %_bindir/fishd
+%attr(0755,root,root) %_bindir/fish_pager
+%attr(0755,root,root) %_bindir/xsel
+%attr(0755,root,root) %_bindir/set_color
+%attr(0755,root,root) %_bindir/tokenize
+%attr(0755,root,root) %_bindir/mimedb
+%attr(0755,root,root) %_bindir/count
+%config %_sysconfdir/fish
+%config %_sysconfdir/fish_inputrc
+%dir %_sysconfdir/fish.d
+%config %_sysconfdir/fish.d/fish_*.fish
+%dir %_sysconfdir/fish.d/completions
+%config %_sysconfdir/fish.d/completions/*.fish
+
+%changelog
+* Mon Sep 12 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.13.4-0
+- 1.13.4
+
+* Wed Sep 07 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.13.3-0
+- 1.13.3
+
+* Tue Sep 06 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.13.2-0
+- 1.13.2
+
+* Fri Aug 30 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.13.1-0
+- 1.13.1
+
+* Sun Aug 28 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.13.0-0
+- 1.13.0
+
+* Sat Aug 13 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.13.0-0
+- Add completions subdirectory
+
+* Thu Jul 28 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.12.1-0
+- 1.12.1
+
+* Fri Jul 15 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.12.0-1
+- 1.12.0
+
+* Thu Jun 30 2005 Michael Schwendt <mschwendt@users.sf.net> 1.11.1-9
+- Set CFLAGS the proper way
+
+* Thu Jun 30 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.11.1-8
+- Fix revision number in changelog
+
+* Wed Jun 29 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.11.1-7
+- Send post-script output to /dev/null
+
+* Wed Jun 29 2005 Axel Liljencrantz <axel@liljencrantz.se> 1.11.1-6
+- Add changelog section to spec file
+- Add macros to source tags
+- Add smp_mflags to 'make all'
+- Fix typo in post install scriptlet test
+- Set CFLAGS from spec file
diff --git a/fish_pager.c b/fish_pager.c
new file mode 100644
index 00000000..cc24cf25
--- /dev/null
+++ b/fish_pager.c
@@ -0,0 +1,992 @@
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <unistd.h>
+#include <termios.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <fcntl.h>
+
+#include <locale.h>
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+#include <signal.h>
+
+#include "util.h"
+#include "wutil.h"
+#include "common.h"
+#include "complete.h"
+#include "output.h"
+#include "input_common.h"
+#include "env_universal.h"
+
+#define WCHAR_END 0x80000000
+
+enum
+{
+ LINE_UP = R_NULL+1,
+ LINE_DOWN,
+ PAGE_UP,
+ PAGE_DOWN
+}
+ ;
+
+
+enum
+{
+ HIGHLIGHT_PAGER_PREFIX,
+ HIGHLIGHT_PAGER_COMPLETION,
+ HIGHLIGHT_PAGER_DESCRIPTION,
+ HIGHLIGHT_PAGER_PROGRESS
+}
+;
+
+/**
+ This struct should be continually updated by signals as the term
+ resizes, and as such always contain the correct current size.
+*/
+
+static struct winsize termsize;
+
+static struct termios saved_modes;
+static struct termios pager_modes;
+
+static int is_ca_mode = 0;
+
+
+/**
+ The environment variables used to specify the color of different
+ tokens.
+*/
+static wchar_t *hightlight_var[] =
+{
+ L"fish_pager_color_prefix",
+ L"fish_pager_color_completion",
+ L"fish_pager_color_description",
+ L"fish_pager_color_progress"
+}
+ ;
+
+static string_buffer_t out_buff;
+static FILE *out_file;
+
+
+int get_color( int highlight )
+{
+ if( highlight < 0 )
+ return FISH_COLOR_NORMAL;
+ if( highlight >= (4) )
+ return FISH_COLOR_NORMAL;
+
+ wchar_t *val = env_universal_get( hightlight_var[highlight]);
+
+ if( val == 0 )
+ return FISH_COLOR_NORMAL;
+
+ if( val == 0 )
+ {
+ return FISH_COLOR_NORMAL;
+ }
+
+ return output_color_code( val );
+}
+
+int try_sequence( char *seq )
+{
+ int j, k;
+ wint_t c=0;
+
+ for( j=0;
+ seq[j] != '\0' && seq[j] == (c=input_common_readch( j>0 ));
+ j++ )
+ ;
+
+ if( seq[j] == '\0' )
+ {
+ return 1;
+ }
+ else
+ {
+ input_common_unreadch(c);
+ for(k=j-1; k>=0; k--)
+ input_common_unreadch(seq[k]);
+ }
+ return 0;
+}
+
+static wint_t readch()
+{
+ struct mapping
+ {
+ char *seq;
+ wint_t bnd;
+ }
+ ;
+
+ struct mapping m[]=
+ {
+ {
+ "\e[A", LINE_UP
+ }
+ ,
+ {
+ key_up, LINE_UP
+ }
+ ,
+ {
+ "\e[B", LINE_DOWN
+ }
+ ,
+ {
+ key_down, LINE_DOWN
+ }
+ ,
+ {
+ key_ppage, PAGE_UP
+ }
+ ,
+ {
+ key_npage, PAGE_DOWN
+ }
+ ,
+ {
+ " ", PAGE_DOWN
+ }
+ ,
+ {
+ "\t", PAGE_DOWN
+ }
+ ,
+ {
+ 0, 0
+ }
+
+ }
+ ;
+ int i;
+
+ for( i=0; m[i].seq; i++ )
+ {
+ if( try_sequence(m[i].seq ) )
+ return m[i].bnd;
+ }
+ return input_common_readch(0);
+}
+
+
+/**
+ Print the specified part of the completion list, using the
+ specified column offsets and quoting style.
+
+ \param l The list of completions to print
+ \param cols number of columns to print in
+ \param width An array specifying the width of each column
+ \param row_start The first row to print
+ \param row_stop the row after the last row to print
+ \param prefix The string to print before each completion
+ \param is_quoted Whether to print the completions are in a quoted environment
+*/
+
+static void completion_print( int cols,
+ int *width,
+ int row_start,
+ int row_stop,
+ wchar_t *prefix,
+ int is_quoted,
+ array_list_t *l)
+{
+
+ int rows = (al_get_count( l )-1)/cols+1;
+ int i, j;
+ int prefix_width= my_wcswidth(prefix);
+
+ for( i = row_start; i<row_stop; i++ )
+ {
+ for( j = 0; j < cols; j++ )
+ {
+ wchar_t *el, *el_end;
+
+ if( al_get_count( l ) <= j*rows + i )
+ continue;
+
+ el = (wchar_t *)al_get( l, j*rows + i );
+ el_end= wcschr( el, COMPLETE_SEP );
+
+ set_color( get_color(HIGHLIGHT_PAGER_PREFIX),FISH_COLOR_NORMAL );
+
+ writestr( prefix );
+
+ set_color( get_color(HIGHLIGHT_PAGER_COMPLETION),FISH_COLOR_IGNORE );
+
+ if( el_end == 0 )
+ {
+ /* We do not have a description for this completion */
+ int written = 0;
+ int max_written = width[j] - prefix_width - (j==cols-1?0:2);
+
+ if( is_quoted )
+ {
+ for( i=0; i<max_written; i++ )
+ {
+ if( !el[i] )
+ break;
+ writech( el[i] );
+ written+= wcwidth( el[i] );
+ }
+ }
+ else
+ {
+ written = write_escaped_str( el, max_written );
+ }
+
+ set_color( get_color( HIGHLIGHT_PAGER_DESCRIPTION ),
+ FISH_COLOR_IGNORE );
+
+ writespace( width[j]-
+ written-
+ prefix_width );
+ }
+ else
+ {
+ int whole_desc_width = my_wcswidth(el_end+1);
+ int whole_comp_width;
+
+ /*
+ Temporarily drop the description so that wcswidth et
+ al only calculate the width of the completion.
+ */
+ *el_end = L'\0';
+
+ /*
+ Calculate preferred completion width
+ */
+ if( is_quoted )
+ {
+ whole_comp_width = my_wcswidth(el);
+ }
+ else
+ {
+ wchar_t *tmp = escape( wcsdup(el), 1 );
+ whole_comp_width = my_wcswidth( tmp );
+ free(tmp);
+ }
+
+ /*
+ Calculate how wide this entry 'wants' to be
+ */
+ int pref_width = whole_desc_width + 4 + prefix_width + 2 -
+ (j==cols-1?2:0) + whole_comp_width;
+
+ int comp_width, desc_width;
+
+ if( pref_width <= width[j] )
+ {
+ /*
+ The entry fits, we give it as much space as it wants
+ */
+ comp_width = whole_comp_width;
+ desc_width = whole_desc_width;
+ }
+ else
+ {
+ /*
+ The completion and description won't fit on the
+ allocated space. Give a maximum of 2/3 of the
+ space to the completion, and whatever is left to
+ the description.
+ */
+ int sum = width[j] - prefix_width - 4 - 2 + (j==cols-1?2:0);
+
+ comp_width = maxi( mini( whole_comp_width,
+ 2*sum/3 ),
+ sum - whole_desc_width );
+ desc_width = sum-comp_width;
+ }
+
+ /* First we must print the completion. */
+ if( is_quoted )
+ {
+ writestr_ellipsis( el, comp_width);
+ }
+ else
+ {
+ write_escaped_str( el, comp_width );
+ }
+
+ /* Put the description back */
+ *el_end = COMPLETE_SEP;
+
+ /* And print it */
+ set_color( get_color(HIGHLIGHT_PAGER_DESCRIPTION),
+ FISH_COLOR_IGNORE );
+ writespace( maxi( 2,
+ width[j]
+ - comp_width
+ - desc_width
+ - 4
+ - prefix_width
+ + (j==cols-1?2:0) ) );
+ /* Print description */
+ writestr(L"(");
+ writestr_ellipsis( el_end+1, desc_width);
+ writestr(L")");
+
+ if( j != cols-1)
+ writestr( L" " );
+
+ }
+ }
+ writech( L'\n' );
+ }
+}
+
+/**
+ Calculates how long the specified string would be when printed on the command line.
+
+ \param str The string to be printed.
+ \param is_quoted Whether the string would be printed quoted or unquoted
+ \param pref_width the preferred width for this item
+ \param min_width the minimum width for this item
+*/
+static void printed_length( wchar_t *str,
+ int is_quoted,
+ int *pref_width,
+ int *min_width )
+{
+ if( is_quoted )
+ {
+ wchar_t *sep = wcschr(str,COMPLETE_SEP);
+ if( sep )
+ {
+ *sep=0;
+ int cw = my_wcswidth( str );
+ int dw = my_wcswidth(sep+1);
+
+ if( termsize.ws_col > 80 )
+ dw = mini( dw, termsize.ws_col/3 );
+
+
+ *pref_width = cw+dw+4;
+
+ if( dw > termsize.ws_col/3 )
+ {
+ dw = termsize.ws_col/3;
+ }
+
+ *min_width=cw+dw+4;
+
+ *sep= COMPLETE_SEP;
+ return;
+ }
+ else
+ {
+ *pref_width=*min_width= my_wcswidth( str );
+ return;
+ }
+
+ }
+ else
+ {
+ int comp_len=0, desc_len=0;
+ int has_description = 0;
+ while( *str != 0 )
+ {
+ switch( *str )
+ {
+ case L'\n':
+ case L'\b':
+ case L'\r':
+ case L'\e':
+ case L'\t':
+ case L'\\':
+ case L'&':
+ case L'$':
+ case L' ':
+ case L'#':
+ case L'^':
+ case L'<':
+ case L'>':
+ case L'@':
+ case L'(':
+ case L')':
+ case L'{':
+ case L'}':
+ case L'?':
+ case L'*':
+ case L'|':
+ case L';':
+ case L':':
+ if( has_description )
+ desc_len++;
+ else
+ comp_len+=2;
+ break;
+
+ case COMPLETE_SEP:
+ has_description = 1;
+ break;
+
+ default:
+ if( has_description )
+ desc_len+= wcwidth(*str);
+ else
+ comp_len+= wcwidth(*str);
+ break;
+ }
+ str++;
+ }
+ if( has_description )
+ {
+ /*
+ Mangle long descriptions to make formating look nicer
+ */
+ debug( 3, L"Desc, width = %d %d\n", comp_len, desc_len );
+// if( termsize.ws_col > 80 )
+// desc_len = mini( desc_len, termsize.ws_col/3 );
+
+ *pref_width = comp_len+ desc_len+4;;
+
+ comp_len = mini( comp_len, maxi(0,termsize.ws_col/3 - 2));
+ desc_len = mini( desc_len, maxi(0,termsize.ws_col/5 - 4));
+
+ *min_width = comp_len+ desc_len+4;
+ return;
+ }
+ else
+ {
+ debug( 3, L"No desc, width = %d\n", comp_len );
+
+ *pref_width=*min_width= comp_len;
+ return;
+ }
+
+ }
+}
+
+
+/**
+ Try to print the list of completions l with the prefix prefix using
+ cols as the number of columns. Return 1 if the completion list was
+ printed, 0 if the terminal is to narrow for the specified number of
+ columns. Always succeeds if cols is 1.
+
+ If all the elements do not fit on the screen at once, make the list
+ scrollable using the up, down and space keys to move. The list will
+ exit when any other key is pressed.
+
+ \param cols the number of columns to try to fit onto the screen
+ \param prefix the character string to prefix each completion with
+ \param is_quoted whether the completions should be quoted
+ \param l the list of completions
+
+ \return zero if the specified number of columns do not fit, no-zero otherwise
+*/
+
+static int completion_try_print( int cols,
+ wchar_t *prefix,
+ int is_quoted,
+ array_list_t *l )
+{
+ /*
+ The calculated preferred width of each column
+ */
+ int pref_width[32];
+ /*
+ The calculated minimum width of each column
+ */
+ int min_width[32];
+ /*
+ If the list can be printed with this width, width will contain the width of each column
+ */
+ int *width=pref_width;
+ /*
+ Set to one if the list should be printed at this width
+ */
+ int print=0;
+
+ int i, j;
+
+ int rows = (al_get_count( l )-1)/cols+1;
+
+ int pref_tot_width=0;
+ int min_tot_width = 0;
+ int prefix_width = my_wcswidth( prefix );
+
+ int res=0;
+ /*
+ Skip completions on tiny terminals
+ */
+
+ if( termsize.ws_col < 16 )
+ return 1;
+
+ memset( pref_width, 0, sizeof(pref_width) );
+ memset( min_width, 0, sizeof(min_width) );
+
+ /* Calculated how wide the list would be */
+ for( j = 0; j < cols; j++ )
+ {
+ for( i = 0; i<rows; i++ )
+ {
+ int pref,min;
+ wchar_t *el;
+ if( al_get_count( l ) <= j*rows + i )
+ continue;
+
+ el = (wchar_t *)al_get( l, j*rows + i );
+ printed_length( el, is_quoted, &pref, &min );
+
+ pref += prefix_width;
+ min += prefix_width;
+ if( j != cols-1 )
+ {
+ pref += 2;
+ min += 2;
+ }
+ min_width[j] = maxi( min_width[j],
+ min );
+ pref_width[j] = maxi( pref_width[j],
+ pref );
+ }
+ min_tot_width += min_width[j];
+ pref_tot_width += pref_width[j];
+ }
+ /*
+ Force fit if one column
+ */
+ if( cols == 1)
+ {
+ if( pref_tot_width > termsize.ws_col )
+ {
+ pref_width[0] = termsize.ws_col;
+ }
+ width = pref_width;
+ print=1;
+ }
+ else if( pref_tot_width <= termsize.ws_col )
+ {
+ /* Terminal is wide enough. Print the list! */
+ width = pref_width;
+ print=1;
+ }
+ else
+ {
+ int next_rows = (al_get_count( l )-1)/(cols-1)+1;
+/* fwprintf( stderr,
+ L"cols %d, min_tot %d, term %d, rows=%d, nextrows %d, termrows %d, diff %d\n",
+ cols,
+ min_tot_width, termsize.ws_col,
+ rows, next_rows, termsize.ws_row,
+ pref_tot_width-termsize.ws_col );
+*/
+ if( min_tot_width < termsize.ws_col &&
+ ( ( (rows < termsize.ws_row) && (next_rows >= termsize.ws_row ) ) ||
+ ( pref_tot_width-termsize.ws_col< 4 && cols < 3 ) ) )
+ {
+ /*
+ Terminal almost wide enough, or squeezing makes the whole list fit on-screen
+ */
+ int tot_width = min_tot_width;
+ width = min_width;
+
+ while( tot_width < termsize.ws_col )
+ {
+ for( i=0; (i<cols) && ( tot_width < termsize.ws_col ); i++ )
+ {
+ if( width[i] < pref_width[i] )
+ {
+ width[i]++;
+ tot_width++;
+ }
+ }
+ }
+ print=1;
+ }
+ }
+
+// return cols==1;
+
+ if( print )
+ {
+ res=1;
+ if( rows < termsize.ws_row )
+ {
+ /* List fits on screen. Print it and leave */
+ if( is_ca_mode )
+ {
+ is_ca_mode = 0;
+ writembs(exit_ca_mode);
+ }
+
+ completion_print( cols, width, 0, rows, prefix, is_quoted, l);
+ }
+ else
+ {
+ int npos, pos = 0;
+ int do_loop = 1;
+
+ is_ca_mode=1;
+ writembs(enter_ca_mode);
+
+ completion_print( cols,
+ width,
+ 0,
+ termsize.ws_row-1,
+ prefix,
+ is_quoted,
+ l);
+ /*
+ List does not fit on screen. Print one screenfull and
+ leave a scrollable interface
+ */
+ while(do_loop)
+ {
+ wchar_t msg[10];
+ int percent = 100*pos/(rows-termsize.ws_row+1);
+ set_color( FISH_COLOR_BLACK,
+ get_color(HIGHLIGHT_PAGER_PROGRESS) );
+ swprintf( msg, 12,
+ L" %ls(%d%%) \r",
+ percent==100?L"":(percent >=10?L" ": L" "),
+ percent );
+ writestr(msg);
+ set_color( FISH_COLOR_NORMAL, FISH_COLOR_NORMAL );
+ int c = readch();
+
+ switch( c )
+ {
+ case LINE_UP:
+ {
+ if( pos > 0 )
+ {
+ pos--;
+ writembs(tparm( cursor_address, 0, 0));
+ writembs(scroll_reverse);
+ completion_print( cols,
+ width,
+ pos,
+ pos+1,
+ prefix,
+ is_quoted,
+ l );
+ writembs( tparm( cursor_address,
+ termsize.ws_row-1, 0) );
+ writembs(clr_eol );
+
+ }
+
+ break;
+ }
+
+ case LINE_DOWN:
+ {
+ if( pos <= (rows - termsize.ws_row ) )
+ {
+ pos++;
+ completion_print( cols,
+ width,
+ pos+termsize.ws_row-2,
+ pos+termsize.ws_row-1,
+ prefix,
+ is_quoted,
+ l );
+ }
+ break;
+ }
+
+ case PAGE_DOWN:
+ {
+
+ npos = mini( rows - termsize.ws_row+1,
+ pos + termsize.ws_row-1 );
+ if( npos != pos )
+ {
+ pos = npos;
+ completion_print( cols,
+ width,
+ pos,
+ pos+termsize.ws_row-1,
+ prefix,
+ is_quoted,
+ l );
+ }
+ else
+ {
+ writembs( flash_screen );
+ }
+
+ break;
+ }
+
+ case PAGE_UP:
+ {
+ npos = maxi( 0,
+ pos - termsize.ws_row+1 );
+
+ if( npos != pos )
+ {
+ pos = npos;
+ completion_print( cols,
+ width,
+ pos,
+ pos+termsize.ws_row-1,
+ prefix,
+ is_quoted,
+ l );
+ }
+ else
+ {
+ writembs( flash_screen );
+ }
+ break;
+ }
+
+ case R_NULL:
+ {
+ do_loop=0;
+ res=2;
+ break;
+
+ }
+
+ default:
+ {
+ sb_append_char( &out_buff, c );
+ do_loop = 0;
+ break;
+ }
+ }
+ }
+ writembs(clr_eol);
+ }
+ }
+ return res;
+}
+
+/**
+ Substitute any series of tabs, newlines, etc. with a single space character in completion description
+*/
+static void mangle_descriptions( array_list_t *l )
+{
+ int i, skip;
+ for( i=0; i<al_get_count( l ); i++ )
+ {
+ wchar_t *next = (wchar_t *)al_get(l, i);
+ wchar_t *in, *out;
+ skip=0;
+
+ while( *next != COMPLETE_SEP && *next )
+ next++;
+
+ if( !*next )
+ continue;
+
+ in=out=(next+1);
+
+ while( *in != 0 )
+ {
+ if( *in == L' ' || *in==L'\t' || *in<32 )
+ {
+ if( !skip )
+ *out++=L' ';
+ skip=1;
+ }
+ else
+ {
+ *out++ = *in;
+ skip=0;
+ }
+ in++;
+ }
+ *out=0;
+ }
+}
+
+/**
+ Respond to a winch signal by checking the terminal size
+*/
+static void handle_winch( int sig )
+{
+ if (ioctl(1,TIOCGWINSZ,&termsize)!=0)
+ {
+ return;
+ }
+}
+
+static int interrupt_handler()
+{
+ return R_NULL;
+}
+
+
+static void init()
+{
+ struct sigaction act;
+ program_name = L"fish_pager";
+ fish_setlocale( LC_ALL, L"" );
+
+
+ int out = dup( 1 );
+ close(1);
+ if( open( ttyname(0), O_WRONLY ) != 1 )
+ {
+ debug( 0, L"Could not set up file descriptors for pager" );
+ exit( 1 );
+
+ }
+ out_file = fdopen( out, "w" );
+ sb_init( &out_buff );
+
+
+
+ env_universal_init( 0, 0, 0);
+ input_common_init( &interrupt_handler );
+
+ sigemptyset( & act.sa_mask );
+ act.sa_flags=0;
+ act.sa_handler=SIG_DFL;
+ act.sa_flags = 0;
+ act.sa_handler= &handle_winch;
+ if( sigaction( SIGWINCH, &act, 0 ) )
+ {
+ wperror( L"sigaction" );
+ exit(1);
+ }
+
+ /* Loop until we are in the foreground. */
+ while (tcgetpgrp( 0 ) != getpid())
+ {
+ kill (- getpid(), SIGTTIN);
+ }
+
+ /* Put ourselves in our own process group. */
+ if( getpgrp() != getpid() )
+ {
+ if (setpgid (getpid(), getpid()) < 0)
+ {
+ debug( 1,
+ L"Couldn't put the shell in its own process group");
+ wperror( L"setpgid" );
+ exit (1);
+ }
+ }
+
+ /* Grab control of the terminal. */
+ if( tcsetpgrp (STDIN_FILENO, getpid()) )
+ {
+ debug( 1,
+ L"Couldn't grab control of terminal" );
+ wperror( L"tcsetpgrp" );
+ exit(1);
+ }
+
+ handle_winch( 0 ); /* Set handler for window change events */
+
+ tcgetattr(0,&pager_modes); /* get the current terminal modes */
+ memcpy( &saved_modes,
+ &pager_modes,
+ sizeof(saved_modes)); /* save a copy so we can reset the terminal later */
+
+ pager_modes.c_lflag &= ~ICANON; /* turn off canonical mode */
+ pager_modes.c_lflag &= ~ECHO; /* turn off echo mode */
+ pager_modes.c_cc[VMIN]=1;
+ pager_modes.c_cc[VTIME]=0;
+
+ if( tcsetattr(0,TCSANOW,&pager_modes)) /* set the new modes */
+ {
+ wperror(L"tcsetattr");
+ exit(1);
+ }
+
+ if( setupterm( 0, STDOUT_FILENO, 0) == ERR )
+ {
+ debug( 0, L"Could not set up terminal" );
+ exit(1);
+ }
+
+
+// writembs( tparm( eat_char, ' ', c ) );
+
+}
+
+void destroy()
+{
+ env_universal_destroy();
+ input_common_destroy();
+ del_curterm( cur_term );
+ sb_destroy( &out_buff );
+ fclose( out_file );
+
+}
+
+int main( int argc, char **argv )
+{
+ int i;
+ int is_quoted=0;
+ array_list_t comp;
+ wchar_t *prefix;
+
+
+ init();
+
+
+ prefix = str2wcs( argv[2] );
+ is_quoted = strcmp( "1", argv[1] )==0;
+
+ debug( 3, L"prefix is '%ls'", prefix );
+
+ al_init( &comp );
+
+ for( i=3; i<argc; i++ )
+ {
+ al_push( &comp, str2wcs( argv[i] ) );
+ }
+
+ mangle_descriptions( &comp );
+
+ for( i = 6; i>0; i-- )
+ {
+ switch( completion_try_print( i, prefix, is_quoted, &comp ) )
+ {
+ case 0:
+ break;
+ case 1:
+ i=0;
+ break;
+ case 2:
+ i=7;
+ break;
+ }
+
+ }
+
+ al_foreach( &comp, (void(*)(const void *))&free );
+ al_destroy( &comp );
+ free(prefix );
+
+ fwprintf( out_file, L"%ls", (wchar_t *)out_buff.buff );
+ if( is_ca_mode )
+ writembs(exit_ca_mode);
+
+
+ destroy();
+}
+
diff --git a/fish_tests.c b/fish_tests.c
new file mode 100644
index 00000000..73756693
--- /dev/null
+++ b/fish_tests.c
@@ -0,0 +1,647 @@
+/** \file main.c
+ Various bug and feature tests. Compiled and run by make test.
+*/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdarg.h>
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#include <signal.h>
+
+#include <locale.h>
+#include <dirent.h>
+
+#include "util.h"
+#include "common.h"
+#include "proc.h"
+#include "reader.h"
+#include "builtin.h"
+#include "function.h"
+#include "complete.h"
+#include "wutil.h"
+#include "env.h"
+#include "expand.h"
+#include "parser.h"
+#include "tokenizer.h"
+
+#define LAPS 50
+
+static int err_count=0;
+
+static void say( wchar_t *blah, ... )
+{
+ va_list va;
+ va_start( va, blah );
+ vwprintf( blah, va );
+ va_end( va );
+ wprintf( L"\n" );
+}
+
+static void err( wchar_t *blah, ... )
+{
+ va_list va;
+ va_start( va, blah );
+ err_count++;
+
+ wprintf( L"Error: " );
+ vwprintf( blah, va );
+ va_end( va );
+ wprintf( L"\n" );
+}
+
+static void ok()
+{
+ wprintf( L"OK\n" );
+}
+
+
+static int pq_compare( void *e1, void *e2 )
+{
+ return e1-e2;
+}
+
+static int pq_test( int elements )
+{
+ int i;
+ int prev;
+
+ int *count = calloc( sizeof(int), 100 );
+
+ priority_queue_t q;
+ pq_init( &q, pq_compare );
+
+
+ for( i=0; i<elements; i++ )
+ {
+ int foo = rand() % 100;
+// printf( "Adding %d\n", foo );
+ pq_put( &q, (void *)foo );
+ count[foo]++;
+ }
+
+ prev = 100;
+
+ for( i=0; i<elements; i++ )
+ {
+ int pos = (int)pq_get( &q );
+ count[ pos ]--;
+ if( pos > prev )
+ err( L"Wrong order of elements in priority_queue_t" );
+ prev = pos;
+
+ }
+
+ for( i=0; i<100; i++ )
+ {
+ if( count[i] != 0 )
+ {
+ err( L"Wrong number of elements in priority_queue_t" );
+ }
+ }
+}
+
+
+static int stack_test( int elements )
+{
+ int i;
+
+ int res=1;
+
+ array_list_t s;
+
+ al_init( &s );
+
+ for( i=0; i<elements; i++ )
+ {
+ int foo;
+
+ al_push( &s, (void*)i);
+ al_push( &s, (void*)i);
+
+ if( (foo=(int)al_pop( &s )) != i )
+ {
+ err( L"Unexpected data" );
+ res = 0;
+ break;
+ }
+ }
+
+ for( i=0; i<elements; i++ )
+ {
+ int foo;
+
+ if( (foo=(int)al_pop( &s )) != (elements-i-1) )
+ {
+ err( L"Unexpected data" );
+ res = 0;
+ break;
+ }
+
+
+ }
+
+
+ al_destroy( &s );
+
+ return res;
+}
+
+
+static int hash_func( const void *data )
+{
+/* srand( (int)data );
+ return rand();
+*/
+ int foo = (int)data;
+ return 127*((foo^0xefc7e214)) ^(foo<<11);
+}
+
+static int compare_func( const void *key1, const void *key2 )
+{
+ return key1==key2;
+}
+
+
+static int hash_test( int elements )
+{
+ int i;
+ int res=1;
+
+ hash_table_t h;
+
+ hash_init( &h, hash_func, compare_func );
+
+ for( i=1; i< elements+1; i++ )
+ {
+ hash_put( &h, (void*)i, (void*)100-i );
+ }
+
+ for( i=1; i< elements+1; i++ )
+ {
+ if( (int)hash_get( &h, (void*)i ) != (100-i) )
+ {
+ err( L"Key %d gave data %d, expected data %d",
+ i,
+ (int)hash_get( &h, (void*)i ),
+ 100-i );
+ res = 0;
+
+ break;
+ }
+ }
+
+ if( hash_get_count( &h ) != elements )
+ {
+ err( L"Table holds %d elements, should hold %d elements",
+ hash_get_count( &h ),
+ elements );
+ res = 0;
+
+ }
+
+
+ for( i=1; i<elements+1; i+=2 )
+ {
+ hash_remove( &h, (void*)i, 0, 0 );
+
+ }
+
+ if( hash_get_count( &h ) != ((elements)/2) )
+ {
+ err( L"Table contains %d elements, should contain %d elements",
+ hash_get_count( &h ),
+ elements/2 );
+ res = 0;
+ }
+
+ for( i=1; i<elements+1; i++ )
+ {
+ if( hash_contains( &h, (void*)i) != (i+1)%2 )
+ {
+ if( i%2 )
+ err( L"Key %d remains, should be deleted",
+ i );
+ else
+ err( L"Key %d does not exist",
+ i );
+ res = 0;
+ break;
+ }
+ }
+
+ hash_destroy( &h );
+
+ return res;
+
+}
+
+static int al_test( int sz)
+{
+ int i;
+ array_list_t l;
+
+
+
+ al_init( &l );
+
+ al_set( &l, 1, (void *)7 );
+ al_set( &l, sz, (void *)7 );
+
+ if( al_get_count( &l ) != maxf( sz+1, 2 ) )
+ err( L"Wrong number of elements in array list" );
+
+ for( i=0; i<al_get_count( &l ); i++ )
+ {
+ int val = (int)((long) al_get( &l, i ));
+ if( (i == 1) || (i==sz))
+ {
+ if( val != 7 )
+ err( L"Canary changed to %d at index %d", val, i );
+ }
+ else
+ {
+ if( val != 0 )
+ err( L"False canary %d found at index %d", val, i );
+ }
+ }
+}
+
+static void sb_test()
+{
+ string_buffer_t b;
+ int res;
+
+ sb_init( &b );
+
+ if( res=sb_printf( &b, L"%ls%s", L"Testing ", "string_buffer_t " ) == -1 )
+ {
+ err( L"Error %d while testing stringbuffers", res );
+ }
+
+ if( (res=sb_printf( &b, L"%ls", L"functionality" ))==-1)
+ {
+ err( L"Error %d while testing stringbuffers", res );
+ }
+ say( (wchar_t *)b.buff );
+}
+
+
+static void test_util()
+{
+ int i;
+
+ say( L"Testing utility library" );
+
+ for( i=0; i<18; i++ )
+ {
+ long t1, t2;
+ pq_test( 1<<i );
+ stack_test( 1<<i );
+ t1 = get_time();
+ hash_test( 1<<i );
+ t2 = get_time();
+ if( i > 8 )
+ say( L"Hashtable uses %f microseconds per element at size %d",
+ ((double)(t2-t1))/(1<<i),
+ 1<<i );
+ al_test( 1<<i );
+ }
+
+ sb_test();
+
+
+/*
+ int i;
+ for( i=2; i<10000000; i*=2 )
+ {
+
+ printf( "%d", i );
+
+ t1 = get_time();
+
+ if(!hash_test(i))
+ exit(0);
+
+ t2 = get_time();
+
+ printf( " %d\n", (t2-t1)/i );
+
+
+ }
+*/
+}
+
+
+
+
+static void test_tok()
+{
+ tokenizer t;
+
+ say( L"Testing tokenizer" );
+
+
+ say( L"Testing invalid input" );
+ tok_init( &t, 0, 0 );
+
+ if( tok_last_type( &t ) != TOK_ERROR )
+ {
+ err(L"Invalid input to tokenizer was undetected" );
+ }
+
+ say( L"Testing use of broken tokenizer" );
+ if( !tok_has_next( &t ) )
+ {
+ err( L"tok_has_next() should return 1 once on broken tokenizer" );
+ }
+
+ tok_next( &t );
+ if( tok_last_type( &t ) != TOK_ERROR )
+ {
+ err(L"Invalid input to tokenizer was undetected" );
+ }
+
+ /*
+ This should crash if there is a bug. No reliable way to detect otherwise.
+ */
+ say( L"Test destruction of broken tokenizer" );
+ tok_destroy( &t );
+
+ {
+
+ wchar_t *str = L"string <redirection 2>&1 'nested \"quoted\" '(string containing subshells ){and,brackets}$as[$well (as variable arrays)]";
+ const int types[] =
+ {
+ TOK_STRING, TOK_REDIRECT_IN, TOK_STRING, TOK_REDIRECT_FD, TOK_STRING, TOK_STRING, TOK_END
+ }
+ ;
+ int i;
+
+ say( L"Test correct tokenization" );
+
+ for( i=0, tok_init( &t, str, 0 ); i<(sizeof(types)/sizeof(int)); i++,tok_next( &t ) )
+ {
+ if( types[i] != tok_last_type( &t ) )
+ {
+ err( L"Tokenization error:");
+ wprintf( L"Token number %d of string \n'%ls'\n, expected token type %ls, got token '%ls' of type %ls\n",
+ i+1,
+ str,
+ tok_get_desc(types[i]),
+ tok_last(&t),
+ tok_get_desc(tok_last_type( &t )) );
+ }
+ }
+ }
+
+
+
+}
+
+static void test_parser()
+{
+ say( L"Testing parser" );
+
+
+ say( L"Testing null input to parser" );
+ if( !parser_test( 0, 0 ) )
+ {
+ err( L"Null input to parser_test undetected" );
+ }
+
+ say( L"Testing block nesting" );
+ if( !parser_test( L"if; end", 0 ) )
+ {
+ err( L"Incomplete if statement undetected" );
+ }
+ if( !parser_test( L"if test; echo", 0 ) )
+ {
+ err( L"Missing end undetected" );
+ }
+ if( !parser_test( L"if test; end; end", 0 ) )
+ {
+ err( L"Unbalanced end undetected" );
+ }
+
+ say( L"Testing detection of invalid use of builtin commands" );
+ if( !parser_test( L"case foo", 0 ) )
+ {
+ err( L"'case' command outside of block context undetected" );
+ }
+ if( !parser_test( L"switch ggg; if true; case foo;end;end", 0 ) )
+ {
+ err( L"'case' command outside of switch block context undetected" );
+ }
+ if( !parser_test( L"else", 0 ) )
+ {
+ err( L"'else' command outside of conditional block context undetected" );
+ }
+ if( !parser_test( L"break", 0 ) )
+ {
+ err( L"'break' command outside of loop block context undetected" );
+ }
+ if( !parser_test( L"exec ls|less", 0 ) || !parser_test( L"echo|return", 0 ))
+ {
+ err( L"Invalid pipe command undetected" );
+ }
+
+ say( L"Testing basic evaluation" );
+ if( !eval( 0, 0, TOP ) )
+ {
+ err( L"Null input when evaluating undetected" );
+ }
+ if( !eval( L"ls", 0, WHILE ) )
+ {
+ err( L"Invalid block mode when evaluating undetected" );
+ }
+
+}
+
+/**
+ Perform parameter expantion and test if the output equals the zero-terminated parameter list supplied.
+
+ \param in the string to expand
+ \param flags the flags to send to expand_string
+*/
+
+static int expand_test( const wchar_t *in, int flags, ... )
+{
+ array_list_t out;
+ va_list va;
+ int i=0;
+ int res=1;
+ wchar_t *arg;
+
+ al_init( &out );
+ expand_string( wcsdup(in), &out, flags);
+
+ va_start( va, flags );
+
+ while( (arg=va_arg(va, wchar_t *) )!= 0 )
+ {
+ if( al_get_count( &out ) == i )
+ {
+ res=0;
+ break;
+ }
+
+ if( wcscmp( al_get( &out, i ),arg) != 0 )
+ {
+ res=0;
+ break;
+ }
+
+ i++;
+ }
+ va_end( va );
+
+ al_foreach( &out, (void (*)(const void *))&free );
+ return res;
+
+}
+
+
+static void test_expand()
+{
+ say( L"Testing parameter expantion" );
+
+ if( !expand_test( L"foo", 0, L"foo", 0 ))
+ {
+ err( L"Strings do not expand to themselves" );
+ }
+
+ if( !expand_test( L"a{b,c,d}e", 0, L"abe", L"ace", L"ade", 0 ) )
+ {
+ err( L"Bracket expantion is broken" );
+ }
+
+ if( !expand_test( L"a*", EXPAND_SKIP_WILDCARDS, L"a*", 0 ) )
+ {
+ err( L"Cannot skip wildcard expantion" );
+ }
+
+}
+
+
+void perf_complete()
+{
+ wchar_t c;
+ array_list_t out;
+ long long t1, t2;
+ int matches=0;
+ double t;
+ wchar_t str[3]=
+ {
+ 0, 0, 0
+ }
+ ;
+ int i;
+
+
+ say( L"Testing completion performance" );
+ al_init( &out );
+
+ reader_push(L"");
+ say( L"Here we go" );
+
+ t1 = get_time();
+
+
+ for( c=L'a'; c<=L'z'; c++ )
+ {
+ str[0]=c;
+ reader_set_buffer( str, 0 );
+
+ complete( str, &out );
+
+ matches += al_get_count( &out );
+
+ al_foreach( &out, (void (*)(const void *))&free );
+ al_truncate( &out, 0 );
+ }
+ t2=get_time();
+
+ t = (double)(t2-t1)/(1000000*26);
+
+ say( L"One letter command completion took %f seconds per completion, %f microseconds/match", t, (double)(t2-t1)/matches );
+
+ matches=0;
+ t1 = get_time();
+ for( i=0; i<LAPS; i++ )
+ {
+ str[0]='a'+(rand()%26);
+ str[1]='a'+(rand()%26);
+
+ reader_set_buffer( str, 0 );
+
+ complete( str, &out );
+
+ matches += al_get_count( &out );
+
+ al_foreach( &out, (void (*)(const void *))&free );
+ al_truncate( &out, 0 );
+ }
+ t2=get_time();
+
+ t = (double)(t2-t1)/(1000000*LAPS);
+
+ say( L"Two letter command completion took %f seconds per completion, %f microseconds/match", t, (double)(t2-t1)/matches );
+
+ al_destroy( &out );
+
+ reader_pop();
+
+}
+
+
+int main( int argc, char **argv )
+{
+
+ say( L"Testing low-level functionality");
+ say( L"Lines beginning with 'fish:' are not errors, they are warning messages\ngenerated by the fish parser library when given broken input, and can be\nignored. All errors begin with 'Error:'." );
+
+ parser_init();
+ function_init();
+ builtin_init();
+ env_init();
+ complete_init();
+ reader_init();
+
+ test_util();
+ test_tok();
+ test_parser();
+ test_expand();
+
+ say( L"Encountered %d errors in low-level tests", err_count );
+
+ /*
+ Skip performance tests for now, since they seem to hang when running from inside make (?)
+ */
+// say( L"Testing performance" );
+// perf_complete();
+
+ reader_destroy();
+
+ parser_destroy();
+ function_destroy();
+ builtin_destroy();
+ env_destroy();
+ complete_destroy();
+ wutil_destroy();
+
+}
diff --git a/fishd.c b/fishd.c
new file mode 100644
index 00000000..30e93b8b
--- /dev/null
+++ b/fishd.c
@@ -0,0 +1,443 @@
+/** \file fishd.c
+
+The universal variable server. fishd is automatically started by fish
+if a fishd server isn't already running. fishd reads any saved
+variables from ~/.fishd, and takes care of commonication between fish
+instances. When no clients are running, fishd will automatically shut
+down and save.
+
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <pwd.h>
+#include <fcntl.h>
+
+#include <errno.h>
+#include <locale.h>
+#include <signal.h>
+
+#include "util.h"
+#include "common.h"
+#include "wutil.h"
+#include "env_universal_common.h"
+
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 100
+#endif
+
+#define GREETING "#Fish universal variable daemon\n#Lines beginning with '#' are ignored\n#Syntax:\n#SET VARNAME:VALUE\n#or\n#ERASE VARNAME\n#Where VALUE is the escaped value of the variable\n#Backslash escapes and \\xxx hexadecimal style escapes are supported\n"
+#define FILE ".fishd"
+
+
+static connection_t *conn;
+static int sock;
+
+
+int get_socket()
+{
+ int s, len;
+ struct sockaddr_un local;
+ char *name;
+ char *dir = getenv( "FISHD_SOCKET_DIR" );
+ char *uname = getenv( "USER" );
+
+ if( !dir )
+ dir = "/tmp";
+ if( uname==0 )
+ {
+ struct passwd *pw;
+ pw = getpwuid( getuid() );
+ uname = strdup( pw->pw_name );
+ }
+
+ name = malloc( strlen(dir)+ strlen(uname)+ strlen(SOCK_FILENAME) + 2 );
+ strcpy( name, dir );
+ strcat( name, "/" );
+ strcat( name, SOCK_FILENAME );
+ strcat( name, uname );
+
+ if( strlen( name ) >= UNIX_PATH_MAX )
+ {
+ debug( 1, L"Filename too long: '%s'", name );
+ exit(1);
+ }
+
+ debug( 1, L"Connect to socket at %s", name );
+
+ if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ perror("socket");
+ exit(1);
+ }
+
+ local.sun_family = AF_UNIX;
+ strcpy(local.sun_path, name );
+ unlink(local.sun_path);
+ len = strlen(local.sun_path) + sizeof(local.sun_family);
+
+ free( name );
+
+ if (bind(s, (struct sockaddr *)&local, len) == -1) {
+ perror("bind");
+ exit(1);
+ }
+/*
+ if( setsockopt( s, SOL_SOCKET, SO_PASSCRED, &on, sizeof( on ) ) )
+ {
+ perror( "setsockopt");
+ exit(1);
+ }
+*/
+
+ if( fcntl( s, F_SETFL, O_NONBLOCK ) != 0 )
+ {
+ wperror( L"fcntl" );
+ close( s );
+ return -1;
+
+ }
+
+ return s;
+}
+
+void enqueue( const void *k,
+ const void *v,
+ void *q)
+{
+ const wchar_t *key = (const wchar_t *)k;
+ const wchar_t *val = (const wchar_t *)v;
+ queue_t *queue = (queue_t *)q;
+
+ message_t *msg = create_message( SET, key, val );
+ msg->count=1;
+
+ q_put( queue, msg );
+}
+
+void enqueue_all( connection_t *c )
+{
+ hash_foreach2( &env_universal_var,
+ &enqueue,
+ (void *)&c->unsent );
+ try_send_all( c );
+}
+
+void broadcast( int type, const wchar_t *key, const wchar_t *val )
+{
+ connection_t *c;
+ message_t *msg;
+
+ if( !conn )
+ return;
+
+ msg = create_message( type, key, val );
+
+ /*
+ Don't merge loops, or try_send_all can free the message prematurely
+ */
+
+ for( c = conn; c; c=c->next )
+ {
+ msg->count++;
+ q_put( &c->unsent, msg );
+ }
+
+ for( c = conn; c; c=c->next )
+ {
+ try_send_all( c );
+ }
+}
+
+void daemonize()
+{
+ /*
+ Fork, and let parent exit
+ */
+ switch( fork() )
+ {
+ case -1:
+ debug( 0, L"Could not put fishd in background. Quitting" );
+ wperror( L"fork" );
+ exit(1);
+
+ case 0:
+ {
+ struct sigaction act;
+ sigemptyset( & act.sa_mask );
+ act.sa_flags=0;
+ act.sa_handler=SIG_IGN;
+ sigaction( SIGHUP, &act, 0);
+ break;
+ }
+
+ default:
+ {
+ debug( 0, L"Parent calling exit" );
+ exit(0);
+ }
+ }
+
+ /*
+ Put ourself in out own processing group
+ */
+ setpgrp();
+
+ /*
+ Close stdin and stdout
+ */
+ close( 0 );
+ close( 1 );
+
+}
+
+
+void load()
+{
+ struct passwd *pw;
+ char *name;
+ char *dir = getenv( "HOME" );
+ if( !dir )
+ {
+ pw = getpwuid( getuid() );
+ dir = pw->pw_dir;
+ }
+
+ name = malloc( strlen(dir)+ strlen(FILE)+ 2 );
+ strcpy( name, dir );
+ strcat( name, "/" );
+ strcat( name, FILE );
+
+ debug( 1, L"Open file for loading: '%s'", name );
+
+ connection_t load;
+ load.fd = open( name, O_RDONLY);
+
+ free( name );
+
+ if( load.fd == -1 )
+ {
+ debug( 0, L"Could not open save file. No previous saves?" );
+ }
+ debug( 1, L"Load input file on fd %d", load.fd );
+ sb_init( &load.input );
+ memset (&load.wstate, '\0', sizeof (mbstate_t));
+ read_message( &load );
+ sb_destroy( &load.input );
+ close( load.fd );
+}
+
+void save()
+{
+ struct passwd *pw;
+ char *name;
+ char *dir = getenv( "HOME" );
+ if( !dir )
+ {
+ pw = getpwuid( getuid() );
+ dir = pw->pw_dir;
+ }
+
+ name = malloc( strlen(dir)+ strlen(FILE)+ 2 );
+ strcpy( name, dir );
+ strcat( name, "/" );
+ strcat( name, FILE );
+
+ debug( 1, L"Open file for saving: '%s'", name );
+
+ connection_t save;
+ save.fd = open( name, O_CREAT | O_TRUNC | O_WRONLY);
+ free( name );
+
+ if( save.fd == -1 )
+ {
+ debug( 0, L"Could not open save file" );
+ wperror( L"open" );
+ exit(1);
+ }
+ debug( 1, L"File open on fd %d'", save.fd );
+ q_init( &save.unsent );
+ enqueue_all( &save );
+ close( save.fd );
+ q_destroy( &save.unsent );
+}
+
+static void init()
+{
+ program_name=L"fishd";
+ sock = get_socket();
+ if (listen(sock, 64) == -1)
+ {
+ wperror(L"listen");
+ exit(1);
+ }
+
+ daemonize();
+
+ fish_setlocale( LC_ALL, L"" );
+
+ env_universal_common_init( &broadcast );
+
+ load();
+}
+
+int main( int argc, char ** argv )
+{
+ int child_socket, t;
+ struct sockaddr_un remote;
+ int max_fd;
+ int update_count=0;
+
+ fd_set read_fd, write_fd;
+
+ init();
+
+ while(1)
+ {
+ connection_t *c;
+ int res;
+
+ t=sizeof( remote );
+
+ FD_ZERO( &read_fd );
+ FD_ZERO( &write_fd );
+ FD_SET( sock, &read_fd );
+ max_fd = sock+1;
+ for( c=conn; c; c=c->next )
+ {
+ FD_SET( c->fd, &read_fd );
+ max_fd = maxi( max_fd, c->fd+1);
+
+ if( ! q_empty( &c->unsent ) )
+ {
+ FD_SET( c->fd, &write_fd );
+ }
+ }
+
+ res=select( max_fd, &read_fd, &write_fd, 0, 0 );
+
+ if( res==-1 )
+ {
+ wperror( L"select" );
+ exit(1);
+ }
+
+ if( FD_ISSET( sock, &read_fd ) )
+ {
+ if( (child_socket =
+ accept( sock,
+ (struct sockaddr *)&remote,
+ &t) ) == -1) {
+ wperror( L"accept" );
+ exit(1);
+ }
+ else
+ {
+ debug( 1, L"Connected with new child on fd %d", child_socket );
+
+ if( fcntl( child_socket, F_SETFL, O_NONBLOCK ) != 0 )
+ {
+ wperror( L"fcntl" );
+ close( child_socket );
+ }
+ else
+ {
+ connection_t *new = malloc( sizeof(connection_t));
+ new->fd = child_socket;
+ new->next = conn;
+ q_init( &new->unsent );
+ new->killme=0;
+ sb_init( &new->input );
+ memset (&new->wstate, '\0', sizeof (mbstate_t));
+ send( new->fd, GREETING, strlen(GREETING), MSG_DONTWAIT );
+ enqueue_all( new );
+ conn=new;
+ }
+ }
+ }
+
+ for( c=conn; c; c=c->next )
+ {
+ if( FD_ISSET( c->fd, &write_fd ) )
+ {
+ try_send_all( c );
+ }
+ }
+
+ for( c=conn; c; c=c->next )
+ {
+ if( FD_ISSET( c->fd, &read_fd ) )
+ {
+ read_message( c );
+
+ /*
+ Occasionally we save during normal use, so that we
+ won't lose everything on a system crash
+ */
+ update_count++;
+ if( update_count >= 8 )
+ {
+ save();
+ update_count = 0;
+ }
+ }
+ }
+
+ connection_t *prev=0;
+ c=conn;
+
+ while( c )
+ {
+ if( c->killme )
+ {
+ debug( 1, L"Close connection %d", c->fd );
+
+ close(c->fd );
+ sb_destroy( &c->input );
+
+ while( !q_empty( &c->unsent ) )
+ {
+ message_t *msg = (message_t *)q_get( &c->unsent );
+ msg->count--;
+ if( !msg->count )
+ free( msg );
+ }
+
+ q_destroy( &c->unsent );
+ if( prev )
+ {
+ prev->next=c->next;
+ }
+ else
+ {
+ conn=c->next;
+ }
+
+ free(c);
+
+ c=(prev?prev->next:conn);
+
+ }
+ else
+ {
+ prev=c;
+ c=c->next;
+ }
+ }
+ if( !conn )
+ {
+ debug( 0, L"No more clients. Quitting" );
+ save();
+ env_universal_common_destroy();
+ exit(0);
+ c=c->next;
+ }
+
+ }
+}
diff --git a/function.c b/function.c
new file mode 100644
index 00000000..385c8c89
--- /dev/null
+++ b/function.c
@@ -0,0 +1,144 @@
+/** \file function.c
+ Functions for storing and retrieving function information.
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <unistd.h>
+#include <termios.h>
+#include <signal.h>
+
+
+#include "config.h"
+#include "util.h"
+#include "function.h"
+#include "proc.h"
+#include "parser.h"
+#include "common.h"
+#include "intern.h"
+
+/**
+ Table containing all functions
+*/
+static hash_table_t function;
+
+/**
+ Struct describing a function
+*/
+typedef struct
+{
+ /** Function definition */
+ wchar_t *cmd;
+ /** Function description */
+ wchar_t *desc;
+}
+function_data_t;
+
+/**
+ Free all contents of an entry to the function hash table
+*/
+static void clear_function_entry( const void *key,
+ const void *data )
+{
+ function_data_t *d = (function_data_t *)data;
+ free( (void *)d->cmd );
+ free( (void *)d->desc );
+ free( (void *)d );
+}
+
+void function_init()
+{
+ hash_init( &function,
+ &hash_wcs_func,
+ &hash_wcs_cmp );
+}
+
+void function_destroy()
+{
+ hash_foreach( &function, &clear_function_entry );
+ hash_destroy( &function );
+}
+
+void function_add( const wchar_t *name,
+ const wchar_t *val,
+ const wchar_t *desc )
+{
+ if( function_exists( name ) )
+ function_remove( name );
+
+ function_data_t *d = malloc( sizeof( function_data_t ) );
+ d->cmd = wcsdup( val );
+ d->desc = desc?wcsdup( desc ):0;
+ hash_put( &function, intern(name), d );
+}
+
+int function_exists( const wchar_t *cmd )
+{
+ return (hash_get(&function, cmd) != 0 );
+}
+
+void function_remove( const wchar_t *name )
+{
+ void *key;
+ function_data_t *d;
+
+ hash_remove( &function,
+ name,
+ (const void **) &key,
+ (const void **)&d );
+
+ if( !d )
+ return;
+
+ clear_function_entry( key, d );
+}
+
+const wchar_t *function_get_definition( const wchar_t *argv )
+{
+ function_data_t *data =
+ (function_data_t *)hash_get( &function, argv );
+ if( data == 0 )
+ return 0;
+ return data->cmd;
+}
+
+const wchar_t *function_get_desc( const wchar_t *argv )
+{
+ function_data_t *data =
+ (function_data_t *)hash_get( &function, argv );
+ if( data == 0 )
+ return 0;
+
+ return data->desc?data->desc:data->cmd;
+}
+
+void function_set_desc( const wchar_t *name, const wchar_t *desc )
+{
+ function_data_t *data =
+ (function_data_t *)hash_get( &function, name );
+ if( data == 0 )
+ return;
+
+ data->desc =wcsdup(desc);
+}
+
+/**
+ Helper function for removing hidden functions
+*/
+static void get_names_internal( const void *key,
+ const void *val,
+ void *aux )
+{
+ wchar_t *name = (wchar_t *)key;
+ if( name[0] != L'_' )
+ al_push( (array_list_t *)aux, name );
+}
+
+void function_get_names( array_list_t *list, int get_hidden )
+{
+ if( get_hidden )
+ hash_get_keys( &function, list );
+ else
+ hash_foreach2( &function, &get_names_internal, list );
+
+}
diff --git a/function.h b/function.h
new file mode 100644
index 00000000..b0e8f1c3
--- /dev/null
+++ b/function.h
@@ -0,0 +1,63 @@
+/** \file function.h
+
+ Prototypes for functions for storing and retrieving function
+ information. Actual function evaluation is taken care of by the
+ parser and to some degree the builtin handling library.
+*/
+
+/**
+ Initialize function data
+*/
+void function_init();
+/**
+ Destroy function data
+*/
+void function_destroy();
+
+/**
+ Add an function. The parameters values are copied and should be freed by the caller.
+*/
+void function_add( const wchar_t *name,
+ const wchar_t *val,
+ const wchar_t *desc );
+
+/**
+ Remove the function with the specified name.
+*/
+void function_remove( const wchar_t *name );
+
+/**
+ Returns true if the function with the name name uses internal variables, false otherwise.
+*/
+int function_use_vars( const wchar_t *name );
+
+/**
+ Returns the definition of the function with the name \c name.
+*/
+const wchar_t *function_get_definition( const wchar_t *name );
+
+/**
+ Returns the description of the function with the name \c name.
+*/
+const wchar_t *function_get_desc( const wchar_t *name );
+
+/**
+ Sets the description of the function with the name \c name.
+*/
+void function_set_desc( const wchar_t *name, const wchar_t *desc );
+
+/**
+ Returns true if the function witrh the name name exists.
+*/
+int function_exists( const wchar_t *name);
+
+/**
+ Insert all function names into l. These are not copies of the strings and should not be freed after use.
+
+ \param list the list to add the names to
+ \param get_hidden whether to include hidden functions, i.e. ones starting with an underscore
+*/
+void function_get_names( array_list_t *list,
+ int get_hidden );
+
+
diff --git a/gen_hdr.sh b/gen_hdr.sh
new file mode 100755
index 00000000..7bf01312
--- /dev/null
+++ b/gen_hdr.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# This little script calls the man command to render a manual page and
+# pipes the result into the gen_hdr2 program to convert the output
+# into a C string literal.
+
+# NAME is the name of the function we are generating documentation for.
+NAME=$(basename $1 .doxygen)
+
+# Render the page
+nroff -man doc_src/builtin_doc/man/man1/${NAME}.1 | col -b | cat -s | sed -e '$d' | ./gen_hdr2
+
diff --git a/gen_hdr2.c b/gen_hdr2.c
new file mode 100644
index 00000000..1a13fbb7
--- /dev/null
+++ b/gen_hdr2.c
@@ -0,0 +1,76 @@
+/** \file gen_hdr2.c
+ A program that reads data from stdin and outputs it as a C string.
+
+ It is used as a part of the build process to generate help texts
+ for the built in commands.
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+/**
+ The main function, does all the work
+*/
+int main()
+{
+ int line = 0;
+ printf( "\t\t\"" );
+ int c;
+ int count=0;
+ while( (c=getchar()) != EOF )
+ {
+ if( c == '\n' )
+ line++;
+
+ if( line > 4 )
+ break;
+ }
+
+ while( (c=getchar()) != EOF )
+ {
+ if( (c >= 'a' && c <= 'z' ) ||
+ (c >= 'A' && c <= 'Z' ) ||
+ (c >= '0' && c <= '9' ) ||
+ ( strchr(" ,.!;:-_#$%&(){}[]<>=?+-*/'",c) != 0) )
+ {
+ count++;
+ putchar(c);
+ }
+ else
+ {
+ switch(c)
+ {
+ case '\n':
+ printf( "\\n" );
+ printf( "\"\n\t\t\"" );
+ count =0;
+ break;
+ case '\t':
+ printf( "\\t" );
+ count +=2;
+ break;
+ case '\r':
+ printf( "\\r" );
+ count +=2;
+ break;
+
+ case '\"':
+ case '\\':
+ printf( "\\%c",c );
+ count +=2;
+ break;
+
+ default:
+ count +=7;
+ printf( "\\x%02x\" \"", c );
+ break;
+ }
+ }
+ if( count > 60 )
+ {
+ count=0;
+ printf( "\"\n\t\t\"" );
+ }
+ }
+ printf( "\"" );
+ return 0;
+}
diff --git a/highlight.c b/highlight.c
new file mode 100644
index 00000000..39385cad
--- /dev/null
+++ b/highlight.c
@@ -0,0 +1,557 @@
+/** \file highlight.c
+ Functions for syntax highlighting
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <signal.h>
+
+#include "config.h"
+#include "util.h"
+#include "wutil.h"
+#include "highlight.h"
+#include "tokenizer.h"
+#include "proc.h"
+#include "parser.h"
+#include "builtin.h"
+#include "function.h"
+#include "env.h"
+#include "expand.h"
+#include "sanity.h"
+#include "common.h"
+#include "complete.h"
+#include "output.h"
+
+static void highlight_universal_internal( wchar_t * buff,
+ int *color,
+ int pos,
+ array_list_t *error );
+
+
+/**
+ The environment variables used to specify the color of different tokens.
+*/
+static wchar_t *hightlight_var[] =
+{
+ L"fish_color_normal",
+ L"fish_color_command",
+ L"fish_color_subshell",
+ L"fish_color_redirection",
+ L"fish_color_end",
+ L"fish_color_error",
+ L"fish_color_param",
+ L"fish_color_comment",
+ L"fish_color_match",
+ L"fish_color_search_match",
+ L"fish_color_pager_prefix",
+ L"fish_color_pager_completion",
+ L"fish_color_pager_description",
+ L"fish_color_pager_progress"
+}
+ ;
+
+
+int highlight_get_color( int highlight )
+{
+ if( highlight < 0 )
+ return FISH_COLOR_NORMAL;
+ if( highlight >= (12) )
+ return FISH_COLOR_NORMAL;
+
+ wchar_t *val = env_get( hightlight_var[highlight]);
+ if( val == 0 )
+ val = env_get( hightlight_var[HIGHLIGHT_NORMAL]);
+
+ if( val == 0 )
+ {
+ return FISH_COLOR_NORMAL;
+ }
+
+ int i;
+ int color;
+
+ return output_color_code( val );
+
+}
+
+
+void highlight_shell( wchar_t * buff,
+ int *color,
+ int pos,
+ array_list_t *error )
+{
+ tokenizer tok;
+ int had_cmd=0;
+ int i;
+ int last_val;
+ wchar_t *last_cmd=0;
+ int len = wcslen(buff);
+
+ if( !len )
+ return;
+
+ for( i=0; buff[i] != 0; i++ )
+ color[i] = -1;
+
+ for( tok_init( &tok, buff, TOK_SHOW_COMMENTS );
+ tok_has_next( &tok );
+ tok_next( &tok ) )
+ {
+ int last_type = tok_last_type( &tok );
+ int prev_argc=0;
+
+ switch( last_type )
+ {
+ case TOK_STRING:
+ {
+ if( had_cmd )
+ {
+
+ /*Parameter */
+ wchar_t *param = tok_last( &tok );
+ if( param[0] == L'-' )
+ {
+ if( complete_is_valid_option( last_cmd, param, error ))
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_PARAM;
+ else
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR;
+ }
+ else
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_PARAM;
+ }
+ }
+ else
+ {
+ prev_argc=0;
+
+ /*
+ Command. First check that the command actually exists.
+ */
+ wchar_t *cmd =
+ (last_type == TOK_STRING) ?
+ expand_one(wcsdup(tok_last( &tok )),EXPAND_SKIP_SUBSHELL | EXPAND_SKIP_VARIABLES) :
+ wcsdup(tok_last( &tok ));
+ if( cmd == 0 )
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR;
+ }
+ else
+ {
+ wchar_t *tmp;
+ int is_cmd = 0;
+ int is_subcommand = 0;
+ int mark = tok_get_pos( &tok );
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_COMMAND;
+
+
+ if( parser_is_subcommand( cmd ) )
+ {
+ tok_next( &tok );
+ if(( wcscmp( L"-h", tok_last( &tok ) ) == 0 ) ||
+ ( wcscmp( L"--help", tok_last( &tok ) ) == 0 ) )
+ {
+ /*
+ The builtin and command builtins
+ are normally followed by another
+ command, but if they are invoked
+ with the -h option, their help text
+ is displayed instead
+ */
+ }
+ else
+ {
+ is_subcommand = 1;
+ }
+ tok_set_pos( &tok, mark );
+ }
+
+ if( !is_subcommand )
+ {
+ /*
+ OK, this is a command, it has been
+ successfully expanded and everything
+ looks ok. Lets check if the command
+ exists.
+ */
+ is_cmd |= builtin_exists( cmd );
+ is_cmd |= function_exists( cmd );
+ is_cmd |= (tmp=get_filename( cmd )) != 0;
+
+ /*
+ Could not find the command. Maybe it is a path for a implicit cd command.
+ Lets check!
+ */
+ if( !is_cmd )
+ {
+ wchar_t *pp = parser_cdpath_get( cmd );
+ if( pp )
+ {
+ free( pp );
+ is_cmd = 1;
+ }
+ }
+
+ free(tmp);
+ if( is_cmd )
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_COMMAND;
+ }
+ else
+ {
+ if( error )
+ al_push( error, wcsdupcat2 ( L"Unknown command \'", cmd, L"\'", 0 ));
+ color[ tok_get_pos( &tok ) ] = (HIGHLIGHT_ERROR);
+ }
+ had_cmd = 1;
+ }
+ free(cmd);
+
+ if( had_cmd )
+ {
+ if( last_cmd )
+ free( last_cmd );
+ last_cmd = wcsdup( tok_last( &tok ) );
+ }
+
+ }
+
+ }
+ break;
+ }
+
+ case TOK_REDIRECT_OUT:
+ case TOK_REDIRECT_IN:
+ case TOK_REDIRECT_APPEND:
+ case TOK_REDIRECT_FD:
+ {
+ if( !had_cmd )
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR;
+ if( error )
+ al_push( error, wcsdup ( L"Redirection without a command" ) );
+ break;
+ }
+
+ wchar_t *target=0;
+
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_REDIRECTION;
+ tok_next( &tok );
+
+ /*
+ Check that we are redirecting into a file
+ */
+
+ switch( tok_last_type( &tok ) )
+ {
+ case TOK_STRING:
+ {
+ target = expand_one( wcsdup( tok_last( &tok ) ), EXPAND_SKIP_SUBSHELL);
+ /*
+ Redirect filename may contain a subshell.
+ If so, it will be ignored/not flagged.
+ */
+ }
+ break;
+ default:
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR;
+ if( error )
+ al_push( error, wcsdup ( L"Invalid redirection" ) );
+ }
+
+ }
+
+ if( target != 0 )
+ {
+ wchar_t *dir = wcsdup( target );
+ wchar_t *dir_end = wcsrchr( dir, L'/' );
+ struct stat buff;
+ /*
+ If file is in directory other than '.', check
+ that the directory exists.
+ */
+ if( dir_end != 0 )
+ {
+ *dir_end = 0;
+ if( wstat( dir, &buff ) == -1 )
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR;
+ if( error )
+ al_push( error, wcsdupcat2( L"Directory \'", dir, L"\' does not exist", 0 ) );
+
+ }
+ }
+ free( dir );
+
+ /*
+ If the file is read from or appended to, check
+ if it exists.
+ */
+ if( last_type == TOK_REDIRECT_IN ||
+ last_type == TOK_REDIRECT_APPEND )
+ {
+ if( wstat( target, &buff ) == -1 )
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR;
+ if( error )
+ al_push( error, wcsdupcat2( L"File \'", target, L"\' does not exist", 0 ) );
+ }
+ }
+ free( target );
+ }
+ break;
+ }
+
+ case TOK_PIPE:
+ case TOK_BACKGROUND:
+ {
+ if( had_cmd )
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_END;
+ had_cmd = 0;
+ }
+ else
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR;
+ if( error )
+ al_push( error, wcsdup ( L"No job to put in background" ) );
+ }
+
+ break;
+ }
+
+ case TOK_END:
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_END;
+ had_cmd = 0;
+ break;
+ }
+
+ case TOK_COMMENT:
+ {
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_COMMENT;
+ break;
+ }
+
+ case TOK_ERROR:
+ default:
+ {
+ /*
+ If the tokenizer reports an error, highlight it as such.
+ */
+ if( error )
+ al_push( error, wcsdup ( tok_last( &tok) ) );
+ color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR;
+ break;
+ }
+ }
+ }
+
+ if( last_cmd )
+ free( last_cmd );
+
+ tok_destroy( &tok );
+
+ /*
+ Locate and syntax highlight subshells recursively
+ */
+
+ wchar_t *buffcpy = wcsdup( buff );
+ wchar_t *subpos=buffcpy;
+ int done=0;
+
+ while( 1 )
+ {
+ wchar_t *begin, *end;
+
+ if( expand_locate_subshell( subpos,
+ &begin,
+ &end,
+ 1) <= 0)
+ {
+ break;
+ }
+
+ if( !*end )
+ done=1;
+ else
+ *end=0;
+
+ highlight_shell( begin+1, color +(begin-buffcpy)+1, -1, error );
+ color[end-buffcpy]=HIGHLIGHT_PARAM;
+
+ if( done )
+ break;
+
+ subpos = end+1;
+ }
+ free( buffcpy );
+
+
+ last_val=0;
+ for( i=0; buff[i] != 0; i++ )
+ {
+ if( color[i] >= 0 )
+ last_val = color[i];
+ else
+ color[i] = last_val;
+ }
+
+
+ highlight_universal_internal( buff, color, pos, error );
+
+ /*
+ Spaces should not be highlighted at all, since it makes cursor look funky in some terminals
+ */
+ for( i=0; buff[i]; i++ )
+ {
+ if( iswspace(buff[i]) )
+ {
+ color[i]=0;
+ }
+ }
+}
+
+/**
+ Perform quote and parenthesis highlighting on the specified string.
+*/
+static void highlight_universal_internal( wchar_t * buff,
+ int *color,
+ int pos,
+ array_list_t *error )
+{
+
+ if( (pos >= 0) && (pos < wcslen(buff)) )
+ {
+
+ /*
+ Highlight matching quotes
+ */
+ if( (buff[pos] == L'\'') || (buff[pos] == L'\"') )
+ {
+
+ array_list_t l;
+ al_init( &l );
+
+ int level=0;
+ wchar_t prev_q=0;
+
+ wchar_t *str=buff;
+
+ int match_found=0;
+
+ while(*str)
+ {
+ switch( *str )
+ {
+ case L'\\':
+ str++;
+ break;
+ case L'\"':
+ case L'\'':
+ if( level == 0 )
+ {
+ level++;
+ al_push( &l, (void *)(str-buff) );
+ prev_q = *str;
+ }
+ else
+ {
+ if( prev_q == *str )
+ {
+ int pos1, pos2;
+
+ level--;
+ pos1 = (int)al_pop( &l );
+ pos2 = str-buff;
+ if( pos1==pos || pos2==pos )
+ {
+ color[pos1]|=HIGHLIGHT_MATCH<<8;
+ color[pos2]|=HIGHLIGHT_MATCH<<8;
+ match_found = 1;
+
+ }
+ prev_q = *str==L'\"'?L'\'':L'\"';
+ }
+ else
+ {
+ level++;
+ al_push( &l, (void *)(str-buff) );
+ prev_q = *str;
+ }
+ }
+
+ break;
+ }
+ if( (*str == L'\0'))
+ break;
+
+ str++;
+ }
+
+ al_destroy( &l );
+
+ if( !match_found )
+ color[pos] = HIGHLIGHT_ERROR<<8;
+ }
+
+ /*
+ Highlight matching parenthesis
+ */
+ if( wcschr( L"()[]{}", buff[pos] ) )
+ {
+ int step = wcschr(L"({[", buff[pos])?1:-1;
+ wchar_t dec_char = *(wcschr( L"()[]{}", buff[pos] ) + step);
+ wchar_t inc_char = buff[pos];
+ int level = 0;
+ wchar_t *str = &buff[pos];
+ int match_found=0;
+
+
+ while( (str >= buff) && *str)
+ {
+ if( *str == inc_char )
+ level++;
+ if( *str == dec_char )
+ level--;
+ if( level == 0 )
+ {
+ int pos2 = str-buff;
+ color[pos]|=HIGHLIGHT_MATCH<<8;
+ color[pos2]|=HIGHLIGHT_MATCH<<8;
+ match_found=1;
+ break;
+ }
+ str+= step;
+ }
+
+ if( !match_found )
+ color[pos] = HIGHLIGHT_ERROR<<8;
+ }
+ }
+}
+
+void highlight_universal( wchar_t * buff,
+ int *color,
+ int pos,
+ array_list_t *error )
+{
+ int i;
+
+ for( i=0; buff[i] != 0; i++ )
+ color[i] = 0;
+
+ highlight_universal_internal( buff, color, pos, error );
+}
diff --git a/highlight.h b/highlight.h
new file mode 100644
index 00000000..fbea36e5
--- /dev/null
+++ b/highlight.h
@@ -0,0 +1,39 @@
+/** \file highlight.h
+ Prototypes for functions for syntax highlighting
+*/
+
+/**
+ Perform syntax highlighting for the shell commands in buff. The result is
+ stored in the color array as a color_code from the HIGHLIGHT_ enum
+ for each character in buff.
+
+ \param buff The buffer on which to perform syntax highlighting
+ \param color The array in wchich to store the color codes. The first 8 bits are used for fg color, the next 8 bits for bg color.
+ \param pos the cursor position. Used for quote matching, etc.
+ \param error a list in which a description of each error will be inserted. May be 0, in whcich case no error descriptions will be generated.
+*/
+void highlight_shell( wchar_t * buff, int *color, int pos, array_list_t *error );
+
+/**
+ Perform syntax highlighting for the text in buff. Matching quotes and paranthesis are highlighted. The result is
+ stored in the color array as a color_code from the HIGHLIGHT_ enum
+ for each character in buff.
+
+ \param buff The buffer on which to perform syntax highlighting
+ \param color The array in wchich to store the color codes. The first 8 bits are used for fg color, the next 8 bits for bg color.
+ \param pos the cursor position. Used for quote matching, etc.
+ \param error a list in which a description of each error will be inserted. May be 0, in whcich case no error descriptions will be generated.
+*/
+void highlight_universal( wchar_t * buff, int *color, int pos, array_list_t *error );
+
+/**
+ Translate from HIGHLIGHT_* to FISH_COLOR_* according to environment
+ variables. Defaults to FISH_COLOR_NORMAL.
+
+ Example:
+
+ If the environment variable FISH_FISH_COLOR_ERROR is set to 'red', a
+ call to highlight_get_color( HIGHLIGHT_ERROR) will return
+ FISH_COLOR_RED.
+*/
+int highlight_get_color( int highlight );
diff --git a/history.c b/history.c
new file mode 100644
index 00000000..c34997b3
--- /dev/null
+++ b/history.c
@@ -0,0 +1,597 @@
+/** \file history.c
+ History functions, part of the user interface.
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <errno.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+#include "config.h"
+#include "util.h"
+#include "wutil.h"
+#include "history.h"
+#include "common.h"
+#include "reader.h"
+#include "env.h"
+#include "sanity.h"
+
+/*
+ The history is implemented using a linked list. Searches are done
+ using linear search.
+*/
+
+
+/**
+ A struct describing the state of the interactive history
+ list. Multiple states can be created. Use \c history_set_mode() to
+ change between history contexts.
+*/
+typedef struct
+{
+ /**
+ Number of entries
+ */
+ int count;
+ /**
+ Last history item
+ */
+ ll_node_t *last;
+ /**
+ The last item loaded from file
+ */
+ ll_node_t *last_loaded;
+}
+ history_data;
+
+
+static ll_node_t /** Last item in history */ *history_last=0, /** Current search position*/ *history_current =0;
+
+/**
+ We only try to save the part of the history that was written in
+ this session. This pointer keeps track of where the cutoff is.
+
+ The reason for wanting to do this is so that when two concurrent
+ fish sessions have exited, the latter one to exit won't push the
+ added history items of the prior session to the top of the history.
+*/
+static ll_node_t *last_loaded=0;
+
+/**
+ past_end is a kludge. It is set when the current history item is
+ unset, i.e. a new entry is being typed.
+*/
+static int past_end =1;
+
+/**
+ Items in history
+*/
+static int history_count=0;
+
+/**
+ The name of the current history list. The name is used to switch between history lists for different commands as sell as for deciding the name of the file to save the history in.
+*/
+static wchar_t *mode_name;
+
+/**
+ Hash table for storing all the history lists.
+*/
+static hash_table_t history_table;
+
+
+/**
+ Load history from ~/.fish_history
+*/
+static void history_load( wchar_t *name )
+{
+ wchar_t *fn;
+ wchar_t *buff=0;
+ int buff_len=0;
+ FILE *in_stream;
+ hash_table_t used;
+
+ block();
+ hash_init2( &used,
+ &hash_wcs_func,
+ &hash_wcs_cmp,
+ 4096 );
+
+ fn = wcsdupcat2( env_get(L"HOME"), L"/.", name, L"_history", 0 );
+
+ in_stream = wfopen( fn, "r" );
+
+ if( in_stream != 0 )
+ {
+ while( !feof( in_stream ) )
+ {
+ int buff_read = fgetws2( &buff, &buff_len, in_stream );
+ if( buff_read == -1 )
+ {
+ debug( 1, L"The following non-fatal error occurred while reading command history from \'%ls\':", name );
+ wperror( L"fgetws2" );
+ fclose( in_stream );
+ free( fn );
+ free( buff );
+ unblock();
+ return;
+ }
+
+ /*
+ We do not call history_add here, since that would make
+ history_load() take quadratic time, and may be
+ unacceptably on slow systems with a long history file.
+
+ Use a hashtable to check for duplicates instead.
+ */
+ if( !hash_get( &used,
+ buff ) )
+ {
+ history_count++;
+
+ history_current = malloc( sizeof( ll_node_t ) );
+ if( !history_current )
+ {
+ die_mem();
+
+ }
+
+ history_current->data=wcsdup( buff );
+
+ hash_put( &used,
+ history_current->data,
+ L"" );
+
+ history_current->next=0;
+ history_current->prev = history_last;
+ if( history_last != 0 )
+ {
+ history_last->next = history_current;
+ }
+ history_last = history_current;
+ }
+ }
+
+ fclose( in_stream );
+ }
+ else
+ {
+ if( errno != ENOENT )
+ {
+ debug( 1, L"The following non-fatal error occurred while reading command history from \'%ls\':", name );
+ wperror( L"fopen" );
+ }
+
+ }
+
+
+ hash_destroy( &used );
+
+ free( buff );
+ free( fn );
+ last_loaded = history_last;
+ unblock();
+}
+
+void history_init()
+{
+ hash_init( &history_table,
+ &hash_wcs_func,
+ &hash_wcs_cmp );
+}
+
+/**
+ Make sure the current history list is in the history_table.
+*/
+static void history_to_hash()
+{
+ history_data *d;
+
+ d = (history_data *)hash_get( &history_table,
+ mode_name );
+
+ if( !d )
+ d = malloc( sizeof(history_data));
+ d->last=history_last;
+ d->last_loaded=last_loaded;
+ d->count=history_count;
+
+ hash_put( &history_table,
+ mode_name,
+ d );
+
+}
+
+
+void history_set_mode( wchar_t *name )
+{
+ history_data *curr;
+
+ if( mode_name )
+ {
+ history_to_hash();
+ }
+ curr = (history_data *)hash_get( &history_table,
+ name );
+ if( curr )
+ {
+ mode_name = (wchar_t *)hash_get_key( &history_table,
+ name );
+ history_current = history_last = curr->last;
+ last_loaded = curr->last_loaded;
+ history_count = curr->count;
+ }
+ else
+ {
+ history_count=0;
+ history_last = history_current = last_loaded=0;
+ mode_name = wcsdup( name );
+ history_load( name );
+ }
+
+ past_end=1;
+
+}
+
+/**
+ Print history node
+*/
+static void history_save_node( ll_node_t *n, FILE *out )
+{
+ if( n==0 )
+ return;
+ history_save_node( n->prev, out );
+ fwprintf(out, L"%ls\n", (wchar_t *)(n->data) );
+}
+
+/**
+ Merge the process history with the history on file, write to
+ disc. The merging operation is done so that two concurrently
+ running shells wont erase each others history.
+*/
+static void history_save()
+{
+ wchar_t *fn;
+ FILE *out_stream;
+ /* First we save this sessions history in local variables */
+ ll_node_t *real_pos = history_last, *real_first = last_loaded;
+
+ if( !real_first )
+ {
+ real_first = history_current;
+ while( real_first->prev )
+ real_first = real_first->prev;
+ }
+
+ if( real_pos == real_first )
+ return;
+
+ /* Then we load the history from file into the global pointers */
+ history_last=history_current=last_loaded=0;
+ history_count=0;
+ past_end=1;
+
+ history_load( mode_name );
+ if( real_pos != 0 )
+ {
+ /*
+ Rewind the session history to the first item which was
+ added in this session
+ */
+ while( (real_pos->prev != 0) && (real_pos->prev != real_first) )
+ {
+ real_pos = real_pos->prev;
+ }
+
+ /* Free old history entries */
+ ll_node_t *kill_node_t = real_pos->prev;
+ while( kill_node_t != 0 )
+ {
+ ll_node_t *tmp = kill_node_t;
+ free( kill_node_t->data );
+ kill_node_t = kill_node_t->prev;
+ free( tmp );
+ }
+
+ /*
+ Add all the history entries from this session to the global
+ history, free the old version
+ */
+ while( real_pos != 0 )
+ {
+ ll_node_t *next = real_pos->next;
+ history_add( (wchar_t *)real_pos->data );
+
+ free( real_pos->data );
+ free( real_pos );
+ real_pos = next;
+ }
+ }
+
+ /* Save the global history */
+ {
+ fn = wcsdupcat2( env_get(L"HOME"), L"/.", mode_name, L"_history", 0 );
+
+ out_stream = wfopen( fn, "w" );
+ if( out_stream )
+ {
+ history_save_node( history_last, out_stream );
+ if( fclose( out_stream ) )
+ {
+ debug( 1, L"The following non-fatal error occurred while saving command history to \'%ls\':", fn );
+ wperror( L"fopen" );
+ }
+ }
+ else
+ {
+ debug( 1, L"The following non-fatal error occurred while saving command history to \'%ls\':", fn );
+ wperror( L"fopen" );
+ }
+ free( fn );
+ }
+
+}
+
+/**
+ Save the specified mode to file
+*/
+
+static void history_destroy_mode( const void *name, const void *link )
+{
+ mode_name = (wchar_t *)name;
+ history_data *d = (history_data *)link;
+ history_last = history_current = d->last;
+ last_loaded = d->last_loaded;
+ history_count = d->count;
+ past_end=1;
+
+// fwprintf( stderr, L"Destroy history mode \'%ls\'\n", mode_name );
+
+ if( history_last )
+ {
+ history_save();
+ history_current = history_last;
+ while( history_current != 0 )
+ {
+ ll_node_t *tmp = history_current;
+ free( history_current->data );
+ history_current = history_current->prev;
+ free( tmp );
+ }
+ }
+ free( d );
+ free( mode_name );
+}
+
+void history_destroy()
+{
+ /**
+ Make sure current mode is in table
+ */
+ history_to_hash();
+
+ /**
+ Save all modes in table
+ */
+ hash_foreach( &history_table,
+ &history_destroy_mode );
+
+ hash_destroy( &history_table );
+
+}
+
+
+
+/**
+ Internal search function
+*/
+static ll_node_t *history_find( ll_node_t *n, const wchar_t *s )
+{
+ if( n == 0 )
+ return 0;
+
+ if( wcscmp( s, (wchar_t *)n->data ) == 0 )
+ {
+ return n;
+ }
+ else
+ return history_find( n->prev, s );
+}
+
+
+void history_add( const wchar_t *str )
+{
+ ll_node_t *old_node;
+
+ if( wcslen( str ) == 0 )
+ return;
+
+ past_end=1;
+
+ old_node = history_find( history_last, str );
+
+ if( old_node == 0 )
+ {
+ history_count++;
+ history_current = malloc( sizeof( ll_node_t ) );
+ history_current->data=wcsdup( str );
+ }
+ else
+ {
+ if( old_node == last_loaded )
+ {
+ last_loaded = last_loaded->prev;
+ }
+
+ history_current = old_node;
+ if( old_node == history_last )
+ {
+ return;
+ }
+
+ if( old_node->prev != 0 )
+ old_node->prev->next = old_node->next;
+ if( old_node->next != 0 )
+ old_node->next->prev = old_node->prev;
+ }
+ history_current->next=0;
+ history_current->prev = history_last;
+ if( history_last != 0 )
+ {
+ history_last->next = history_current;
+ }
+ history_last = history_current;
+}
+
+/**
+ This function tests if the search string is a match for the given string
+*/
+static int history_test( const wchar_t *needle, const wchar_t *haystack )
+{
+
+/*
+ return wcsncmp( haystack, needle, wcslen(needle) )==0;
+*/
+ return (int)wcsstr( haystack, needle );
+}
+
+const wchar_t *history_prev_match( const wchar_t *str )
+{
+ if( history_current == 0 )
+ return str;
+
+ if( history_current->prev == 0 )
+ {
+ if( history_test( str, history_current->data ) )
+ return (wchar_t *)history_current->data;
+ else
+ return str;
+ }
+ if( past_end )
+ past_end = 0;
+ else
+ history_current = history_current->prev;
+
+ if( history_test( str, history_current->data) )
+ return (wchar_t *)history_current->data;
+ else
+ return history_prev_match( str );
+}
+
+
+const wchar_t *history_next_match( const wchar_t *str)
+{
+ if( history_current == 0 )
+ return str;
+
+ if( history_current->next == 0 )
+ {
+ past_end = 1;
+ return str;
+ }
+ history_current = history_current->next;
+
+ if( history_test( str, history_current->data ) )
+ return (wchar_t *)history_current->data;
+ else
+ return history_next_match( str );
+}
+
+void history_reset()
+{
+ history_current = history_last;
+}
+
+/**
+ Move to first history item
+*/
+void history_first()
+{
+ while( history_current->prev )
+ history_current = history_current->prev;
+
+}
+
+wchar_t *history_get( int idx )
+{
+ ll_node_t *n=history_last;
+ int i;
+ if( idx<0)
+ {
+ debug( 1, L"Tried to access negative history index %d", idx );
+ return 0;
+ }
+
+ for( i=0; i<idx; i++ )
+ {
+ if( !n )
+ break;
+ n=n->prev;
+ }
+ return n?n->data:0;
+}
+
+
+void history_sanity_check()
+{
+ return;
+
+ if( history_current != 0 )
+ {
+ int i;
+ int history_ok = 1;
+ int found_current = 0;
+
+ validate_pointer( history_last, L"History root", 1);
+
+ ll_node_t *tmp = history_last;
+ for( i=0; i<history_count; i++ )
+ {
+ found_current |= tmp == history_current;
+
+ if( tmp == 0 )
+ {
+ history_ok = 0;
+ debug( 1, L"History items missing" );
+ break;
+ }
+
+ if( (tmp->prev != 0) && (tmp->prev->next != tmp ) )
+ {
+ history_ok = 0;
+ debug( 1, L"History items order is inconsistent" );
+ break;
+ }
+
+ validate_pointer( tmp->data, L"History data", 1);
+ if( tmp->data == 0 )
+ {
+ history_ok = 0;
+ debug( 1, L"Empty history item" );
+ break;
+ }
+ validate_pointer( tmp->prev, L"History node", 1);
+ tmp = tmp->prev;
+ }
+ if( tmp != 0 )
+ {
+ history_ok = 0;
+ debug( 1, L"History list too long" );
+ }
+
+ if( (i!= history_count )|| (!found_current))
+ {
+ debug( 1, L"No history item selected" );
+ history_ok=0;
+ }
+
+ if( !history_ok )
+ {
+ sanity_lose();
+ }
+ }
+}
+
diff --git a/history.h b/history.h
new file mode 100644
index 00000000..dd83b1c8
--- /dev/null
+++ b/history.h
@@ -0,0 +1,65 @@
+/** \file history.h
+ Prototypes for history functions, part of the user interface.
+*/
+
+/**
+ Load history from file.
+*/
+void history_init();
+
+/**
+ Saves the new history to disc and frees all memory used by the history.
+*/
+void history_destroy();
+
+/**
+ Add a new history item to the bottom of the history, containing a
+ copy of str. Remove any duplicates. Moves the current item past the
+ end of the history list.
+*/
+void history_add( const wchar_t *str );
+
+/**
+ Find previous history item starting with str. If this moves before
+ the start of the history, str is returned.
+*/
+const wchar_t *history_prev_match( const wchar_t *str );
+
+/**
+ Return the specified history at the specified index, or 0 if out of bounds. 0 is the index of the current commandline.
+*/
+wchar_t *history_get( int idx );
+
+
+/**
+ Move to first history item
+*/
+void history_first();
+
+/**
+ Make current point to last history item
+*/
+void history_reset();
+
+
+/**
+ Find next history item starting with str. If this moves past
+ the end of the history, str is returned.
+*/
+const wchar_t *history_next_match( const wchar_t *str);
+
+
+/**
+ Set the current mode name for history. Each application that uses
+ the history has it's own mode. This must be called prior to any use
+ of the history.
+*/
+
+void history_set_mode( wchar_t *name );
+
+
+/**
+ Perform sanity checks
+*/
+void history_sanity_check();
+
diff --git a/input.c b/input.c
new file mode 100644
index 00000000..9abbb5d7
--- /dev/null
+++ b/input.c
@@ -0,0 +1,1210 @@
+/** \file input.c
+
+Functions for reading a character of input from stdin, using the
+inputrc information for key bindings.
+
+The inputrc file format was invented for the readline library. The
+implementation in fish is as of yet incomplete.
+
+*/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+#include <signal.h>
+#include <dirent.h>
+#include <wctype.h>
+
+
+#include "util.h"
+#include "wutil.h"
+#include "reader.h"
+#include "proc.h"
+#include "common.h"
+#include "sanity.h"
+#include "input_common.h"
+#include "input.h"
+#include "parser.h"
+#include "env.h"
+#include "expand.h"
+
+static void input_read_inputrc( wchar_t *fn );
+
+/**
+ Array containing characters which have been peeked by the escape
+ sequence matching functions and returned
+*/
+
+typedef struct
+{
+ wchar_t *seq; /**< Character sequence which generates this event */
+ wchar_t *seq_desc; /**< Description of the character sequence suitable for printing on-screen */
+ wchar_t *command; /**< command that should be evaluated by this mapping */
+
+}
+ mapping;
+
+/**
+ Names of all the readline functions supported
+*/
+const wchar_t *name_arr[] =
+{
+ L"beginning-of-line",
+ L"end-of-line",
+ L"forward-char",
+ L"backward-char",
+ L"forward-word",
+ L"backward-word",
+ L"history-search-backward",
+ L"history-search-forward",
+ L"delete-char",
+ L"backward-delete-char",
+ L"kill-line",
+ L"yank",
+ L"yank-pop",
+ L"complete",
+ L"beginning-of-history",
+ L"end-of-history",
+ L"delete-line",
+ L"backward-kill-line",
+ L"kill-whole-line",
+ L"kill-word",
+ L"backward-kill-word",
+ L"dump-functions",
+ L"clear-screen",
+ L"exit",
+ L"history-token-search-backward",
+ L"history-token-search-forward",
+ L"self-insert",
+ L"null"
+}
+ ;
+
+/**
+ Description of each supported readline function
+*/
+const wchar_t *desc_arr[] =
+{
+ L"Move to beginning of line",
+ L"Move to end of line",
+ L"Move forward one character",
+ L"Move backward one character",
+ L"Move forward one word",
+ L"Move backward one word",
+ L"Search backward through list of previous commands",
+ L"Search forward through list of previous commands",
+ L"Delete one character forward",
+ L"Delete one character backward",
+ L"Move contents from cursor to end of line to killring",
+ L"Paste contents of killring",
+ L"Rotate to previous killring entry",
+ L"Guess the rest of the next input token",
+ L"Move to first item of history",
+ L"Move to last item of history",
+ L"Clear current line",
+ L"Move contents from beginning of line to cursor to killring",
+ L"Move entire line to killring",
+ L"Move next word to killring",
+ L"Move previous word to killring",
+ L"Write out keybindings",
+ L"Clear entire screen",
+ L"Quit the running program",
+ L"Search backward through list of previous commands for matching token",
+ L"Search forward through list of previous commands for matching token",
+ L"Insert the pressed key",
+ L"Do nothing"
+}
+ ;
+
+/**
+ Internal code for each supported readline function
+*/
+const wchar_t code_arr[] =
+{
+ R_BEGINNING_OF_LINE,
+ R_END_OF_LINE,
+ R_FORWARD_CHAR,
+ R_BACKWARD_CHAR,
+ R_FORWARD_WORD,
+ R_BACKWARD_WORD,
+ R_HISTORY_SEARCH_BACKWARD,
+ R_HISTORY_SEARCH_FORWARD,
+ R_DELETE_CHAR,
+ R_BACKWARD_DELETE_CHAR,
+ R_KILL_LINE,
+ R_YANK,
+ R_YANK_POP,
+ R_COMPLETE,
+ R_BEGINNING_OF_HISTORY,
+ R_END_OF_HISTORY,
+ R_DELETE_LINE,
+ R_BACKWARD_KILL_LINE,
+ R_KILL_WHOLE_LINE,
+ R_KILL_WORD,
+ R_BACKWARD_KILL_WORD,
+ R_DUMP_FUNCTIONS,
+ R_CLEAR_SCREEN,
+ R_EXIT,
+ R_HISTORY_TOKEN_SEARCH_BACKWARD,
+ R_HISTORY_TOKEN_SEARCH_FORWARD,
+ R_SELF_INSERT,
+ R_NULL
+}
+ ;
+
+
+/**
+ List of all key bindings, as mappings from one sequence to either a character or a command
+*/
+static hash_table_t all_mappings;
+
+static array_list_t *current_mode_mappings, *current_application_mappings, *global_mappings;
+
+/**
+ Number of nested conditional statement levels that are not evaluated
+*/
+static int inputrc_skip_block_count=0;
+/**
+ Number of nested conditional statements that have evaluated to true
+*/
+static int inputrc_block_count=0;
+
+/**
+ True if syntax errors where found in the inputrc file
+*/
+static int inputrc_error = 0;
+
+wchar_t input_get_code( wchar_t *name )
+{
+
+ int i;
+ for( i = 0; i<(sizeof( code_arr )/sizeof(wchar_t)) ; i++ )
+ {
+ if( wcscmp( name, name_arr[i] ) == 0 )
+ {
+ return code_arr[i];
+ }
+ }
+ return -1;
+}
+
+/**
+ Returns the function name for the given function code.
+*/
+static const wchar_t *input_get_name( wchar_t c )
+{
+
+ int i;
+ for( i = 0; i<(sizeof( code_arr )/sizeof(wchar_t)) ; i++ )
+ {
+ if( c == code_arr[i] )
+ {
+ return name_arr[i];
+ }
+ }
+ return 0;
+}
+
+/**
+ Returns the function description for the given function code.
+*/
+static const wchar_t *input_get_desc( wchar_t c )
+{
+
+ int i;
+ for( i = 0; i<(sizeof( code_arr )/sizeof(wchar_t)) ; i++ )
+ {
+ if( c == code_arr[i] )
+ {
+ return desc_arr[i];
+ }
+ }
+ return 0;
+}
+
+void input_set_mode( wchar_t *name )
+{
+ current_mode_mappings = (array_list_t *)hash_get( &all_mappings, name );
+}
+
+void input_set_application( wchar_t *name )
+{
+ current_application_mappings = (array_list_t *)hash_get( &all_mappings, name );
+}
+
+static array_list_t *get_mapping( const wchar_t *mode )
+{
+
+ array_list_t * mappings = (array_list_t *)hash_get( &all_mappings, mode );
+
+ if( !mappings )
+ {
+ mappings = malloc( sizeof( array_list_t ));
+ al_init( mappings );
+
+ hash_put( &all_mappings, wcsdup(mode), mappings );
+
+ }
+ return mappings;
+
+}
+
+
+void add_mapping( const wchar_t *mode,
+ const wchar_t *s,
+ const wchar_t *d,
+ const wchar_t *c )
+{
+ int i;
+
+ array_list_t *mappings;
+
+ if( s == 0 )
+ return;
+
+ if( mode == 0 )
+ return;
+
+ mappings = get_mapping( mode );
+
+ for( i=0; i<al_get_count( mappings); i++ )
+ {
+ mapping *m = (mapping *)al_get( mappings, i );
+ if( wcscmp( m->seq, s ) == 0 )
+ {
+ free( m->command );
+ free( m->seq_desc );
+ m->seq_desc = wcsdup(d );
+ m->command=wcsdup(c);
+ return;
+ }
+ }
+
+ mapping *m = malloc( sizeof( mapping ) );
+ m->seq = wcsdup( s );
+ m->seq_desc = wcsdup(d );
+ m->command=wcsdup(c);
+ al_push( mappings, m );
+}
+
+/**
+ Compare sort order for two keyboard mappings. This function is made
+ to be suitable for use with the qsort method.
+*/
+static int mapping_compare( const void *a, const void *b )
+{
+ mapping *c = *(mapping **)a;
+ mapping *d = *(mapping **)b;
+
+// fwprintf( stderr, L"%ls %ls\n", c->seq_desc, d->seq_desc );
+
+ return wcscmp( c->seq_desc, d->seq_desc );
+
+}
+
+
+
+/**
+ Print a listing of all keybindings and a description of each
+ function. This is used by the dump-functions readline function.
+*/
+static void dump_functions()
+{
+/* int i;
+ fwprintf( stdout, L"\n" );
+
+ qsort(current_mappings.arr,
+ al_get_count( &mappings),
+ sizeof( void*),
+ &mapping_compare );
+
+ for( i=0; i<al_get_count( &mappings ); i++ )
+ {
+ mapping *m = (mapping *)al_get( &mappings, i );
+
+// write_sequence( m->seq );
+fwprintf( stdout,
+L"%ls: %ls\n",
+m->seq_desc,
+m->command );
+}
+repaint();*/
+}
+
+
+/**
+ Unescape special character from the specified inputrc-style key sequence.
+
+ \\C-a is expanded to 1, etc.
+*/
+static wchar_t *input_expand_sequence( const wchar_t *in )
+{
+ wchar_t *res = malloc( sizeof( wchar_t)*(4*wcslen(in)+1));
+ wchar_t *out=res;
+ int error = 0;
+
+ while( *in && !error)
+ {
+ switch( *in )
+ {
+ case L'\\':
+ {
+ in++;
+// fwprintf( stderr, L"Backslash\n" );
+ switch( *in )
+ {
+ case L'\0':
+ error = 1;
+ break;
+
+ case L'e':
+ *(out++)=L'\e';
+ break;
+
+ case L'\\':
+ case L'\"':
+ case L'\'':
+ *(out++)=*in;
+ break;
+
+ case L'b':
+ *(out++)=L'\b';
+ break;
+
+ case L'd':
+ {
+ wchar_t *str = str2wcs( key_dc );
+ wchar_t *p=str;
+ if( p )
+ {
+ while( *p )
+ {
+ *(out++)=*(p++);
+ }
+ free( str );
+ }
+ break;
+ }
+
+ case L'f':
+ *(out++)=L'\f';
+ break;
+
+ case L'n':
+ *(out++)=L'\n';
+ break;
+
+ case L'r':
+ *(out++)=L'\r';
+ break;
+
+ case L't':
+ *(out++)=L'\t';
+ break;
+
+ case L'v':
+ *(out++)=L'\v';
+ break;
+
+ case L'C':
+ {
+ //fwprintf( stderr, L"Control\n" );
+ in++;
+ if( *in != L'-' )
+ {
+ error=1;
+// fwprintf( stderr, L"no dash\n" );
+ break;
+ }
+ in++;
+
+ if( (*in >= L'a') &&
+ (*in <= L'z') )
+ {
+ *(out++)=*in-L'a'+1;
+ break;
+ }
+
+ if( (*in >= L'A') &&
+ (*in <= L'Z') )
+ {
+ *(out++)=*in-L'A'+1;
+ break;
+ }
+// fwprintf( stderr, L"No char after\n" );
+ error = 1;
+
+ break;
+ }
+
+ case L'M':
+ {
+ in++;
+ if( *in != L'-' )
+ {
+ error=1;
+// fwprintf( stderr, L"no dash\n" );
+ break;
+ }
+ in++;
+
+ if( (*in >= L'a') &&
+ (*in <= L'z') )
+ {
+ *(out++)=L'\e';
+ *(out++)=*in;
+ break;
+ }
+
+ if( (*in >= L'A') &&
+ (*in <= L'Z') )
+ {
+ *(out++)=L'\e';
+ *(out++)=*in;
+ break;
+ }
+// fwprintf( stderr, L"No char after\n" );
+ error = 1;
+
+ break;
+ }
+
+ default:
+ {
+ *(out++)=*in;
+ break;
+ }
+
+ }
+
+ break;
+ }
+ default:
+ {
+ *(out++)=*in;
+ break;
+ }
+ }
+ in++;
+ }
+
+ if( error )
+ {
+// fwprintf( stderr, L"%ls had errors\n", in_orig );
+ free( res);
+ res=0;
+ }
+ else
+ {
+// fwprintf( stderr, L"%ls translated ok\n", in_orig );
+ *out = L'\0';
+ }
+
+ return res;
+}
+
+
+void input_parse_inputrc_line( wchar_t *cmd )
+{
+ wchar_t *p=cmd;
+
+ /* Make all whitespace into space characters */
+ while( *p )
+ {
+ if( *p == L'\t' || *p == L'\r' )
+ *p=L' ';
+ p++;
+ }
+
+ /* Remove spaces at beginning/end */
+ while( *cmd == L' ' )
+ cmd++;
+
+ p = cmd + wcslen(cmd)-1;
+ while( (p >= cmd) && (*p == L' ') )
+ {
+ *p=L'\0';
+ p--;
+ }
+
+ /* Skip comments */
+ if( *cmd == L'#' )
+ return;
+
+ /* Skip empty lines */
+ if( *cmd == L'\0' )
+ return;
+
+ if( wcscmp( L"$endif", cmd) == 0 )
+ {
+ if( inputrc_skip_block_count )
+ {
+ inputrc_skip_block_count--;
+/*
+ if( !inputrc_skip_block_count )
+ fwprintf( stderr, L"Stop skipping\n" );
+ else
+ fwprintf( stderr, L"Decrease skipping\n" );
+*/
+ }
+ else
+ {
+ if( inputrc_block_count )
+ {
+ inputrc_block_count--;
+// fwprintf( stderr, L"End of active block\n" );
+ }
+ else
+ {
+ inputrc_error = 1;
+ debug( 1,
+ L"Mismatched $endif in inputrc file" );
+ }
+ }
+ return;
+ }
+
+ if( wcscmp( L"$else", cmd) == 0 )
+ {
+ if( inputrc_skip_block_count )
+ {
+ if( inputrc_skip_block_count == 1 )
+ {
+ inputrc_skip_block_count--;
+ inputrc_block_count++;
+ }
+
+ }
+ else
+ {
+ inputrc_skip_block_count++;
+ inputrc_block_count--;
+ }
+
+ return;
+ }
+
+ if( inputrc_skip_block_count )
+ {
+ if( wcsncmp( L"$if ", cmd, wcslen( L"$if " )) == 0 )
+ inputrc_skip_block_count++;
+// fwprintf( stderr, L"Skip %ls\n", cmd );
+
+ return;
+ }
+
+ if( *cmd == L'\"' )
+ {
+
+ wchar_t *key;
+ wchar_t *val;
+ wchar_t *sequence;
+ wchar_t prev=0;
+
+
+ cmd++;
+ key=cmd;
+
+ for( prev=0; ;prev=*cmd,cmd++ )
+ {
+ if( !*cmd )
+ {
+ debug( 1,
+ L"Mismatched quote" );
+ inputrc_error = 1;
+ return;
+ }
+
+ if(( *cmd == L'\"' ) && prev != L'\\' )
+ break;
+
+ }
+ *cmd=0;
+ cmd++;
+ if( *cmd != L':' )
+ {
+ debug( 1,
+ L"Expected a \':\'" );
+ inputrc_error = 1;
+ return;
+ }
+ cmd++;
+ while( *cmd == L' ' )
+ cmd++;
+
+ val = cmd;
+
+ sequence = input_expand_sequence( key );
+ add_mapping( L"global", sequence, key, val );
+
+// fwprintf( stderr, L"Map %ls to %ls\n", key, val );
+
+ free( sequence );
+
+ //fwprintf( stderr, L"Remainder \'%ls\', endchar %d\n", cmd, *cmd );
+
+ //fwprintf( stderr, L"%ls -> %ls\n", key, val );
+
+ return;
+ }
+ else if( wcsncmp( L"$include ", cmd, wcslen(L"$include ") ) == 0 )
+ {
+ wchar_t *tmp;
+
+ cmd += wcslen( L"$include ");
+ while( *cmd == L' ' )
+ cmd++;
+ tmp=wcsdup(cmd);
+ tmp = expand_tilde(tmp);
+ if( tmp )
+ input_read_inputrc( tmp );
+ free(tmp);
+ return;
+ }
+ else if( wcsncmp( L"set", cmd, wcslen( L"set" ) ) == 0 )
+ {
+ wchar_t *set, *key, *value, *end;
+ wchar_t *state;
+
+ set = wcstok( cmd, L" \t", &state );
+ key = wcstok( 0, L" \t", &state );
+ value = wcstok( 0, L" \t", &state );
+ end = wcstok( 0, L" \t", &state );
+
+ if( wcscmp( set, L"set" ) != 0 )
+ {
+ debug( 1, L"I don\'t know what %ls means", set );
+ }
+ else if( end )
+ {
+ debug( 1, L"Expected end of line, got '%ls'", end );
+
+ }
+ else if( (!key) || (!value) )
+ {
+ debug( 1, L"Syntax: set KEY VALUE" );
+ }
+ else
+ {
+ if( wcscmp( key, L"editing-mode" ) == 0 )
+ {
+// current_mode_mappings = get_mapping( value );
+ }
+ }
+
+ return;
+ }
+ else if( wcsncmp( L"$if ", cmd, wcslen( L"$if " )) == 0 )
+ {
+ wchar_t *term_line = wcsdupcat( L"term=", env_get( L"TERM" ) );
+ wchar_t *term_line2 = wcsdup( term_line );
+ wchar_t *mode_line = L"mode=emacs";
+ wchar_t *app_line = L"fish";
+
+ wchar_t *term_line2_end = wcschr( term_line2, L'-' );
+ if( term_line2_end )
+ *term_line2_end=0;
+
+
+ cmd += wcslen( L"$if ");
+ while( *cmd == L' ' )
+ cmd++;
+
+ if( (wcscmp( cmd, app_line )==0) ||
+ (wcscmp( cmd, term_line )==0) ||
+ (wcscmp( cmd, mode_line )==0) )
+ {
+// fwprintf( stderr, L"Conditional %ls is true\n", cmd );
+ inputrc_block_count++;
+ }
+ else
+ {
+// fwprintf( stderr, L"Conditional %ls is false\n", cmd );
+ inputrc_skip_block_count++;
+ }
+ free( term_line );
+ free( term_line2 );
+
+ return;
+ }
+ debug( 1, L"I don\'t know what %ls means", cmd );
+}
+
+/**
+ Read the specified inputrc file
+*/
+static void input_read_inputrc( wchar_t *fn )
+{
+ FILE *rc;
+ wchar_t *buff=0;
+ int buff_len=0;
+ int error=0;
+// fwprintf( stderr, L"read %ls\n", fn );
+
+ block();
+ rc = wfopen( fn, "r" );
+
+ if( rc )
+ {
+ while( !feof( rc ) && (!error))
+ {
+ switch( fgetws2( &buff, &buff_len, rc ) )
+ {
+ case -1:
+ {
+ debug( 1,
+ L"Error while reading input information from file: %s",
+ fn );
+
+ wperror( L"fgetws2 (read_ni)" );
+ error=1;
+ break;
+ }
+
+ default:
+ {
+ input_parse_inputrc_line( buff );
+
+ if( inputrc_error )
+ {
+ fwprintf( stderr, L"%ls\n", buff );
+ error=1;
+ }
+ }
+ }
+ }
+ free( buff );
+ fclose( rc );
+ }
+ unblock();
+
+ inputrc_skip_block_count=0;
+ inputrc_block_count=0;
+}
+
+/**
+ Add a char * based character string mapping.
+*/
+static void add_terminfo_mapping( const wchar_t *mode,
+ const char *seq,
+ const wchar_t *desc,
+ const wchar_t *func )
+{
+ if( seq )
+ {
+ wchar_t *tmp;
+ tmp=str2wcs(seq);
+ if( tmp )
+ {
+ add_mapping( mode, tmp, desc, func );
+ free(tmp);
+ }
+ }
+
+}
+
+static void add_escaped_mapping( const wchar_t *mode,
+ const wchar_t *seq,
+ const wchar_t *desc,
+ const wchar_t *func )
+{
+ wchar_t *esc = input_expand_sequence( seq );
+ if( esc )
+ {
+ add_mapping( mode, esc, desc, func );
+ free(esc);
+ }
+}
+
+
+static void add_common_bindings()
+{
+ static const wchar_t *name[] =
+ {
+ L"emacs",
+ L"vi",
+ L"vi-command"
+ }
+ ;
+ int i;
+
+ /*
+ Universal bindings
+ */
+ for( i=0; i<3; i++ )
+ {
+ add_mapping( name[i], L"\e[A", L"Up", L"history-search-backward" );
+ add_mapping( name[i], L"\e[B", L"Down", L"history-search-forward" );
+ add_mapping( name[i], L"\e[C", L"Right", L"forward-char" );
+ add_mapping( name[i], L"\e[D", L"Left", L"backward-char" );
+
+ add_terminfo_mapping( name[i], (key_left), L"Left", L"backward-char" );
+
+ add_terminfo_mapping( name[i], (key_right), L"Right", L"forward-char" );
+
+ add_terminfo_mapping( name[i], (key_up), L"Up", L"history-search-backward" );
+
+ add_terminfo_mapping( name[i], (key_down), L"Down", L"history-search-forward" );
+ add_terminfo_mapping( name[i], (key_dc), L"Delete", L"delete-char" );
+
+ add_terminfo_mapping( name[i], (key_backspace), L"Backspace", L"backward-delete-char" );
+
+ add_mapping( name[i], L"\x7f", L"Backspace", L"backward-delete-char" );
+
+ add_terminfo_mapping( name[i], (key_home), L"Home", L"beginning-of-line" );
+
+ add_terminfo_mapping( name[i], (key_end), L"End", L"end-of-line" );
+
+ add_mapping( name[i], L"\e\eOC", L"Alt-Right", L"forward-word" );
+ add_mapping( name[i], L"\e\eOD", L"Alt-Left", L"backward-word" );
+
+ add_mapping( name[i], L"\eO3C", L"Alt-Right", L"forward-word" );
+ add_mapping( name[i], L"\eO3D", L"Alt-Left", L"backward-word" );
+
+ add_mapping( name[i], L"\e[3C", L"Alt-Right", L"forward-word" );
+ add_mapping( name[i], L"\e[3D", L"Alt-Left", L"backward-word" );
+
+ add_mapping( name[i], L"\e\eOA", L"Alt-Up", L"history-token-search-backward" );
+ add_mapping( name[i], L"\e\eOB", L"Alt-Down", L"history-token-search-forward" );
+
+ add_mapping( name[i], L"\eO3A", L"Alt-Up", L"history-token-search-backward" );
+ add_mapping( name[i], L"\eO3B", L"Alt-Down", L"history-token-search-forward" );
+
+ add_mapping( name[i], L"\e[3A", L"Alt-Up", L"history-token-search-backward" );
+ add_mapping( name[i], L"\e[3B", L"Alt-Down", L"history-token-search-forward" );
+
+ }
+
+ /*
+ Bindings used in emacs and vi mode, but not in vi-command mode
+ */
+ for( i=0; i<2; i++ )
+ {
+ add_mapping( name[i], L"\t", L"Tab", L"complete" );
+ add_escaped_mapping( name[i], (L"\\C-k"), L"Control-k", L"kill-line" );
+ add_escaped_mapping( name[i], (L"\\C-y"), L"Control-y", L"yank" );
+ add_mapping( name[i], L"", L"Any key", L"self-insert" );
+ }
+}
+
+static void add_emacs_bindings()
+{
+ add_escaped_mapping( L"emacs", (L"\\C-a"), L"Control-a", L"beginning-of-line" );
+ add_escaped_mapping( L"emacs", (L"\\C-e"), L"Control-e", L"end-of-line" );
+ add_escaped_mapping( L"emacs", (L"\\M-y"), L"Alt-y", L"yank-pop" );
+ add_escaped_mapping( L"emacs", (L"\\C-h"), L"Control-h", L"backward-delete-char" );
+ add_escaped_mapping( L"emacs", (L"\\C-e"), L"Control-e", L"end-of-line" );
+ add_escaped_mapping( L"emacs", (L"\\C-w"), L"Control-w", L"backward-kill-word" );
+ add_terminfo_mapping( L"emacs", (key_ppage), L"Page Up", L"beginning-of-history" );
+ add_terminfo_mapping( L"emacs", (key_npage), L"Page Down", L"end-of-history" );
+}
+
+static void add_vi_bindings()
+{
+ add_mapping( L"vi", L"\e", L"Escape", L"bind -M vi-command" );
+
+ add_mapping( L"vi-command", L"i", L"i", L"bind -M vi" );
+ add_mapping( L"vi-command", L"I", L"I", L"bind -M vi" );
+ add_mapping( L"vi-command", L"k", L"k", L"history-search-backward" );
+ add_mapping( L"vi-command", L"j", L"j", L"history-search-forward" );
+ add_mapping( L"vi-command", L" ", L"Space", L"forward-char" );
+ add_mapping( L"vi-command", L"l", L"l", L"forward-char" );
+ add_mapping( L"vi-command", L"h", L"h", L"backward-char" );
+ add_mapping( L"vi-command", L"$", L"$", L"end-of-line" );
+ add_mapping( L"vi-command", L"^", L"^", L"beginning-of-line" );
+ add_mapping( L"vi-command", L"0", L"0", L"beginning-of-line" );
+ add_mapping( L"vi-command", L"b", L"b", L"backward-word" );
+ add_mapping( L"vi-command", L"B", L"B", L"backward-word" );
+ add_mapping( L"vi-command", L"w", L"w", L"forward-word" );
+ add_mapping( L"vi-command", L"W", L"W", L"forward-word" );
+ add_mapping( L"vi-command", L"x", L"x", L"delete-char" );
+
+/*
+ movement ("h", "l"), word movement
+ ("b", "B", "w", "W", "e", "E"), moving to beginning and end of line
+ ("0", "^", "$"), and inserting and appending ("i", "I", "a", "A"),
+ changing and deleting ("c", "C", "d", "D"), character replacement and
+ deletion ("r", "x"), and finally yanking and pasting ("y", "p")
+*/
+
+}
+
+static int interrupt_handler()
+{
+ if( job_do_notification() )
+ repaint();
+ if( reader_interupted() )
+ {
+ return 3;
+ }
+ return 0;
+}
+
+int input_init()
+{
+ wchar_t *fn;
+
+ input_common_init( &interrupt_handler );
+
+ if( setupterm( 0, STDOUT_FILENO, 0) == ERR )
+ {
+ debug( 0, L"Could not set up terminal" );
+ exit(1);
+ }
+ hash_init( &all_mappings, &hash_wcs_func, &hash_wcs_cmp );
+
+ /*
+ Add the default key bindings.
+
+ Maybe some/most of these should be moved to the keybindings file?
+ */
+
+ /*
+ Many terminals (xterm, screen, etc.) have two different valid escape
+ sequences for arrow keys. One which is defined in terminfo/termcap
+ and one which is actually emitted by the arrow keys. The logic
+ escapes me, but I put in these hardcodes here for that reason.
+ */
+
+ add_common_bindings();
+ add_emacs_bindings();
+ add_vi_bindings();
+
+ current_mode_mappings = (array_list_t *)hash_get( &all_mappings,
+ L"emacs" );
+
+
+ fn = env_get( L"INPUTRC" );
+
+ if( !fn )
+ fn = L"~/.inputrc";
+
+ fn = expand_tilde( wcsdup( fn ));
+
+ if( fn )
+ {
+ input_read_inputrc( fn );
+ free(fn);
+ }
+
+ current_application_mappings = (array_list_t *)hash_get( &all_mappings,
+ L"fish" );
+ global_mappings = (array_list_t *)hash_get( &all_mappings,
+ L"global" );
+
+ return 1;
+
+}
+
+static void destroy_mapping( const void *key, const void *val )
+{
+ int i;
+ array_list_t *mappings = (array_list_t *)val;
+
+ for( i=0; i<al_get_count( mappings ); i++ )
+ {
+ mapping *m = (mapping *)al_get( mappings, i );
+ free( m->seq );
+ free( m->seq_desc );
+
+ free( m->command );
+ free(m );
+ }
+
+ al_destroy( mappings );
+ free((void *)key);
+ free((void *)val);
+}
+
+
+void input_destroy()
+{
+ input_common_destroy();
+
+ hash_foreach( &all_mappings, &destroy_mapping );
+ hash_destroy( &all_mappings );
+
+ del_curterm( cur_term );
+}
+
+
+static wint_t input_exec_binding( mapping *m, const wchar_t *seq )
+{
+// fwprintf( stderr, L"Binding %ls\n", m->command );
+ wchar_t code = input_get_code( m->command );
+ if( code != -1 )
+ {
+ switch( code )
+ {
+ case R_DUMP_FUNCTIONS:
+ {
+ dump_functions();
+ return input_readch();
+ }
+ case R_SELF_INSERT:
+ {
+ return seq[0];
+ }
+ default:
+ return code;
+ }
+ }
+ else
+ {
+
+ /*
+ This key sequence is bound to a command, which
+ is sent to the parser for evaluation.
+ */
+
+ /*
+ First clear the commandline. Do not issue a linebreak, since
+ many shortcut commands do not procuce output.
+ */
+ write( 1, "\r", 1 );
+ tputs(clr_eol,1,&writeb);
+
+ reader_run_command( m->command );
+
+ /*
+ We still need to return something to the caller, R_NULL
+ tells the reader that nothing happened, but it might be a
+ godd idea to redraw and reexecute the prompt.
+ */
+ return R_NULL;
+ }
+
+}
+
+
+
+/**
+ Try reading the specified function mapping
+*/
+
+static wint_t input_try_mapping( mapping *m)
+{
+ int j, k;
+ wint_t c=0;
+
+ if( m->seq != 0 )
+ {
+ for( j=0; m->seq[j] != L'\0' &&
+ m->seq[j] == (c=input_common_readch( j>0 )); j++ )
+ ;
+ if( m->seq[j] == L'\0' )
+ {
+
+ return input_exec_binding( m, m->seq );
+ }
+ else
+ {
+ input_unreadch(c);
+ for(k=j-1; k>=0; k--)
+ input_unreadch(m->seq[k]);
+ }
+ }
+ return 0;
+
+}
+
+void input_unreadch( wint_t ch )
+{
+ input_common_unreadch( ch );
+}
+
+
+wint_t input_readch()
+{
+
+ int i;
+
+ /*
+ Clear the interrupted flag
+ */
+ reader_interupted();
+
+ /*
+ Search for sequence in various mapping tables
+ */
+
+ while( 1 )
+ {
+
+ if( current_application_mappings )
+ {
+ for( i=0; i<al_get_count( current_application_mappings); i++ )
+ {
+ wint_t res = input_try_mapping( (mapping *)al_get( current_application_mappings, i ));
+ if( res )
+ return res;
+ }
+ }
+
+ if( global_mappings )
+ {
+ for( i=0; i<al_get_count( global_mappings); i++ )
+ {
+ wint_t res = input_try_mapping( (mapping *)al_get( global_mappings, i ));
+ if( res )
+ return res;
+ }
+ }
+
+ if( current_mode_mappings )
+ {
+ for( i=0; i<al_get_count( current_mode_mappings); i++ )
+ {
+ wint_t res = input_try_mapping( (mapping *)al_get( current_mode_mappings, i ));
+ if( res )
+ return res;
+ }
+ }
+
+ /*
+ No matching exact mapping, try to find the generic mapping.
+ */
+
+ for( i=0; i<al_get_count( current_mode_mappings); i++ )
+ {
+ mapping *m = (mapping *)al_get( current_mode_mappings, i );
+ if( wcslen( m->seq) == 0 )
+ {
+ wchar_t arr[2]=
+ {
+ 0,
+ 0
+ }
+ ;
+ arr[0] = input_common_readch(0);
+
+ return input_exec_binding( m, arr );
+ }
+ }
+
+ input_common_readch( 0 );
+
+ }
+}
diff --git a/input.h b/input.h
new file mode 100644
index 00000000..35eec3a9
--- /dev/null
+++ b/input.h
@@ -0,0 +1,109 @@
+/** \file input.h
+
+Functions for reading a character of input from stdin, using the
+inputrc information for key bindings.
+
+*/
+
+/**
+ Key codes for inputrc-style keyboard functions that are passed on
+ to the caller of input_read()
+*/
+enum
+{
+ R_BEGINNING_OF_LINE = R_NULL+1,
+ R_END_OF_LINE,
+ R_FORWARD_CHAR,
+ R_BACKWARD_CHAR,
+ R_FORWARD_WORD,
+ R_BACKWARD_WORD,
+ R_HISTORY_SEARCH_BACKWARD,
+ R_HISTORY_SEARCH_FORWARD,
+ R_DELETE_CHAR,
+ R_BACKWARD_DELETE_CHAR,
+ R_KILL_LINE,
+ R_YANK,
+ R_YANK_POP,
+ R_COMPLETE,
+ R_BEGINNING_OF_HISTORY,
+ R_END_OF_HISTORY,
+ R_DELETE_LINE,
+ R_BACKWARD_KILL_LINE,
+ R_KILL_WHOLE_LINE,
+ R_KILL_WORD,
+ R_BACKWARD_KILL_WORD,
+ R_DUMP_FUNCTIONS,
+ R_CLEAR_SCREEN,
+ R_EXIT,
+ R_HISTORY_TOKEN_SEARCH_BACKWARD,
+ R_HISTORY_TOKEN_SEARCH_FORWARD,
+ R_SELF_INSERT,
+}
+;
+
+/**
+ Initialize the terminal by calling setupterm, and set up arrays
+ used by readch to detect escape sequences for special keys.
+
+ Before calling input_init, terminfo is not initialized and MUST not be used
+*/
+int input_init();
+
+/**
+ free up memory used by terminal functions.
+*/
+void input_destroy();
+
+/**
+ Read a character from fd 0. Try to convert some escape sequences
+ into character constants, but do not permanently block the escape
+ character.
+
+ This is performed in the same way vim does it, i.e. if an escape
+ character is read, wait for more input for a short time (a few
+ milliseconds). If more input is avaialable, it is assumed to be an
+ escape sequence for a special character (such as an arrow key), and
+ readch attempts to parse it. If no more input follows after the
+ escape key, it is assumed to be an actual escape key press, and is
+ returned as such.
+*/
+wint_t input_readch();
+
+/**
+ Push a character or a readline function onto the stack of unread
+ characters that input_readch will return before actually reading from fd
+ 0.
+*/
+void input_unreadch( wint_t ch );
+
+
+/**
+ Add a key mapping from the specified sequence
+
+ \param mode the name of the mapping mode to add this mapping to
+ \param s the sequence
+ \param d a description of the sequence
+ \param c am input function that will be run whenever the key sequence occurs
+*/
+void add_mapping( const wchar_t *mode, const wchar_t *s, const wchar_t * d, const wchar_t *cmd );
+
+/**
+ Sets the mode keybindings.
+*/
+void input_set_mode( wchar_t *name );
+
+/**
+ Sets the application keybindings
+*/
+void input_set_application( wchar_t *name );
+
+/**
+ Parse a single line of inputrc information.
+*/
+void input_parse_inputrc_line( wchar_t *cmd );
+
+/**
+ Returns the function for the given function name.
+*/
+wchar_t input_get_code( wchar_t *name );
+
diff --git a/input_common.c b/input_common.c
new file mode 100644
index 00000000..0f92a314
--- /dev/null
+++ b/input_common.c
@@ -0,0 +1,220 @@
+/** \file input_common.h
+
+Implementation file for the low level input library
+
+*/
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include "util.h"
+#include "common.h"
+#include "wutil.h"
+#include "input_common.h"
+#include "env_universal.h"
+
+/**
+ Time in milliseconds to wait for another byte to be available for
+ reading after \e is read before assuming that escape key was
+ pressed, and not an escape sequence.
+*/
+#define WAIT_ON_ESCAPE 10
+
+/**
+ Characters that have been read and returned by the sequence matching code
+*/
+static wint_t lookahead_arr[32];
+
+/**
+ Number of entries in lookahead_arr
+*/
+static int lookahead_count = 0;
+
+/**
+ Callback function for handling interrupts on reading
+*/
+static int (*interrupt_handler)();
+
+void input_common_init( int (*ih)() )
+{
+ interrupt_handler = ih;
+}
+
+void input_common_destroy()
+{
+
+}
+
+/**
+ Internal function used by input_common_readch to read one byte from fd 1. This function should only be called by
+ input_common_readch().
+*/
+static wint_t readb()
+{
+ char arr[1];
+ int do_loop = 0;
+
+ do
+ {
+ fd_set fd;
+ int fd_max=1;
+ int res;
+
+ FD_ZERO( &fd );
+ FD_SET( 0, &fd );
+ if( env_universal_server.fd > 0 )
+ {
+ FD_SET( env_universal_server.fd, &fd );
+ fd_max = env_universal_server.fd+1;
+ }
+
+ do_loop = 0;
+
+ res = select( fd_max, &fd, 0, 0, 0 );
+ if( res==-1 )
+ {
+ switch( errno )
+ {
+ case EINTR:
+ case EAGAIN:
+ {
+// wperror( L"select" );
+ if( interrupt_handler )
+ {
+ int res = interrupt_handler();
+
+/* debug( 0,
+ L"interrupt, %d is %ls",
+ res,
+ (res==R_NULL?L"good": L"Bad") );
+*/
+ if( res )
+ return res;
+ }
+
+ do_loop = 1;
+ break;
+ }
+ default:
+ {
+ debug( 0, L"Error while reading input from keyboard, shutting down" );
+ wperror(L"read");
+ exit(1);
+ }
+ }
+ }
+ else
+ {
+ if( env_universal_server.fd > 0 )
+ {
+ if( FD_ISSET( env_universal_server.fd, &fd ) )
+ {
+ debug( 3, L"Wake up on universal variable event" );
+ env_universal_read_all();
+ return R_NULL;
+ }
+ }
+ if( FD_ISSET( 0, &fd ) )
+ {
+ if( read_blocked( 0, arr, 1 ) == -1 )
+ {
+ debug( 0, L"Error while reading input from keyboard, shutting down" );
+ wperror(L"read");
+ exit(1);
+ }
+ do_loop = 0;
+ }
+ }
+ }
+ while( do_loop );
+
+ return arr[0];
+}
+
+wchar_t input_common_readch( int timed )
+{
+ if( lookahead_count == 0 )
+ {
+ if( timed )
+ {
+ int count;
+ fd_set fds;
+ struct timeval tm=
+ {
+ 0,
+ 1000 * WAIT_ON_ESCAPE
+ }
+ ;
+
+ FD_ZERO( &fds );
+ FD_SET( 0, &fds );
+ count = select(1, &fds, 0, 0, &tm);
+
+ switch( count )
+ {
+ case 0:
+ return WEOF;
+
+ case -1:
+ return WEOF;
+ break;
+ default:
+ break;
+
+ }
+ }
+
+ wchar_t res;
+ static mbstate_t state;
+
+ while(1)
+ {
+ wint_t b = readb();
+ int sz;
+
+ if( b == R_NULL )
+ return R_NULL;
+
+ sz = mbrtowc( &res, &b, 1, &state );
+
+ switch( sz )
+ {
+ case -1:
+ memset (&state, '\0', sizeof (state));
+ debug( 2, L"Illegal input" );
+ return R_NULL;
+ case -2:
+ break;
+ case 0:
+ return 0;
+ default:
+
+ return res;
+ }
+ }
+ }
+ else
+ {
+ if( !timed )
+ {
+ while( (lookahead_count >= 0) && (lookahead_arr[lookahead_count-1] == WEOF) )
+ lookahead_count--;
+ if( lookahead_count == 0 )
+ return input_common_readch(0);
+ }
+
+ return lookahead_arr[--lookahead_count];
+ }
+}
+
+
+void input_common_unreadch( wint_t ch )
+{
+ lookahead_arr[lookahead_count++] = ch;
+}
+
diff --git a/input_common.h b/input_common.h
new file mode 100644
index 00000000..ad4f862f
--- /dev/null
+++ b/input_common.h
@@ -0,0 +1,40 @@
+/** \file input_common.h
+
+Header file for the low level input library
+
+*/
+#ifndef INPUT_COMMON_HH
+#define INPUT_COMMON_HH
+
+/**
+ Hopefully, the biggest value that a wchar_t can have. UCS4 is a
+ 31-bit character set, we use the upper half for special key
+ sequences. On systems where wchar_t is not a 31 (or less) bit character set
+ in a 32 (or more) bit type this will fail horribly.
+*/
+#define WCHAR_END 0x80000000
+
+enum
+{
+ R_NULL = WCHAR_END + 1
+}
+ ;
+
+void input_common_init( int (*ih)() );
+
+void input_common_destroy();
+
+/**
+ Function used by input_readch to read bytes from stdin until enough
+ bytes have been read to convert them to a wchar_t. Conversion is
+ done using mbrtowc. If a character has previously been read and
+ then 'unread' using \c input_common_unreadch, that character is
+ returned. If timed is true, readch2 will wait at most
+ WAIT_ON_ESCAPE milliseconds for a character to be available for
+ reading before returning with the value WEOF.
+*/
+wchar_t input_common_readch( int timed );
+
+void input_common_unreadch( wint_t ch );
+
+#endif
diff --git a/install-sh b/install-sh
new file mode 100755
index 00000000..e9de2384
--- /dev/null
+++ b/install-sh
@@ -0,0 +1,251 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+# This comes from X11R5 (mit/util/scripts/install.sh).
+#
+# Copyright 1991 by the Massachusetts Institute of Technology
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of M.I.T. not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission. M.I.T. makes no representations about the
+# suitability of this software for any purpose. It is provided "as is"
+# without express or implied warranty.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch. It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+ case $1 in
+ -c) instcmd="$cpprog"
+ shift
+ continue;;
+
+ -d) dir_arg=true
+ shift
+ continue;;
+
+ -m) chmodcmd="$chmodprog $2"
+ shift
+ shift
+ continue;;
+
+ -o) chowncmd="$chownprog $2"
+ shift
+ shift
+ continue;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift
+ shift
+ continue;;
+
+ -s) stripcmd="$stripprog"
+ shift
+ continue;;
+
+ -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+ shift
+ continue;;
+
+ -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+ shift
+ continue;;
+
+ *) if [ x"$src" = x ]
+ then
+ src=$1
+ else
+ # this colon is to work around a 386BSD /bin/sh bug
+ :
+ dst=$1
+ fi
+ shift
+ continue;;
+ esac
+done
+
+if [ x"$src" = x ]
+then
+ echo "install: no input file specified"
+ exit 1
+else
+ true
+fi
+
+if [ x"$dir_arg" != x ]; then
+ dst=$src
+ src=""
+
+ if [ -d $dst ]; then
+ instcmd=:
+ chmodcmd=""
+ else
+ instcmd=mkdir
+ fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad
+# if $src (and thus $dsttmp) contains '*'.
+
+ if [ -f $src -o -d $src ]
+ then
+ true
+ else
+ echo "install: $src does not exist"
+ exit 1
+ fi
+
+ if [ x"$dst" = x ]
+ then
+ echo "install: no destination specified"
+ exit 1
+ else
+ true
+ fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+ if [ -d $dst ]
+ then
+ dst="$dst"/`basename $src`
+ else
+ true
+ fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+# this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+ pathcomp="${pathcomp}${1}"
+ shift
+
+ if [ ! -d "${pathcomp}" ] ;
+ then
+ $mkdirprog "${pathcomp}"
+ else
+ true
+ fi
+
+ pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+ $doit $instcmd $dst &&
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+ if [ x"$transformarg" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ dstfile=`basename $dst $transformbasename |
+ sed $transformarg`$transformbasename
+ fi
+
+# don't allow the sed command to completely eliminate the filename
+
+ if [ x"$dstfile" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ true
+ fi
+
+# Make a temp file name in the proper directory.
+
+ dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+ $doit $instcmd $src $dsttmp &&
+
+ trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing. If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+ $doit $rmcmd -f $dstdir/$dstfile &&
+ $doit $mvcmd $dsttmp $dstdir/$dstfile
+
+fi &&
+
+
+exit 0
diff --git a/intern.c b/intern.c
new file mode 100644
index 00000000..2c7d2eff
--- /dev/null
+++ b/intern.c
@@ -0,0 +1,119 @@
+/** \file intern.c
+
+ Library for pooling common strings
+
+*/
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+
+#include "util.h"
+#include "common.h"
+#include "intern.h"
+
+hash_table_t *intern_table=0;
+hash_table_t *intern_static_table=0;
+
+static void intern_load_common_static()
+{
+ intern_static( L"" );
+}
+
+const wchar_t *intern( const wchar_t *in )
+{
+ const wchar_t *res=0;
+
+ if( !in )
+ return 0;
+
+ intern_load_common_static();
+
+
+ if( !intern_table )
+ {
+ intern_table = malloc( sizeof( hash_table_t ) );
+ if( !intern_table )
+ {
+ die_mem();
+ }
+ hash_init( intern_table, &hash_wcs_func, &hash_wcs_cmp );
+ }
+
+ if( intern_static_table )
+ {
+ res = hash_get( intern_static_table, in );
+ }
+
+ if( !res )
+ {
+ res = hash_get( intern_table, in );
+
+ if( !res )
+ {
+ res = wcsdup( in );
+ if( !res )
+ {
+ die_mem();
+ }
+
+ hash_put( intern_table, res, res );
+ }
+ }
+
+ return res;
+}
+
+const wchar_t *intern_static( const wchar_t *in )
+{
+ const wchar_t *res=0;
+
+ if( !in )
+ return 0;
+
+ if( !intern_static_table )
+ {
+ intern_static_table = malloc( sizeof( hash_table_t ) );
+ if( !intern_static_table )
+ {
+ die_mem();
+ }
+ hash_init( intern_static_table, &hash_wcs_func, &hash_wcs_cmp );
+ }
+
+ res = hash_get( intern_static_table, in );
+
+ if( !res )
+ {
+ res = in;
+ hash_put( intern_static_table, res, res );
+ }
+
+ return res;
+}
+
+static void clear_value( const void *key, const void *data )
+{
+ debug( 3, L"interned string: '%ls'", data );
+ free( (void *)data );
+}
+
+void intern_free_all()
+{
+ if( intern_table )
+ {
+ hash_foreach( intern_table, &clear_value );
+ hash_destroy( intern_table );
+ free( intern_table );
+ intern_table=0;
+ }
+
+ if( intern_static_table )
+ {
+ hash_destroy( intern_static_table );
+ free( intern_static_table );
+ intern_static_table=0;
+ }
+
+}
diff --git a/intern.h b/intern.h
new file mode 100644
index 00000000..7e0fdeda
--- /dev/null
+++ b/intern.h
@@ -0,0 +1,24 @@
+/** \file intern.h
+
+ Library for pooling common strings
+
+*/
+
+/**
+ Return an identical copy of the specified string from a pool of unique strings. If the string was not in the pool, add a copy.
+
+ \param The string to return an interned copy of
+*/
+const wchar_t *intern( const wchar_t *in );
+
+/**
+ Insert the specified string literal into the pool of unique
+ strings. The string will not first be copied, and it will not be
+ free'd on exit.
+*/
+const wchar_t *intern_static( const wchar_t *in );
+
+/**
+ Free all interned strings
+*/
+void intern_free_all();
diff --git a/key_reader.c b/key_reader.c
new file mode 100644
index 00000000..bfaaf809
--- /dev/null
+++ b/key_reader.c
@@ -0,0 +1,92 @@
+/*
+ A small utility to print the resulting key codes from pressing a
+ key. Servers the same function as hitting ^V in bash, but I prefer
+ the way key_reader works.
+
+ Type ^C to exit the program.
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <termcap.h>
+
+static int readch()
+{
+ char arr[1];
+ if( read( 0, arr, 1 ) < 0 )
+ {
+ perror( "read" );
+ return readch();
+ }
+ else
+ return arr[0];
+}
+
+int writestr( char *str )
+{
+ write( 1, str, strlen(str) );
+ return 0;
+}
+
+int main( int argc, char **argv)
+{
+
+ if( argc == 2 )
+ {
+ static char term_buffer[2048];
+ char *termtype = getenv ("TERM");
+ char *tbuff = malloc( sizeof(char)*9999);
+ char *res;
+
+ tgetent( term_buffer, termtype );
+ res = tgetstr( argv[1], &tbuff );
+ if( res != 0 )
+ {
+ while( *res != 0 )
+ {
+ printf("%d ", *res );
+
+
+ res++;
+ }
+ printf( "\n" );
+ }
+ else
+ {
+ printf("Undefined sequence\n");
+ }
+ }
+ else
+ {
+ char scratch[1024];
+ unsigned int c;
+
+ struct termios modes, /* so we can change the modes */
+ savemodes; /* so we can reset the modes when we're done */
+
+ tcgetattr(0,&modes); /* get the current terminal modes */
+ savemodes = modes; /* save a copy so we can reset them */
+
+ modes.c_lflag &= ~ICANON; /* turn off canonical mode */
+ modes.c_lflag &= ~ECHO; /* turn off echo mode */
+ modes.c_cc[VMIN]=1;
+ modes.c_cc[VTIME]=0;
+ tcsetattr(0,TCSANOW,&modes); /* set the new modes */
+ while(1)
+ {
+ if( (c=readch()) == EOF )
+ break;
+ if((c > 31) && (c != 127) )
+ sprintf( scratch, "dec: %d hex: %x char: %c\n", c, c, c );
+ else
+ sprintf( scratch, "dec: %d hex: %x\n", c, c );
+ writestr( scratch );
+ }
+ /* reset the terminal to the saved mode */
+ tcsetattr(0,TCSANOW,&savemodes);
+ }
+
+ return 0;
+}
diff --git a/kill.c b/kill.c
new file mode 100644
index 00000000..86a6f34a
--- /dev/null
+++ b/kill.c
@@ -0,0 +1,256 @@
+/** \file kill.c
+ The killring.
+
+ Works like the killring in emacs and readline. The killring is cut
+ and paste with a memory of previous cuts. It supports integration
+ with the X clipboard.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <termios.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+
+#include "config.h"
+#include "util.h"
+#include "wutil.h"
+#include "kill.h"
+#include "proc.h"
+#include "sanity.h"
+#include "common.h"
+#include "env.h"
+#include "expand.h"
+#include "exec.h"
+#include "parser.h"
+
+/**
+ Maximum entries in killring
+*/
+#define KILL_MAX 8192
+
+
+static ll_node_t /** Last kill string */*kill_last=0, /** Current kill string */*kill_current =0;
+/**
+ Contents of the X clipboard, at last time we checked it
+*/
+static wchar_t *cut_buffer=0;
+
+/**
+ Test if the xsel command is installed
+*/
+static int has_xsel()
+{
+ wchar_t *path = get_filename( L"xsel" );
+ if( path)
+ {
+ free(path);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+
+
+/**
+ Add the string to the internal killring
+*/
+static void kill_add_internal( wchar_t *str )
+{
+ if( wcslen( str ) == 0 )
+ return;
+
+ if( kill_last == 0 )
+ {
+ kill_current = kill_last=malloc( sizeof( ll_node_t ) );
+ kill_current->data = wcsdup(str);
+ kill_current->prev = kill_current;
+ }
+ else
+ {
+ kill_current = malloc( sizeof( ll_node_t ) );
+ kill_current->data = kill_last->data;
+ kill_last->data = wcsdup(str);
+ kill_current->prev = kill_last->prev;
+ kill_last->prev = kill_current;
+ kill_current = kill_last;
+ }
+}
+
+
+void kill_add( wchar_t *str )
+{
+ kill_add_internal(str);
+
+ if( !has_xsel() )
+ return;
+
+ /* This is for sending the kill to the X copy-and-paste buffer */
+ wchar_t *disp;
+ if( (disp = env_get( L"DISPLAY" )) )
+ {
+ wchar_t *escaped_str = expand_escape( wcsdup(str), 1 );
+ wchar_t *cmd = wcsdupcat2(L"echo ", escaped_str, L"|xsel -b",0);
+ exec_subshell( cmd, 0 );
+ free( cut_buffer );
+ free( cmd );
+
+ cut_buffer = escaped_str;
+ }
+}
+
+
+wchar_t *kill_yank_rotate()
+{
+ if( kill_current == 0 )
+ return L"";
+ kill_current = kill_current->prev;
+ return (wchar_t *)kill_current->data;
+}
+
+/**
+ Check the X clipboard. If it has been changed, add the new
+ clipboard contents to the fish killring.
+*/
+static void kill_check_x_buffer()
+{
+ wchar_t *disp;
+
+ if( !has_xsel() )
+ return;
+
+
+ if( (disp = env_get( L"DISPLAY" )) )
+ {
+ int i;
+ wchar_t *cmd = L"xsel -t 500 -b";
+ wchar_t *new_cut_buffer=0;
+ array_list_t list;
+ al_init( &list );
+ exec_subshell( cmd, &list );
+
+ for( i=0; i<al_get_count( &list ); i++ )
+ {
+ wchar_t *next_line = expand_escape(wcsdup(al_get( &list, i )), 0);
+ if( i==0 )
+ {
+ new_cut_buffer = next_line;
+ }
+ else
+ {
+ wchar_t *old = new_cut_buffer;
+ new_cut_buffer= wcsdupcat2( new_cut_buffer, L"\\n", next_line, 0 );
+ free( old );
+ free( next_line );
+ }
+ }
+
+ if( new_cut_buffer )
+ {
+ /*
+ The buffer is inserted with backslash escapes,
+ since we don't really like tabs, newlines,
+ etc. anyway.
+ */
+
+ if( cut_buffer != 0 )
+ {
+ if( wcscmp( new_cut_buffer, cut_buffer ) == 0 )
+ {
+ free( new_cut_buffer );
+ new_cut_buffer = 0;
+ }
+ else
+ {
+ free( cut_buffer );
+ cut_buffer = 0;
+ }
+ }
+ if( cut_buffer == 0 )
+ {
+ cut_buffer = new_cut_buffer;
+ kill_add_internal( cut_buffer );
+ }
+ }
+
+ al_foreach( &list, (void (*)(const void *))&free );
+ al_destroy( &list );
+ }
+}
+
+
+wchar_t *kill_yank()
+{
+ kill_check_x_buffer();
+ if( kill_current == 0 )
+ return L"";
+ kill_current=kill_last;
+ return (wchar_t *)kill_current->data;
+}
+
+void kill_sanity_check()
+{
+ int i;
+ if( is_interactive )
+ {
+ /* Test that the kill-ring is consistent */
+ if( kill_current != 0 )
+ {
+ int kill_ok = 0;
+ ll_node_t *tmp = kill_current->prev;
+ for( i=0; i<KILL_MAX; i++ )
+ {
+ if( tmp == 0 )
+ break;
+ if( tmp->data == 0 )
+ break;
+
+ if( tmp == kill_current )
+ {
+ kill_ok = 1;
+ break;
+ }
+ tmp = tmp->prev;
+ }
+ if( !kill_ok )
+ {
+ debug( 0,
+ L"Killring inconsistent" );
+ sanity_lose();
+ }
+ }
+
+ }
+}
+
+void kill_init()
+{
+}
+
+void kill_destroy()
+{
+ if( cut_buffer )
+ free( cut_buffer );
+
+ if( kill_current != 0 )
+ {
+ kill_current = kill_last->prev;
+ kill_last->prev = 0;
+
+ while( kill_current )
+ {
+ ll_node_t *tmp = kill_current;
+ kill_current = kill_current->prev;
+ free( tmp->data );
+ free( tmp );
+ }
+ }
+
+}
+
diff --git a/kill.h b/kill.h
new file mode 100644
index 00000000..f3005bb5
--- /dev/null
+++ b/kill.h
@@ -0,0 +1,31 @@
+/** \file kill.h
+ Prototypes for the killring.
+
+ Works like the killring in emacs and readline. The killring is cut and paste whith a memory of previous cuts.
+*/
+
+/**
+ Add a string to the top of the killring
+*/
+void kill_add( wchar_t *str );
+/**
+ Rotate the killring
+*/
+wchar_t *kill_yank_rotate();
+/**
+ Paste from the killring
+*/
+wchar_t *kill_yank();
+/**
+ Sanity check
+*/
+void kill_sanity_check();
+/**
+ Initialize the killring
+*/
+void kill_init();
+/**
+ Destroy the killring
+*/
+void kill_destroy();
+
diff --git a/main.c b/main.c
new file mode 100644
index 00000000..fe2a1b69
--- /dev/null
+++ b/main.c
@@ -0,0 +1,306 @@
+/*
+Copyright (C) 2005 Axel Liljencrantz
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+
+/** \file main.c
+ The main loop of <tt>fish</tt>.
+*/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#include <locale.h>
+#include <signal.h>
+
+#include "util.h"
+#include "common.h"
+#include "reader.h"
+#include "builtin.h"
+#include "function.h"
+#include "complete.h"
+#include "wutil.h"
+#include "env.h"
+#include "sanity.h"
+#include "proc.h"
+#include "parser.h"
+#include "expand.h"
+#include "intern.h"
+
+/**
+ Parse init files
+*/
+static int read_init()
+{
+ char cwd[4096];
+ wchar_t *wcwd;
+
+ if( !getcwd( cwd, 4096 ) )
+ {
+ wperror( L"getcwd" );
+ return 0;
+ }
+
+ env_set( L"__fish_help_dir", DOCDIR, 0);
+
+ eval( L"builtin cd /etc 2>/dev/null; . fish 2>/dev/null", 0, TOP );
+ eval( L"builtin cd 2>/dev/null;. .fish 2>/dev/null", 0, TOP );
+
+ if( chdir( cwd ) == -1 )
+ {
+// fwprintf( stderr, L"Invalid directory: %s\n", cwd );
+// wperror( L"chdir" );
+// return 0;
+ }
+ wcwd = str2wcs( cwd );
+ if( wcwd )
+ {
+ env_set( L"PWD", wcwd, ENV_EXPORT );
+ free( wcwd );
+ }
+
+ return 1;
+}
+
+/**
+ Calls a bunch of init functions, parses the init files and then
+ parses commands from stdin or files, depending on arguments
+*/
+
+int main( int argc, char **argv )
+{
+ int res=1;
+ int force_interactive=0;
+ int my_optind;
+
+ char *cmd=0;
+
+ fish_setlocale( LC_ALL, L"" );
+ is_interactive_session=1;
+ program_name=L"fish";
+
+ while( 1 )
+ {
+#ifdef __GLIBC__
+ static struct option
+ long_options[] =
+ {
+ {
+ "command", required_argument, 0, 'c'
+ }
+ ,
+ {
+ "interactive", no_argument, 0, 'i'
+ }
+ ,
+ {
+ "profile", required_argument, 0, 'p'
+ }
+ ,
+ {
+ "help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ "version", no_argument, 0, 'v'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ int opt_index = 0;
+
+ int opt = getopt_long( argc,
+ argv,
+ "hivc:p:",
+ long_options,
+ &opt_index );
+
+#else
+ int opt = getopt( argc,
+ argv,
+ "hivc:p:" );
+#endif
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ break;
+
+ case 'c':
+ cmd = optarg;
+ is_interactive_session = 0;
+ break;
+
+ case 'h':
+ cmd = "help";
+ //interactive=0;
+
+ break;
+
+ case 'i':
+ force_interactive = 1;
+ break;
+
+ case 'p':
+ profile = optarg;
+ break;
+
+ case 'v':
+ fwprintf( stderr,
+ L"%s, version %s\n",
+ PACKAGE_NAME,
+ PACKAGE_VERSION );
+ exit( 0 );
+
+ case '?':
+ return 1;
+
+ }
+ }
+
+ my_optind = optind;
+
+ is_login |= strcmp( argv[0], "-fish") == 0;
+// fwprintf( stderr, L"%s\n", argv[0] );
+
+ is_interactive_session &= (cmd == 0);
+ is_interactive_session &= (my_optind == argc);
+ is_interactive_session &= isatty(STDIN_FILENO);
+
+// fwprintf( stderr, L"%d %d %d\n", cmd==0, my_optind == argc, isatty(STDIN_FILENO) );
+
+ if( force_interactive )
+ is_interactive_session=1;
+
+ parser_init();
+ builtin_init();
+ function_init();
+ env_init();
+ complete_init();
+ reader_init();
+
+ reader_push_current_filename( L"(internal)" );
+
+ if( read_init() )
+ {
+ if( cmd != 0 )
+ {
+ wchar_t *cmd_wcs = str2wcs( cmd );
+ res = eval( cmd_wcs, 0, TOP );
+ free(cmd_wcs);
+ reader_exit(0);
+ }
+ else
+ {
+ if( my_optind == argc )
+ {
+ reader_push_current_filename( L"(stdin)" );
+ res = reader_read();
+ reader_pop_current_filename();
+ }
+ else
+ {
+ char **ptr;
+ char *file = *(argv+1);
+ int i;
+ string_buffer_t sb;
+
+ if( close( 0 ) )
+ {
+ wperror(L"close");
+ return 1;
+ }
+ if( open(file, O_RDONLY) == -1 )
+ {
+ wperror( L"open" );
+ return 1;
+ }
+
+ sb_init( &sb );
+
+ if( *(argv+2))
+ {
+ for( i=1,ptr = argv+2; *ptr; i++, ptr++ )
+ {
+ if( i != 1 )
+ sb_append( &sb, ARRAY_SEP_STR );
+ wchar_t *val = str2wcs( *ptr );
+ sb_append( &sb, val );
+ free( val );
+ }
+
+ env_set( L"argv", (wchar_t *)sb.buff, 0 );
+ sb_destroy( &sb );
+ }
+
+ reader_push_current_filename( str2wcs( file ) );
+ res = reader_read();
+
+ if( res )
+ {
+ debug( 1,
+ L"Error while reading file %ls\n",
+ reader_current_filename() );
+ }
+ free(reader_pop_current_filename());
+ }
+ }
+ }
+
+
+ if( function_exists(L"fish_on_exit"))
+ {
+ eval( L"fish_on_exit", 0, TOP );
+ }
+ job_do_notification();
+
+ reader_pop_current_filename();
+
+ proc_destroy();
+ env_destroy();
+ builtin_destroy();
+ function_destroy();
+ complete_destroy();
+ reader_destroy();
+ parser_destroy();
+ wutil_destroy();
+ common_destroy();
+
+ intern_free_all();
+
+ return res;
+}
diff --git a/mimedb.c b/mimedb.c
new file mode 100644
index 00000000..62eb0d79
--- /dev/null
+++ b/mimedb.c
@@ -0,0 +1,1266 @@
+/** \file mimedb.c
+
+mimedb is a program for checking the mimetype, description and
+default action associated with a file or mimetype. It uses the
+xdgmime library written by the fine folks at freedesktop.org. There does
+not seem to be any standard way for the user to change the preferred
+application yet.
+
+The first implementation of mimedb used xml_grep to parse the xml
+file for the mime entry to determine the description. This was abandoned
+because of the performance implications of parsing xml. The current
+version only does a simple string search, which is much, much
+faster but it might fall on it's head.
+
+This code is Copyright 2005 Axel Liljencrantz.
+It is released under the GPL.
+
+The xdgmime library is dual licensed under LGPL/artistic
+license. Read the source code of the library for more information.
+*/
+
+#include "config.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <errno.h>
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#include "xdgmime.h"
+#include "util.h"
+
+/**
+ Location of the applications .desktop file, relative to a base mime directory
+*/
+#define APPLICATIONS_DIR "applications/"
+
+/**
+ Location of the mime xml database, relative to a base mime directory
+*/
+#define MIME_DIR "mime/"
+/**
+ Filename suffix for XML files
+*/
+#define MIME_SUFFIX ".xml"
+
+/**
+ Start tag for comment
+*/
+#define START_TAG "<comment>"
+
+/**
+ End tab for comment
+*/
+#define STOP_TAG "</comment>"
+
+/**
+ File contains cached list of mime actions
+*/
+#define DESKTOP_DEFAULT "applications/defaults.list"
+
+
+/**
+ All types of input and output possible
+*/
+enum
+{
+ FILEDATA,
+ FILENAME,
+ MIMETYPE,
+ DESCRIPTION,
+ ACTION,
+ LAUNCH
+}
+;
+
+/**
+ Error flag. Non-zero if something bad happened.
+*/
+static int error = 0;
+
+/**
+ String of characters to send to system() to launch a file
+*/
+static char *launch_buff=0;
+
+/**
+ Length of the launch_buff buffer
+*/
+static int launch_len=0;
+/**
+ Current position in the launch_buff buffer
+*/
+static int launch_pos=0;
+
+/**
+ Dynamically generated function, made from the documentation in doc_src.
+*/
+void print_help();
+
+/**
+ Call malloc, set error flag and print message on failure
+*/
+void *my_malloc( size_t s )
+{
+ void *res = malloc( s );
+ if( !s )
+ {
+ error=1;
+ fprintf( stderr, "mimedb: Out of memory\n" );
+ }
+ return res;
+}
+
+/**
+ Duplicate string, set error flag and print message on failure
+*/
+char *my_strdup( char *s )
+{
+ char *res = strdup( s );
+ if( !s )
+ {
+ error=1;
+ fprintf( stderr, "mimedb: Out of memory\n" );
+ }
+ return res;
+}
+
+
+/**
+ Search the file \c filename for the first line starting with \c
+ match, which is returned in a newly allocated string.
+*/
+static char * search_ini( const char *filename, const char *match )
+{
+ FILE *f = fopen( filename, "r" );
+ char buf[4096];
+ int len=strlen(match);
+ int done = 0;
+
+ if(!f )
+ {
+ perror( "fopen" );
+ error=1;
+ return 0;
+ }
+ while( !done )
+ {
+ if( !fgets( buf, 4096, f ) )
+ {
+ if( !feof( f ) )
+ {
+ perror( "fgets" );
+ error=1;
+ }
+ buf[0]=0;
+ done = 1;
+ }
+ else if( strncmp( buf, match,len )==0)
+ {
+ done=1;
+ }
+ }
+ fclose( f );
+ if( buf[0] )
+ {
+ char *res=strdup(buf);
+ if( res )
+ {
+ if(res[strlen(res)-1]=='\n' )
+ res[strlen(res)-1]='\0';
+ }
+ return res;
+ }
+ else
+ return (char *)0;
+}
+
+/**
+ Test if the specified file exists. If it does not, also try
+ replacing dashes with slashes in \c in.
+*/
+static char *file_exists( const char *dir, const char *in )
+{
+ char *filename = my_malloc( strlen( dir ) + strlen(in) + 1 );
+ char *replaceme;
+ struct stat buf;
+
+// fprintf( stderr, "Check %s%s\n", dir, in );
+
+ if( !filename )
+ {
+ return 0;
+ }
+ strcpy( filename, dir );
+ strcat( filename, in );
+
+ if( !stat( filename, &buf ) )
+ return filename;
+
+ free( filename );
+
+ /*
+ DOH! File does not exist. But all is not lost. KDE sometimes uses
+ a slash in the name as a directory separator. We try to replace
+ a dash with a slash and try again.
+ */
+ replaceme = strchr( in, '-' );
+ if( replaceme )
+ {
+ char *res;
+
+ *replaceme = '/';
+ res = file_exists( dir, in );
+ *replaceme = '-';
+ return res;
+ }
+ /*
+ OK, no more slashes left. We really are screwed. Nothing to to
+ but admit defeat and go home.
+ */
+ return 0;
+}
+
+
+/**
+ Try to find the specified file in any of the possible directories
+ where mime files can be located. This code is shamelessly stolen
+ from xdg_run_command_on_dirs.
+*/
+static char *get_filename( char *f )
+{
+ char *result;
+ const char *xdg_data_home;
+ const char *xdg_data_dirs;
+ const char *ptr;
+
+ xdg_data_home = getenv ("XDG_DATA_HOME");
+ if (xdg_data_home)
+ {
+ result = file_exists( xdg_data_home, f );
+ if (result)
+ return result;
+ }
+ else
+ {
+ const char *home;
+
+ home = getenv ("HOME");
+ if (home != NULL)
+ {
+ char *guessed_xdg_home;
+
+ guessed_xdg_home = my_malloc (strlen (home) + strlen ("/.local/share/") + 1);
+ if( !guessed_xdg_home )
+ return 0;
+
+ strcpy (guessed_xdg_home, home);
+ strcat (guessed_xdg_home, "/.local/share/");
+ result = file_exists( guessed_xdg_home, f );
+ free (guessed_xdg_home);
+
+ if (result)
+ return result;
+ }
+ }
+
+ xdg_data_dirs = getenv ("XDG_DATA_DIRS");
+ if (xdg_data_dirs == NULL)
+ xdg_data_dirs = "/usr/local/share/:/usr/share/";
+
+ ptr = xdg_data_dirs;
+
+ while (*ptr != '\000')
+ {
+ const char *end_ptr;
+ char *dir;
+ int len;
+
+ end_ptr = ptr;
+ while (*end_ptr != ':' && *end_ptr != '\000')
+ end_ptr ++;
+
+ if (end_ptr == ptr)
+ {
+ ptr++;
+ continue;
+ }
+
+ if (*end_ptr == ':')
+ len = end_ptr - ptr;
+ else
+ len = end_ptr - ptr + 1;
+ dir = my_malloc (len + 1);
+ if( !dir )
+ return 0;
+
+ strncpy (dir, ptr, len);
+ dir[len] = '\0';
+ result = file_exists( dir, f );
+
+ free (dir);
+
+ if (result)
+ return result;
+
+ ptr = end_ptr;
+ }
+ return 0;
+}
+
+/**
+ Remove excessive whitespace from string. Replaces arbitrary sequence
+ of whitespace with a single space. Also removes any leading and
+ trailing whitespace
+*/
+static char *munge( char *in )
+{
+ char *out = my_malloc( strlen( in )+1 );
+ char *p=out;
+ int had_whitespace = 0;
+ int printed = 0;
+ if( !out )
+ {
+ return 0;
+ }
+
+ while( 1 )
+ {
+// fprintf( stderr, "%c\n", *in );
+
+ switch( *in )
+ {
+ case ' ':
+ case '\n':
+ case '\t':
+ case '\r':
+ {
+ had_whitespace = 1;
+ break;
+ }
+ case '\0':
+ *p = '\0';
+ return out;
+ default:
+ {
+ if( printed && had_whitespace )
+ {
+ *(p++)=' ';
+ }
+ printed=1;
+ had_whitespace=0;
+ *(p++)=*in;
+ break;
+ }
+ }
+ in++;
+ }
+ fprintf( stderr, "mimedb: Unknown error in munge()\n" );
+ error=1;
+ return 0;
+}
+
+/**
+ Get description for a specified mimetype.
+*/
+static char *get_description( const char *mimetype )
+{
+ char *fn_part;
+
+ char *fn;
+ int fd;
+ struct stat st;
+ char *contents;
+ char *start, *stop;
+
+ fn_part = my_malloc( strlen(MIME_DIR) + strlen( mimetype) + strlen(MIME_SUFFIX) + 1 );
+
+ if( !fn_part )
+ {
+ return 0;
+ }
+
+ strcpy( fn_part, MIME_DIR );
+ strcat( fn_part, mimetype );
+ strcat( fn_part, MIME_SUFFIX );
+
+ fn = get_filename(fn_part); //malloc( strlen(MIME_DIR) +strlen( MIME_SUFFIX)+ strlen( mimetype ) + 1 );
+ free(fn_part );
+
+ if( !fn )
+ {
+ return 0;
+ }
+
+ fd = open( fn, O_RDONLY );
+
+// fprintf( stderr, "%s\n", fn );
+
+ if( fd == -1 )
+ {
+ perror( "open" );
+ error=1;
+ return 0;
+ }
+
+ if( stat( fn, &st) )
+ {
+ perror( "stat" );
+ error=1;
+ return 0;
+ }
+
+ contents = my_malloc( st.st_size + 1 );
+ if( !contents )
+ {
+ return 0;
+ }
+
+ if( read( fd, contents, st.st_size ) != st.st_size )
+ {
+ perror( "read" );
+ error=1;
+ return 0;
+ }
+
+ close( fd );
+ free( fn );
+
+ contents[st.st_size]=0;
+
+ start = strstr( contents, START_TAG );
+ if( start )
+ {
+ start += strlen(START_TAG);
+ stop = strstr( start, STOP_TAG );
+ if( stop )
+ {
+ char *res;
+ *stop = '\0';
+ res = munge( start );
+ free( contents );
+ return res;
+ }
+ }
+ free( contents );
+ fprintf( stderr, "mimedb: No description for type %s\n", mimetype );
+ error=1;
+ return 0;
+
+}
+
+
+/**
+ Get default action for a specified mimetype.
+*/
+static char *get_action( const char *mimetype )
+{
+ char *res=0;
+
+ char *launcher;
+ char *end;
+ char *mime_filename;
+
+ char *launcher_str;
+ char *launcher_filename, *launcher_command_str, *launcher_command;
+ char *launcher_full;
+
+ mime_filename = get_filename( DESKTOP_DEFAULT );
+ if( !mime_filename )
+ return 0;
+
+ launcher_str = search_ini( mime_filename, mimetype );
+
+ free( mime_filename );
+
+ if( !launcher_str )
+ {
+ /*
+ This type does not have a launcher. Try the supertype!
+ */
+// fprintf( stderr, "mimedb: %s does not have launcher, try supertype\n", mimetype );
+ const char ** parents = xdg_mime_get_mime_parents(mimetype);
+
+ const char **p;
+ if( parents )
+ {
+ for( p=parents; *p; p++ )
+ {
+ char *a = get_action(*p);
+ if( a != 0 )
+ return a;
+ }
+ }
+ /*
+ Just in case subclassing doesn't work, (It doesn't on Fedora
+ Core 3) we also test some common subclassings.
+ */
+
+ if( strncmp( mimetype, "text/", 5 ) == 0 )
+ return get_action( "text/plain" );
+
+ return 0;
+ }
+
+// fprintf( stderr, "WOOT %s\n", launcher_str );
+ launcher = strchr( launcher_str, '=' );
+
+ if( !launcher )
+ {
+ fprintf( stderr, "Could not parse launcher string %s\n", launcher_str );
+ error=1;
+ return 0;
+ }
+
+ /* Skip the = */
+ launcher++;
+
+ /* Only use first launcher */
+ end = strchr( launcher, ';' );
+ if( end )
+ *end = '\0';
+
+ launcher_full = my_malloc( strlen( launcher) + strlen( APPLICATIONS_DIR)+1 );
+ if( !launcher_full )
+ {
+ free( launcher_str );
+ return 0;
+ }
+
+ strcpy( launcher_full, APPLICATIONS_DIR );
+ strcat( launcher_full, launcher );
+ free( launcher_str );
+
+ launcher_filename = get_filename( launcher_full );
+
+ free( launcher_full );
+
+ launcher_command_str = search_ini( launcher_filename, "Exec=" );
+
+ if( !launcher_command_str )
+ {
+ fprintf( stderr,
+ "mimedb: Default launcher %s does not specify how to start\n",
+ launcher_filename );
+ free( launcher_filename );
+ return 0;
+ }
+
+ free( launcher_filename );
+
+ launcher_command = strchr( launcher_command_str, '=' );
+ launcher_command++;
+
+ res = my_strdup( launcher_command );
+
+ free( launcher_command_str );
+
+ return res;
+}
+
+
+/**
+ Helper function for launch. Write the specified byte to the string we will execute
+*/
+static void writer( char c )
+{
+ if( launch_len == -1 )
+ return;
+
+ if( launch_len <= launch_pos )
+ {
+ int new_len = launch_len?2*launch_len:256;
+ char *new_buff = realloc( launch_buff, new_len );
+ if( !new_buff )
+ {
+ free( launch_buff );
+ launch_len = -1;
+ error=1;
+ return;
+ }
+ launch_buff = new_buff;
+ launch_len = new_len;
+
+ }
+ launch_buff[launch_pos++]=c;
+}
+
+/**
+ Write out the specified byte in hex
+*/
+static void writer_hex( int num )
+{
+ int a, b;
+ a = num /16;
+ b = num %16;
+ writer( a>9?('A'+a-10):('0'+a));
+ writer( b>9?('A'+b-10):('0'+b));
+}
+
+/**
+ Return current directory in newly allocated string
+*/
+static char *my_getcwd ()
+{
+ size_t size = 100;
+ while (1)
+ {
+ char *buffer = (char *) malloc (size);
+ if (getcwd (buffer, size) == buffer)
+ return buffer;
+ free (buffer);
+ if (errno != ERANGE)
+ return 0;
+ size *= 2;
+ }
+}
+
+/**
+ Return absolute filename of specified file
+ */
+static char *get_fullfile( char *file )
+{
+ char *fullfile;
+
+ if( file[0] == '/' )
+ {
+ fullfile = file;
+ }
+ else
+ {
+ char *cwd = my_getcwd();
+ if( !cwd )
+ {
+ error = 1;
+ perror( "getcwd" );
+ return 0;
+ }
+
+ int l = strlen(cwd);
+
+ fullfile = my_malloc( l + strlen(file)+2 );
+ if( !fullfile )
+ {
+ free(cwd);
+ return 0;
+ }
+ strcpy( fullfile, cwd );
+ if( cwd[l-1] != '/' )
+ strcat(fullfile, "/" );
+ strcat( fullfile, file );
+
+ free(cwd);
+ }
+ return fullfile;
+}
+
+
+/**
+ Write specified file as an URL
+*/
+static void write_url( char *file )
+{
+ char *fullfile = get_fullfile( file );
+ char *str = fullfile;
+
+ if( str == 0 )
+ {
+ launch_len = -1;
+ return;
+ }
+
+ writer( 'f');
+ writer( 'i');
+ writer( 'l');
+ writer( 'e');
+ writer( ':');
+ writer( '/');
+ writer( '/');
+ while( *str )
+ {
+ if( ((*str >= 'a') && (*str <='z')) ||
+ ((*str >= 'A') && (*str <='Z')) ||
+ ((*str >= '0') && (*str <='9')) ||
+ (strchr( "./_",*str) != 0) )
+ {
+ writer(*str);
+ }
+ else if(strchr( "()?&=",*str) != 0)
+ {
+ writer('\\');
+ writer(*str);
+ }
+ else
+ {
+ writer( '%' );
+ writer_hex( *str );
+ }
+ str++;
+ }
+ if( fullfile != file )
+ free( fullfile );
+
+}
+
+/**
+ Write specified file
+*/
+static void write_file( char *file, int print_path )
+{
+ char *fullfile;
+ char *str;
+ if( print_path )
+ {
+ fullfile = get_fullfile( file );
+ str = fullfile;
+ }
+ else
+ {
+ fullfile = my_strdup( file );
+ if( !fullfile )
+ {
+ return;
+ }
+ str = basename( fullfile );
+ }
+
+ if( !str )
+ {
+ error = 1;
+ return;
+ }
+
+ while( *str )
+ {
+ switch(*str )
+ {
+ case ')':
+ case '(':
+ case '-':
+ case '#':
+ case '$':
+ case '}':
+ case '{':
+ case ']':
+ case '[':
+ case '*':
+ case '?':
+ case ' ':
+ case '|':
+ case '<':
+ case '>':
+ case '^':
+ case '&':
+ case '\\':
+ case '`':
+ case '\'':
+ case '\"':
+ writer('\\');
+ writer(*str);
+ break;
+
+ case '\n':
+ writer('\\');
+ writer('n');
+ break;
+
+ case '\r':
+ writer('\\');
+ writer('r');
+ break;
+
+ case '\t':
+ writer('\\');
+ writer('t');
+ break;
+
+ case '\b':
+ writer('\\');
+ writer('b');
+ break;
+
+ case '\v':
+ writer('\\');
+ writer('v');
+ break;
+
+ default:
+ writer(*str);
+ break;
+ }
+ str++;
+ }
+
+ if( fullfile != file )
+ free( fullfile );
+}
+
+/**
+ Use the specified launch filter to launch all the files in the specified list.
+
+ \param filter the action to take
+ \param files the list of files for which to perform the action
+ \param fileno an internal value. Should always be set to zero.
+*/
+static void launch( char *filter, array_list_t *files, int fileno )
+{
+ char *filter_org=filter;
+ int count=0;
+ int launch_again=0;
+
+ if( al_get_count( files ) <= fileno )
+ return;
+
+
+ launch_pos=0;
+
+ for( ;*filter && !error; filter++)
+ {
+ if(*filter == '%')
+ {
+ filter++;
+ switch( *filter )
+ {
+ case 'u':
+ {
+ launch_again = 1;
+ write_url( (char *)al_get( files, fileno ) );
+ break;
+ }
+ case 'U':
+ {
+ int i;
+ for( i=0; i<al_get_count( files ); i++ )
+ {
+ if( i != 0 )
+ writer( ' ' );
+ write_url( (char *)al_get( files, i ) );
+ if( error )
+ break;
+ }
+
+ break;
+ }
+
+ case 'f':
+ case 'n':
+ {
+ launch_again = 1;
+ write_file( (char *)al_get( files, fileno ), *filter == 'f' );
+ break;
+ }
+
+ case 'F':
+ case 'N':
+ {
+ int i;
+ for( i=0; i<al_get_count( files ); i++ )
+ {
+ if( i != 0 )
+ writer( ' ' );
+ write_file( (char *)al_get( files, i ), *filter == 'F' );
+ if( error )
+ break;
+ }
+ break;
+ }
+
+
+ case 'd':
+ {
+ char *cpy = get_fullfile( (char *)al_get( files, fileno ) );
+ char *dir;
+
+ launch_again=1;
+ /*
+ We wish to modify this string, make sure it is only a copy
+ */
+ if( cpy == al_get( files, fileno ) )
+ cpy = my_strdup( cpy );
+
+ if( cpy == 0 )
+ {
+ break;
+ }
+ dir=dirname( cpy );
+ write_file( dir, 1 );
+ free( cpy );
+
+ break;
+ }
+
+ case 'D':
+ {
+ int i;
+ for( i=0; i<al_get_count( files ); i++ )
+ {
+ char *cpy = get_fullfile( (char *)al_get( files, i ) );
+ char *dir;
+
+ /*
+ We wish to modify this string, make sure it is only a copy
+ */
+ if( cpy == al_get( files, i ) )
+ cpy = my_strdup( cpy );
+
+ if( cpy == 0 )
+ {
+ break;
+ }
+ dir=dirname( cpy );
+
+ if( i != 0 )
+ writer( ' ' );
+
+ write_file( dir, 1 );
+ free( cpy );
+
+ }
+ break;
+ }
+
+ default:
+ fprintf( stderr, "Unsupported switch %c in launch string %s\n", *filter, filter_org );
+ launch_len=0;
+ break;
+
+ }
+ }
+ else
+ {
+ writer( *filter );
+ count++;
+ }
+ }
+
+ if( error )
+ return;
+
+ switch( launch_len )
+ {
+ case -1:
+ {
+ launch_len = 0;
+ fprintf( stderr, "mimedb: Out of memory\n" );
+ return;
+ }
+ case 0:
+ {
+ return;
+ }
+ default:
+ {
+
+ writer( ' ' );
+ writer( '&' );
+ writer( '\0' );
+
+// fprintf( stderr, "mimedb: %s\n", launch_buff );
+ system( launch_buff );
+ break;
+ }
+ }
+ if( launch_again )
+ {
+ launch( filter_org, files, fileno+1 );
+ }
+
+}
+
+/**
+ Clean up one entry from the hash table of launch files
+*/
+static void clear_entry( const void *key, const void *val )
+{
+ /*
+ The key is a mime value, either from the libraries internal hash
+ table of mime types or from the command line. Either way, it
+ should not be freed.
+
+ The value is an array_list_t of filenames. The filenames com from
+ the argument list and should not be freed. The arraylist,
+ however, should be destroyed and freed.
+ */
+ array_list_t *l = (array_list_t *)val;
+ al_destroy( l );
+ free( l );
+}
+
+
+
+/**
+ Main function. Parses options and calls helper function for any heavy lifting.
+*/
+int main (int argc, char *argv[])
+{
+ int input_type=FILEDATA;
+ int output_type=MIMETYPE;
+
+ const char *mimetype;
+ char *output=0;
+
+ int i;
+
+ hash_table_t launch_hash;
+
+
+ /*
+ Parse options
+ */
+ while( 1 )
+ {
+#ifdef __GLIBC__
+ static struct option
+ long_options[] =
+ {
+ {
+ "input-file-data", no_argument, 0, 't'
+ }
+ ,
+ {
+ "input-filename", no_argument, 0, 'f'
+ }
+ ,
+ {
+ "input-mime", no_argument, 0, 'i'
+ }
+ ,
+ {
+ "output-mime", no_argument, 0, 'm'
+ }
+ ,
+ {
+ "output-description", no_argument, 0, 'd'
+ }
+ ,
+ {
+ "output-action", no_argument, 0, 'a'
+ }
+ ,
+ {
+ "help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ "version", no_argument, 0, 'v'
+ }
+ ,
+ {
+ "launch", no_argument, 0, 'l'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ int opt_index = 0;
+
+ int opt = getopt_long( argc,
+ argv,
+ "tfimdalhv",
+ long_options,
+ &opt_index );
+
+#else
+ int opt = getopt( argc,
+ argv,
+ "tfimdalhv" );
+#endif
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ break;
+
+ case 't':
+ input_type=FILEDATA;
+ break;
+
+ case 'f':
+ input_type=FILENAME;
+ break;
+
+ case 'i':
+ input_type=MIMETYPE;
+ break;
+
+ case 'm':
+ output_type=MIMETYPE;
+ break;
+
+ case 'd':
+ output_type=DESCRIPTION;
+ break;
+
+ case 'a':
+ output_type=ACTION;
+ break;
+
+ case 'l':
+ output_type=LAUNCH;
+ break;
+
+ case 'h':
+ print_help();
+ exit(0);
+
+ case 'v':
+ printf( "mimedb, version %s\n", PACKAGE_VERSION );
+ exit( 0 );
+
+ case '?':
+ return 1;
+
+ }
+ }
+
+ if( ( output_type == LAUNCH )&&(input_type==MIMETYPE))
+ {
+ fprintf( stderr, "Can not launch a mimetype\n" );
+ print_help();
+ exit(1);
+ }
+
+ if( output_type == LAUNCH )
+ hash_init( &launch_hash, &hash_str_func, &hash_str_cmp );
+
+
+ /*
+ Loop over all non option arguments and do the specified lookup
+ */
+
+ //fprintf( stderr, "Input %d, output %d\n", input_type, output_type );
+
+ for (i = optind; (i < argc)&&(!error); i++)
+ {
+ /* Convert from filename to mimetype, if needed */
+ if( input_type == FILENAME )
+ {
+ mimetype = xdg_mime_get_mime_type_from_file_name(argv[i]);
+ }
+ else if( input_type == FILEDATA )
+ {
+ mimetype = xdg_mime_get_mime_type_for_file(argv[i]);
+ }
+ else
+ mimetype = xdg_mime_is_valid_mime_type(argv[i])?argv[i]:0;
+
+ mimetype = xdg_mime_unalias_mime_type (mimetype);
+ if( !mimetype )
+ {
+ fprintf( stderr, "mimedb: Could not parse mimetype from argument %s\n", argv[i] );
+ error=1;
+ return 1;
+ }
+
+ /*
+ Convert from mimetype to whatever, if needed
+ */
+ switch( output_type )
+ {
+ case MIMETYPE:
+ {
+ output = (char *)mimetype;
+ break;
+
+ }
+ case DESCRIPTION:
+ {
+ output = get_description( mimetype );
+ break;
+ }
+ case ACTION:
+ {
+ output = get_action( mimetype );
+ break;
+ }
+ case LAUNCH:
+ {
+ /*
+ There may be more files using the same launcher, we
+ add them all up in little array_list_ts and launched
+ them together after all the arguments have been
+ parsed.
+ */
+ array_list_t *l= (array_list_t *)hash_get( &launch_hash, mimetype );
+ output = 0;
+
+ if( !l )
+ {
+ l = my_malloc( sizeof( array_list_t ) );
+ if( l == 0 )
+ {
+ break;
+ }
+ al_init( l );
+ hash_put( &launch_hash, mimetype, l );
+ }
+ al_push( l, argv[i] );
+ }
+ }
+
+ /*
+ Print the glorious result
+ */
+ if( output )
+ {
+ printf( "%s\n", output );
+ if( output != mimetype )
+ free( output );
+ }
+ output = 0;
+ }
+
+ /*
+ Perform the actual launching
+ */
+ if( output_type == LAUNCH )
+ {
+ int i;
+ array_list_t mimes;
+ al_init( &mimes );
+ hash_get_keys( &launch_hash, &mimes );
+ for( i=0; i<al_get_count( &mimes ); i++ )
+ {
+ char *mimetype = (char *)al_get( &mimes, i );
+ array_list_t *files = (array_list_t *)hash_get( &launch_hash, mimetype );
+ if( !files )
+ {
+ fprintf( stderr, "mimedb: Unknown error\n" );
+ error=1;
+ break;
+ }
+
+ char *launcher = get_action( mimetype );
+
+ if( launcher )
+ {
+ launch( launcher, files, 0 );
+ free( launcher );
+ }
+ }
+ hash_foreach( &launch_hash, &clear_entry );
+ hash_destroy( &launch_hash );
+ al_destroy( &mimes );
+ }
+
+ if( launch_buff )
+ free( launch_buff );
+
+ xdg_mime_shutdown();
+
+ return error;
+}
diff --git a/mimedb.h b/mimedb.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mimedb.h
diff --git a/output.c b/output.c
new file mode 100644
index 00000000..92f97f03
--- /dev/null
+++ b/output.c
@@ -0,0 +1,367 @@
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <wctype.h>
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <time.h>
+#include <wchar.h>
+
+#include "util.h"
+#include "wutil.h"
+#include "expand.h"
+#include "common.h"
+#include "output.h"
+#include "highlight.h"
+
+/**
+ Number of color names in the col array
+*/
+#define COLORS (sizeof(col)/sizeof(wchar_t *))
+
+/**
+ Names of different colors.
+*/
+static wchar_t *col[]=
+{
+ L"black",
+ L"red",
+ L"green",
+ L"brown",
+ L"yellow",
+ L"blue",
+ L"magenta",
+ L"purple",
+ L"cyan",
+ L"white"
+ L"normal"
+}
+ ;
+
+/**
+ Mapping from color name (the 'col' array) to color index as used in
+ ANSI color terminals, and also the fish_color_* constants defined
+ in highlight.h. Non-ANSI terminals will display the wrong colors,
+ since they use a different mapping.
+*/
+static int col_idx[]=
+{
+ 0,
+ 1,
+ 2,
+ 3,
+ 3,
+ 4,
+ 5,
+ 5,
+ 6,
+ 7,
+ FISH_COLOR_NORMAL,
+}
+ ;
+
+void set_color( int c, int c2 )
+{
+ static int last_color = FISH_COLOR_NORMAL, last_color2=FISH_COLOR_NORMAL;
+ int bg_set=0, last_bg_set=0;
+ char *fg = 0, *bg=0;
+
+ if( (set_a_foreground != 0) && (strlen( set_a_foreground) != 0 ) )
+ {
+ fg = set_a_foreground;
+ bg = set_a_background;
+ }
+ else if( (set_foreground != 0) && (strlen( set_foreground) != 0 ) )
+ {
+ fg = set_foreground;
+ bg = set_background;
+ }
+
+ if( (c == FISH_COLOR_RESET) || (c2 == FISH_COLOR_RESET))
+ {
+ c = c2 = FISH_COLOR_NORMAL;
+ if( fg )
+ writembs( tparm( set_a_foreground, 0 ) );
+ writembs( exit_attribute_mode );
+ return;
+ }
+
+ if( last_color2 != FISH_COLOR_NORMAL &&
+ last_color2 != FISH_COLOR_RESET &&
+ last_color2 != FISH_COLOR_IGNORE )
+ {
+ /*
+ Background was set
+ */
+ last_bg_set=1;
+ }
+
+ if( c2 != FISH_COLOR_NORMAL &&
+ c2 != FISH_COLOR_RESET &&
+ c2 != FISH_COLOR_IGNORE )
+ {
+ /*
+ Background is set
+ */
+ bg_set=1;
+ c = (c2==FISH_COLOR_WHITE)?FISH_COLOR_BLACK:FISH_COLOR_WHITE;
+ }
+
+ if( (enter_bold_mode != 0) && (strlen(enter_bold_mode) > 0))
+ {
+ if(bg_set && !last_bg_set)
+ {
+ /*
+ Background color changed and is set, so we enter bold mode to make reading easier
+ */
+ writembs( enter_bold_mode );
+ }
+ if(!bg_set && last_bg_set)
+ {
+ /*
+ Background color changed and is no longer set, so we exit bold mode
+ */
+ writembs( exit_attribute_mode );
+ /*
+ We don't know if exit_attribute_mode resets colors, so
+ we set it to something known.
+ */
+ if( fg )
+ {
+ writembs( tparm( fg, 0 ) );
+ last_color=0;
+ }
+ }
+ }
+
+ if( last_color != c )
+ {
+ if( c==FISH_COLOR_NORMAL )
+ {
+ if( fg )
+ writembs( tparm( fg, 0 ) );
+ writembs( exit_attribute_mode );
+
+ last_color2 = FISH_COLOR_NORMAL;
+ }
+ else if( ( c >= 0) && ( c < FISH_COLOR_NORMAL ) )
+ {
+ if( fg )
+ {
+ writembs( tparm( fg, c ) );
+ }
+ }
+ }
+
+ last_color = c;
+
+ if( last_color2 != c2 )
+ {
+ if( c2 == FISH_COLOR_NORMAL )
+ {
+ if( bg )
+ {
+ writembs( tparm( bg, 0 ) );
+ }
+
+ writembs(exit_attribute_mode);
+ if(( last_color != FISH_COLOR_NORMAL ) && fg )
+ {
+ writembs(tparm( fg, last_color ));
+ }
+
+ last_color2 = c2;
+ }
+ else if ((c2 >= 0 ) &&(c2 < FISH_COLOR_NORMAL))
+ {
+ if( bg )
+ {
+ writembs( tparm( bg, c2 ) );
+ }
+ last_color2 = c2;
+ }
+ }
+}
+
+int writembs( char *str )
+{
+#ifdef TPUTS_KLUDGE
+ write( 1, str, strlen(str));
+#else
+ tputs(str,1,&writeb);
+#endif
+ return 0;
+}
+
+/**
+ Write a wide character to fd 1.
+*/
+int writech( wint_t ch )
+{
+ static mbstate_t out_state;
+ char buff[MB_CUR_MAX];
+ size_t bytes = wcrtomb( buff, ch, &out_state );
+ int err;
+
+ while( (err =write( 1, buff, bytes ) ) )
+ {
+ if( err >= 0 )
+ break;
+
+ if( errno == EINTR )
+ continue;
+
+ wperror( L"write" );
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ Write a wide character string to FD 1.
+*/
+void writestr( const wchar_t *str )
+{
+ while( *str != 0 )
+ writech( *(str++) );
+}
+
+
+/**
+ Write a wide character string to FD 1. If the string is wider than
+ the specified maximum, truncate and ellipsize it.
+*/
+void writestr_ellipsis( const wchar_t *str, int max_width )
+{
+ int written=0;
+ int tot = my_wcswidth(str);
+
+ if( tot <= max_width )
+ {
+ writestr( str );
+ return;
+ }
+
+ while( *str != 0 )
+ {
+ int w = wcwidth( *str );
+ if( written+w+wcwidth( ellipsis_char )>max_width )
+ break;
+ written+=w;
+ writech( *(str++) );
+ }
+
+ written += wcwidth( ellipsis_char );
+ writech( ellipsis_char );
+
+ while( written < max_width )
+ {
+ written++;
+ writestr( L" " );
+ }
+}
+
+/**
+ Escape and write a string to fd 1
+*/
+int write_escaped_str( const wchar_t *str, int max_len )
+{
+
+ wchar_t *out = escape( wcsdup(str), 1 );
+ int i;
+ int len = my_wcswidth( out );
+ int written=0;
+
+ if( max_len && (max_len < len))
+ {
+ for( i=0; (written+wcwidth(out[i]))<=(max_len-1); i++ )
+ {
+ writech( out[i] );
+ written += wcwidth( out[i] );
+ }
+ writech( ellipsis_char );
+ written += wcwidth( ellipsis_char );
+
+ for( i=written; i<max_len; i++ )
+ {
+ writech( L' ' );
+ written++;
+ }
+ }
+ else
+ {
+ written = len;
+ writestr( out );
+ }
+
+ free( out );
+ return written;
+}
+
+
+/**
+ parm_ich seems to often be undefined, so we use this
+ workalike. Writes the specified number of spaces.
+*/
+int writespace( int c )
+{
+ if( repeat_char && strlen(repeat_char) )
+ {
+ debug( 1, L"YAY" );
+
+ writembs( tparm( repeat_char, ' ', c ) );
+ }
+ else
+ {
+ write( 1, " ", mini(c,8) );
+ if( c>8)
+ {
+ writespace( c-8);
+ }
+ }
+
+ return 0;
+}
+
+
+int output_color_code( const wchar_t *val )
+{
+ int i, color=-1;
+
+ for( i=0; i<COLORS; i++ )
+ {
+ if( wcscasecmp( col[i], val ) == 0 )
+ {
+ color = col_idx[i];
+ break;
+ }
+ }
+
+ if( color >= 0 )
+ return color;
+ else
+ return FISH_COLOR_NORMAL;
+}
diff --git a/output.h b/output.h
new file mode 100644
index 00000000..cf6fc9b6
--- /dev/null
+++ b/output.h
@@ -0,0 +1,89 @@
+/**
+ Constants for various character classifications. Each character of a command string can be classified as one of the following types.
+*/
+enum
+{
+ HIGHLIGHT_NORMAL,
+ HIGHLIGHT_COMMAND,
+ HIGHLIGHT_SUBSHELL,
+ HIGHLIGHT_REDIRECTION,
+ HIGHLIGHT_END,
+ HIGHLIGHT_ERROR,
+ HIGHLIGHT_PARAM,
+ HIGHLIGHT_COMMENT,
+ HIGHLIGHT_MATCH,
+ HIGHLIGHT_SEARCH_MATCH,
+}
+ ;
+
+/**
+ Constants for various colors as used by the set_color function.
+*/
+enum
+{
+ FISH_COLOR_BLACK,
+ FISH_COLOR_RED,
+ FISH_COLOR_GREEN,
+ FISH_COLOR_YELLOW,
+ FISH_COLOR_BLUE,
+ FISH_COLOR_MAGENTA,
+ FISH_COLOR_CYAN,
+ FISH_COLOR_WHITE,
+ /** The default fg color of the terminal */
+ FISH_COLOR_NORMAL
+}
+;
+
+
+/**
+ Sets the fg and bg color. May be called as often as you like, since
+ if the new color is the same as the previous, nothing will be
+ written. Negative values for set_color will also be ignored. Since
+ the terminfo string this function emits can potentially cause the
+ screen to flicker, the function takes care to write as little as
+ possible.
+
+ Possible values for color are any form the FISH_COLOR_* enum,
+ FISH_COLOR_IGNORE and FISH_COLOR_RESET. FISH_COLOR_IGNORE will
+ leave the color unchanged, and FISH_COLOR_RESET will perform an
+ exit_attribute_mode, even if set_color thinks it is already in
+ FISH_COLOR_NORMAL mode.
+
+ In order to set the color to normal, three terminfo strings may
+ have to be written.
+
+ - First a string to set the color, such as set_a_foreground. This
+ is needed because otherwise the previous strings colors might be
+ removed as well.
+
+ - After that we write the exit_attribute_mode string to reset all
+ color attributes.
+
+ - Lastly we may need to write set_a_background or set_a_foreground
+ to set the other half of the color pair to what it should be.
+
+ \param c Foreground color.
+ \param c2 Background color.
+*/
+
+
+void set_color( int c, int c2 );
+
+/**
+ Write a char * narrow string to FD 1, needed for the terminfo
+ strings.
+*/
+int writembs( char *str );
+
+int writech( wint_t ch );
+
+void writestr( const wchar_t *str );
+
+void writestr_ellipsis( const wchar_t *str, int max_width );
+
+int write_escaped_str( const wchar_t *str, int max_len );
+
+int writespace( int c );
+
+int output_color_code( const wchar_t *val );
+
diff --git a/parser.c b/parser.c
new file mode 100644
index 00000000..4925f168
--- /dev/null
+++ b/parser.c
@@ -0,0 +1,2230 @@
+/** \file parser.c
+
+The fish parser. Contains functions for parsing code.
+
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <pwd.h>
+#include <dirent.h>
+#include <signal.h>
+
+#include "config.h"
+#include "util.h"
+#include "common.h"
+#include "wutil.h"
+#include "proc.h"
+#include "parser.h"
+#include "tokenizer.h"
+#include "exec.h"
+#include "wildcard.h"
+#include "function.h"
+#include "builtin.h"
+#include "builtin_help.h"
+#include "env.h"
+#include "expand.h"
+#include "reader.h"
+#include "sanity.h"
+#include "env_universal.h"
+
+/** Length of the lineinfo string used for describing the current tokenizer position */
+#define LINEINFO_SIZE 128
+
+/**
+ Maximum number of block levels in code. This is not the same as
+ maximum recursion depth, this only has to do with how many block
+ levels are legal in the source code, not at evaluation.
+*/
+#define BLOCK_MAX_COUNT 64
+
+/**
+ Maximum number of function calls, i.e. recursion depth.
+*/
+#define MAX_RECURSION_DEPTH 128
+
+/**
+ Error message for improper use of the exec builtin
+*/
+#define EXEC_ERR_MSG L"this command can not be used in a pipeline"
+
+/**
+ Error message for tokenizer error. The tokenizer message is
+ appended to this message.
+*/
+#define TOK_ERR_MSG L"Tokenizer error"
+
+/**
+ Error message for short circut command error.
+*/
+#define COND_ERR_MSG L"Short circut command requires additional command"
+
+/**
+ Error message on reaching maximum recusrion depth
+*/
+#define RECURSION_ERR_MSG L"Maximum recursion depth reached. Accidental infinite loop?"
+
+/**
+ Error message on reaching maximum number of block calls
+*/
+#define BLOCK_ERR_MSG L"Maximum number of nested blocks reached."
+
+/**
+ Error message on missing 'end'
+*/
+#define END_ERR_MSG L"Block missing 'end'"
+
+/**
+ Error message on pipe/bg character without command
+*/
+#define CMD_ERR_MSG L"Expected command"
+
+/** Last error code */
+int error_code;
+
+/** Position of last error */
+
+static int err_pos;
+
+/** Description of last error */
+static wchar_t err_str[256];
+
+/** Pointer to the current tokenizer */
+static tokenizer *current_tokenizer;
+
+/** String for representing the current line */
+static wchar_t lineinfo[LINEINFO_SIZE];
+
+/** This is the position of the beginning of the currently parsed command */
+static int current_tokenizer_pos;
+
+/** The current innermost block */
+block_t *current_block=0;
+
+/** List of called functions, used to help prevent infinite recursion */
+static array_list_t forbidden_function;
+
+/**
+ String index where the current job started.
+*/
+static int job_start_pos;
+
+io_data_t *block_io;
+
+/**
+ List of all profiling data
+*/
+static array_list_t profile_data;
+
+static int eval_level=-1;
+
+static int parse_job( process_t *p,
+ job_t *j,
+ tokenizer *tok );
+
+typedef struct
+{
+ int exec, parse, level, skipped;
+ wchar_t *cmd;
+} profile_element_t;
+
+
+int block_count( block_t *b )
+{
+ if( b==0)
+ return 0;
+ return( block_count(b->outer)+1);
+}
+
+
+void parser_push_block( int type )
+{
+ block_t *new = malloc( sizeof( block_t ));
+
+// debug( 2, L"Block push %ls %d\n", bl[type], block_count( current_block)+1 );
+ new->outer = current_block;
+ new->type = (current_block && current_block->skip)?FAKE:type;
+
+ new->skip=current_block?current_block->skip:0;
+
+ new->loop_status=LOOP_NORMAL;
+
+ current_block = new;
+
+ if( (new->type != FUNCTION_DEF) &&
+ (new->type != FAKE) &&
+ (new->type != OR) &&
+ (new->type != AND) &&
+ (new->type != TOP) )
+ {
+ env_push( type == FUNCTION_CALL );
+ }
+}
+
+void parser_pop_block()
+{
+// debug( 2, L"Block pop %ls %d\n", bl[current_block->type], block_count(current_block)-1 );
+
+ if( (current_block->type != FUNCTION_DEF ) &&
+ (current_block->type != FAKE) &&
+ (current_block->type != OR) &&
+ (current_block->type != AND) &&
+ (current_block->type != TOP) )
+ {
+ env_pop();
+ }
+
+ switch( current_block->type)
+ {
+ case FOR:
+ {
+ free( current_block->for_variable );
+ al_foreach( &current_block->for_vars,
+ (void (*)(const void *))&free );
+ al_destroy( &current_block->for_vars );
+ break;
+ }
+
+ case SWITCH:
+ {
+ free( current_block->switch_value );
+ break;
+ }
+
+ case FUNCTION_DEF:
+ {
+ free( current_block->function_name );
+ free( current_block->function_description );
+ break;
+ }
+
+ }
+
+ block_t *old = current_block;
+ current_block = current_block->outer;
+ free( old );
+}
+
+wchar_t *parser_get_block_desc( int block )
+{
+ switch( block )
+ {
+ case WHILE:
+ return L"while block";
+
+ case FOR:
+ return L"for block";
+
+ case IF:
+ return L"'if' conditional block";
+
+ case FUNCTION_DEF:
+ return L"function definition block";
+
+ case FUNCTION_CALL:
+ return L"fuction invocation block";
+
+ case SWITCH:
+ return L"switch block";
+
+ case FAKE:
+ return L"unexecutable block";
+
+ case TOP:
+ return L"global root block";
+
+ case SUBST:
+ return L"command substitution block";
+
+ case BEGIN:
+ return L"unconditional block";
+
+ case AND:
+ return L"'and' conditional command";
+
+ case OR:
+ return L"'or' conditional command";
+
+ default:
+ return L"unknown/invalid block";
+ }
+
+}
+
+
+int parser_is_subcommand( const wchar_t *cmd )
+{
+ return contains_str( cmd,
+ L"command",
+ L"builtin",
+ L"while",
+ L"exec",
+ L"if",
+ L"and",
+ L"or",
+ L"not",
+ 0 );
+}
+
+/**
+ Test if the specified string is command that opens a new block
+*/
+
+static int parser_is_block( wchar_t *word)
+{
+ return contains_str( word,
+ L"for",
+ L"while",
+ L"if",
+ L"function",
+ L"switch",
+ L"begin",
+ 0 );
+}
+
+int parser_is_reserved( wchar_t *word)
+{
+ return parser_is_block(word) ||
+ parser_is_subcommand( word ) ||
+ contains_str( word,
+ L"end",
+ L"case",
+ L"else",
+ L"return",
+ L"continue",
+ L"break",
+ 0 );
+}
+
+int parser_is_pipe_forbidden( wchar_t *word )
+{
+ return contains_str( word,
+ L"exec",
+ L"case",
+ L"break",
+ L"return",
+ L"continue",
+ 0 );
+}
+
+static const wchar_t *parser_find_end( const wchar_t * buff )
+{
+ tokenizer tok;
+ int had_cmd=0;
+ int count = 0;
+ int error=0;
+ int mark=0;
+
+ for( tok_init( &tok, buff, 0 );
+ tok_has_next( &tok ) && !error;
+ tok_next( &tok ) )
+ {
+ int last_type = tok_last_type( &tok );
+ switch( last_type )
+ {
+ case TOK_STRING:
+ {
+ if( !had_cmd )
+ {
+ if( wcscmp(tok_last(&tok), L"end")==0)
+ {
+ count--;
+ }
+ else if( parser_is_block( tok_last(&tok) ) )
+ {
+ count++;
+ }
+
+ if( count < 0 )
+ {
+ error = 1;
+ }
+ had_cmd = 1;
+ }
+ break;
+ }
+
+ case TOK_END:
+ {
+ had_cmd = 0;
+ break;
+ }
+
+ case TOK_PIPE:
+ case TOK_BACKGROUND:
+ {
+ if( had_cmd )
+ {
+ had_cmd = 0;
+ }
+ else
+ {
+ error = 1;
+ }
+ break;
+
+ }
+
+ case TOK_ERROR:
+ error = 1;
+ break;
+
+ default:
+ break;
+
+ }
+ if(!count)
+ {
+ tok_next( &tok );
+ mark = tok_get_pos( &tok );
+ break;
+ }
+
+ }
+
+ tok_destroy( &tok );
+ if(!count && !error){
+
+ return buff+mark;
+ }
+ return 0;
+
+}
+
+wchar_t *parser_cdpath_get( wchar_t *dir )
+{
+ wchar_t *res = 0;
+
+ if( !dir )
+ return 0;
+
+
+ if( dir[0] == L'/' )
+ {
+ struct stat buf;
+ if( wstat( dir, &buf ) == 0 )
+ {
+ if( S_ISDIR(buf.st_mode) )
+ {
+ res = wcsdup( dir );
+ }
+ }
+ }
+ else
+ {
+ wchar_t *path = env_get(L"CDPATH");
+
+ if( path == 0 )
+ {
+ path = L".";
+ }
+
+ wchar_t *path_cpy = wcsdup( path );
+ wchar_t *nxt_path = path;
+ wchar_t *state;
+ wchar_t *whole_path;
+
+ if( (path_cpy==0) )
+ {
+ die_mem();
+ }
+
+ for( nxt_path = wcstok( path_cpy, ARRAY_SEP_STR, &state );
+ nxt_path != 0;
+ nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) )
+ {
+ wchar_t *expanded_path = expand_tilde( wcsdup(nxt_path) );
+
+// debug( 2, L"woot %ls\n", expanded_path );
+
+ int path_len = wcslen( expanded_path );
+ if( path_len == 0 )
+ {
+ free(expanded_path );
+ continue;
+ }
+
+ whole_path =
+ wcsdupcat2( expanded_path,
+ ( expanded_path[path_len-1] != L'/' )?L"/":L"",
+ dir, 0 );
+
+ free(expanded_path );
+
+ struct stat buf;
+ if( wstat( whole_path, &buf ) == 0 )
+ {
+ if( S_ISDIR(buf.st_mode) )
+ {
+ res = whole_path;
+ break;
+ }
+ }
+ free( whole_path );
+
+ }
+ free( path_cpy );
+ }
+ return res;
+}
+
+
+void parser_forbid_function( wchar_t *function )
+{
+/*
+ if( function )
+ debug( 2, L"Forbid %ls\n", function );
+*/
+ al_push( &forbidden_function, function?wcsdup(function):0 );
+}
+
+void parser_allow_function()
+{
+/*
+ if( al_peek( &forbidden_function) )
+ debug( 2, L"Allow %ls\n", al_peek( &forbidden_function) );
+*/
+ free( (void *) al_pop( &forbidden_function ) );
+}
+
+void error( int ec, const wchar_t *str, int p )
+{
+ error_code = ec;
+ wcsncpy( err_str, str, 256 );
+ err_pos = p;
+}
+
+/**
+ Wrapper for the error function, which sets the error string to "ec 'ea'".
+*/
+static void error_arg( int ec, const wchar_t *es, const wchar_t *ea, int p )
+{
+ wchar_t *msg = wcsdupcat2( es, L" \'", ea, L"\'", 0 );
+ error( ec, msg, p );
+ free(msg);
+}
+
+wchar_t *get_filename( const wchar_t *cmd )
+{
+ wchar_t *path;
+
+ if(wcschr( cmd, '/' ) != 0 )
+ {
+ if( waccess( cmd, X_OK )==0 )
+ {
+ struct stat buff;
+ wstat( cmd, &buff );
+ if( S_ISREG(buff.st_mode) )
+ return wcsdup( cmd );
+ else
+ return 0;
+ }
+ }
+ else
+ {
+ path = env_get(L"PATH");
+ if( path != 0 )
+ {
+ /*
+ Allocate string long enough to hold the whole command
+ */
+ wchar_t *new_cmd = malloc( sizeof(wchar_t)*(wcslen(cmd)+wcslen(path)+2) );
+ /*
+ We tokenize a copy of the path, since strtok modifies
+ its arguments
+ */
+ wchar_t *path_cpy = wcsdup( path );
+ wchar_t *nxt_path = path;
+ wchar_t *state;
+
+ if( (new_cmd==0) || (path_cpy==0) )
+ {
+ die_mem();
+
+ }
+
+ for( nxt_path = wcstok( path_cpy, ARRAY_SEP_STR, &state );
+ nxt_path != 0;
+ nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) )
+ {
+ int path_len = wcslen( nxt_path );
+ wcscpy( new_cmd, nxt_path );
+ if( new_cmd[path_len-1] != '/' )
+ {
+ new_cmd[path_len++]='/';
+ }
+ wcscpy( &new_cmd[path_len], cmd );
+ if( waccess( new_cmd, X_OK )==0 )
+ {
+ struct stat buff;
+ if( wstat( new_cmd, &buff )==-1 )
+ {
+ if( errno != EACCES )
+ wperror( L"stat" );
+ continue;
+ }
+ if( S_ISREG(buff.st_mode) )
+ {
+ free( path_cpy );
+ return new_cmd;
+ }
+ }
+ else
+ {
+ switch( errno )
+ {
+ case ENOENT:
+ case ENAMETOOLONG:
+ case EACCES:
+ case ENOTDIR:
+ break;
+ default:
+ debug( 1,
+ L"Error while searching for command %d",
+ new_cmd );
+ wperror( L"access" );
+ }
+ }
+ }
+ free( path_cpy );
+ free( new_cmd );
+ }
+ }
+
+ return 0;
+}
+
+void parser_init()
+{
+ if( profile )
+ {
+ al_init( &profile_data);
+ }
+ al_init( &forbidden_function );
+}
+
+void print_profile( array_list_t *p,
+ int pos,
+ FILE *out )
+{
+ profile_element_t *me, *prev;
+ int i;
+ int my_time;
+
+ if( pos >= al_get_count( p ) )
+ return;
+
+ me= (profile_element_t *)al_get( p, pos );
+ if( !me->skipped )
+ {
+ my_time=me->parse+me->exec;
+
+ for( i=pos+1; i<al_get_count(p); i++ )
+ {
+ prev = (profile_element_t *)al_get( p, i );
+ if( prev->skipped )
+ continue;
+
+ if( prev->level <= me->level )
+ break;
+ if( prev->level > me->level+1 )
+ continue;
+ my_time -= prev->parse;
+ my_time -= prev->exec;
+ }
+
+ if( me->cmd )
+ {
+ fwprintf( out, L"%d\t%d\t", my_time, me->parse+me->exec );
+ for( i=0; i<me->level; i++ )
+ {
+ fwprintf( out, L"-" );
+ }
+ fwprintf( out, L"> %ls\n", me->cmd );
+ }
+ }
+ print_profile( p, pos+1, out );
+ free( me->cmd );
+ free( me );
+}
+
+void parser_destroy()
+{
+ if( profile )
+ {
+ /*
+ Save profiling information
+ */
+ FILE *f = fopen( profile, "w" );
+ if( !f )
+ {
+ debug( 1,
+ L"Could not write profiling information to file '%s'",
+ profile );
+ }
+ else
+ {
+ fwprintf( f,
+ L"Time\tSum\tCommand\n",
+ al_get_count( &profile_data ) );
+ print_profile( &profile_data, 0, f );
+ fclose( f );
+ }
+ al_destroy( &profile_data );
+ }
+
+ al_destroy( &forbidden_function );
+}
+
+static void print_errors()
+{
+ if( error_code )
+ {
+ int tmp;
+
+
+ debug( 0, L"%ls", err_str );
+
+ tmp = current_tokenizer_pos;
+ current_tokenizer_pos = err_pos;
+
+ fwprintf( stderr, L"%ls", parser_current_line() );
+
+ current_tokenizer_pos=tmp;
+ }
+}
+
+int eval_args( const wchar_t *line, array_list_t *args )
+{
+ tokenizer tok;
+ /*
+ eval_args may be called while evaulating another command, so we
+ save the previous tokenizer and restore it on exit
+ */
+ tokenizer *previous_tokenizer=current_tokenizer;
+ int previous_pos=current_tokenizer_pos;
+ int do_loop=1;
+ tok_init( &tok, line, 0 );
+
+ current_tokenizer=&tok;
+
+ error_code=0;
+
+ for(;do_loop && tok_has_next( &tok) ; tok_next( &tok ) )
+ {
+ current_tokenizer_pos = tok_get_pos( &tok );
+ switch(tok_last_type( &tok ) )
+ {
+ case TOK_STRING:
+ if( !expand_string( wcsdup(tok_last( &tok )), args, 0 ) )
+ {
+ err_pos=tok_get_pos( &tok );
+ do_loop=0;
+ }
+
+ break;
+ case TOK_END:
+ break;
+
+ case TOK_ERROR:
+ {
+ error_arg( SYNTAX_ERROR,
+ TOK_ERR_MSG,
+ tok_last(&tok),
+ tok_get_pos( &tok ) );
+
+ do_loop=0;
+ break;
+ }
+
+ default:
+ error_arg( SYNTAX_ERROR,
+ L"Unexpected token of type",
+ tok_get_desc( tok_last_type(&tok)),
+ tok_get_pos( &tok ) );
+ do_loop=0;
+ break;
+
+ }
+ }
+
+ print_errors();
+ tok_destroy( &tok );
+
+ current_tokenizer=previous_tokenizer;
+ current_tokenizer_pos = previous_pos;
+
+ return 1;
+}
+
+wchar_t *parser_current_line()
+{
+ int lineno=1;
+
+ wchar_t *file = reader_current_filename();
+ wchar_t *whole_str = tok_string( current_tokenizer );
+ wchar_t *line = whole_str;
+ wchar_t *line_end;
+ int i;
+ int offset;
+ int current_line_pos=current_tokenizer_pos;
+
+ if( !line )
+ return L"";
+
+ lineinfo[0]=0;
+
+ /*
+ Calculate line number, line offset, etc.
+ */
+ for( i=0; i<current_tokenizer_pos; i++ )
+ {
+ if( whole_str[i] == L'\n' )
+ {
+ lineno++;
+ current_line_pos = current_tokenizer_pos-i-1;
+ line = &whole_str[i+1];
+ }
+ }
+
+ /*
+ Copy current line from whole string
+ */
+ line_end = wcschr( line, L'\n' );
+ if( !line_end )
+ line_end = line+wcslen(line);
+
+ line = wcsndup( line, line_end-line );
+
+// debug( 2, L"Current pos %d, line pos %d, file_length %d\n", current_tokenizer_pos, current_line_pos, wcslen(whole_str));
+
+ if( !is_interactive )
+ {
+ swprintf( lineinfo,
+ LINEINFO_SIZE,
+ L"%ls (line %d): %n",
+ file,
+ lineno,
+ &offset );
+ }
+ else
+ {
+ offset=0;
+ }
+
+ /* Skip printing if we are in interactive mode and the error was on the first character of the line */
+ if( offset+current_line_pos )
+ swprintf( lineinfo+offset,
+ LINEINFO_SIZE-offset,
+ L"%ls\n%*c^\n",
+ line,
+ offset+current_line_pos,
+ L' ' );
+
+ free( line );
+
+ return lineinfo;
+}
+
+int parser_get_pos()
+{
+ return tok_get_pos( current_tokenizer );
+}
+
+int parser_get_job_pos()
+{
+ return job_start_pos;
+}
+
+
+void parser_set_pos( int p)
+{
+ tok_set_pos( current_tokenizer, p );
+}
+
+const wchar_t *parser_get_buffer()
+{
+ return tok_string( current_tokenizer );
+}
+
+
+int parser_is_help( wchar_t *s, int min_match )
+{
+ int len = wcslen(s);
+
+ return ( wcscmp( L"-h", s ) == 0 ) ||
+ ( len >= 3 && (wcsncmp( L"--help", s, len ) == 0) );
+}
+
+/**
+ Parse options for the specified job
+
+ \param p the process to parse options for
+ \param j the job to which the process belongs to
+ \param tok the tokenizer to read options from
+ \param args the argument list to insert options into
+*/
+static void parse_job_main_loop( process_t *p,
+ job_t *j,
+ tokenizer *tok,
+ array_list_t *args )
+{
+ int is_finished=0;
+
+ int proc_is_count=0;
+
+ /*
+ Test if this is the 'count' command. We need to special case
+ count, since it should display a help message on 'count .h',
+ but not on 'set foo -h; count $foo'.
+ */
+ if( p->actual_cmd )
+ {
+ wchar_t *woot = wcsrchr( p->actual_cmd, L'/' );
+ if( !woot )
+ woot = p->actual_cmd;
+ else
+ woot++;
+ proc_is_count = wcscmp( woot, L"count" )==0;
+ }
+
+ while( 1 )
+ {
+
+ /* debug( 2, L"Read token %ls\n", wcsdup(tok_last( tok )) ); */
+
+ switch( tok_last_type( tok ) )
+ {
+ case TOK_PIPE:
+ if( (p->type == INTERNAL_EXEC) )
+ {
+ error( SYNTAX_ERROR,
+ EXEC_ERR_MSG,
+ tok_get_pos( tok ) );
+ return;
+ }
+
+ p->argv = list_to_char_arr( args );
+ p->next = calloc( 1, sizeof( process_t ) );
+ if( p->next == 0 )
+ {
+ die_mem();
+
+ }
+ tok_next( tok );
+ if( !parse_job( p->next, j, tok ))
+ {
+ /*
+ Do not free args content on error - it is
+ already in p->argv and will be freed by job_free
+ later on.
+ */
+ al_truncate( args, 0 );
+ }
+ is_finished = 1;
+ break;
+
+ case TOK_BACKGROUND:
+ j->fg = 0;
+ case TOK_END:
+ {
+ p->argv = list_to_char_arr( args );
+ if( tok_has_next(tok))
+ tok_next(tok);
+
+ is_finished = 1;
+
+ break;
+ }
+
+ case TOK_STRING:
+ {
+ int skip=0;
+
+ if( current_block->skip )
+ {
+ skip=1;
+ if( (current_block->type == SWITCH) &&
+ (wcscmp( al_get( args, 0), L"case" )==0) &&
+ p->type )
+ {
+ skip=0;
+ }
+
+ }
+
+ if( !skip )
+ {
+ if( proc_is_count &&
+ (al_get_count( args) == 1) &&
+ ( parser_is_help( tok_last(tok), 0) ) )
+ {
+ /*
+ Display help for count
+ */
+ p->type = INTERNAL_BUILTIN;
+ wcscpy( p->actual_cmd, L"count" );
+ }
+
+ if( !expand_string( wcsdup(tok_last( tok )),
+ args,
+ 0 )
+ )
+ {
+ err_pos=tok_get_pos( tok );
+ if( error_code == 0 )
+ {
+ error_arg( SYNTAX_ERROR,
+ L"Could not expand string",
+ tok_last(tok),
+ tok_get_pos( tok ) );
+ }
+
+ }
+ }
+
+ break;
+ }
+
+ case TOK_REDIRECT_OUT:
+ case TOK_REDIRECT_IN:
+ case TOK_REDIRECT_APPEND:
+ case TOK_REDIRECT_FD:
+ {
+ int type = tok_last_type( tok );
+ io_data_t *new_io;
+ wchar_t *target = 0;
+
+
+
+ /*
+ Don't check redirections in skipped part
+
+ Otherwise, bogus errors may be the result
+ */
+ if( current_block->skip )
+ {
+ tok_next( tok );
+ break;
+ }
+
+ new_io = calloc( 1, sizeof(io_data_t) );
+ if( !new_io )
+ die_mem();
+
+ new_io->fd = wcstol( tok_last( tok ),
+ 0,
+ 10 );
+ tok_next( tok );
+
+ switch( tok_last_type( tok ) )
+ {
+ case TOK_STRING:
+ {
+ target = expand_one( wcsdup( tok_last( tok ) ), 0);
+ if( target == 0 && error_code == 0 )
+ {
+ error_arg( SYNTAX_ERROR,
+ L"Could not expand string",
+ tok_last( tok ),
+ tok_get_pos( tok ) );
+
+ }
+ }
+ break;
+ default:
+ error_arg( SYNTAX_ERROR,
+ L"Expected redirection, got token of type",
+ tok_get_desc( tok_last_type(tok)),
+ tok_get_pos( tok ) );
+ }
+
+ if( target == 0 || wcslen( target )==0 )
+ {
+ if( error_code == 0 )
+ error( SYNTAX_ERROR,
+ L"Invalid IO redirection",
+ tok_get_pos( tok ) );
+ tok_next(tok);
+ }
+ else
+ {
+
+
+ switch( type )
+ {
+ case TOK_REDIRECT_APPEND:
+ new_io->io_mode = IO_FILE;
+ new_io->flags = O_CREAT | O_APPEND | O_WRONLY;
+ new_io->filename = target;
+ break;
+
+ case TOK_REDIRECT_OUT:
+ new_io->io_mode = IO_FILE;
+ new_io->flags = O_CREAT | O_WRONLY | O_TRUNC;
+ new_io->filename = target;
+ break;
+
+ case TOK_REDIRECT_IN:
+ new_io->io_mode = IO_FILE;
+ new_io->flags = O_RDONLY;
+ new_io->filename = target;
+ break;
+
+ case TOK_REDIRECT_FD:
+ if( wcscmp( target, L"-" ) == 0 )
+ {
+ new_io->io_mode = IO_CLOSE;
+ }
+ else
+ {
+ new_io->io_mode = IO_FD;
+ new_io->old_fd = wcstol( target,
+ 0,
+ 10 );
+ if( ( new_io->old_fd < 0 ) ||
+ ( new_io->old_fd > 10 ) )
+ {
+ error_arg( SYNTAX_ERROR,
+ L"Requested redirection to something "
+ L"that is not a file descriptor",
+ target,
+ tok_get_pos( tok ) );
+ tok_next(tok);
+ }
+ free(target);
+ }
+ break;
+ }
+ }
+
+ j->io = io_add( j->io, new_io );
+
+ }
+ break;
+
+ case TOK_ERROR:
+ {
+ error_arg( SYNTAX_ERROR,
+ TOK_ERR_MSG,
+ tok_last(tok),
+ tok_get_pos( tok ) );
+
+ return;
+ }
+
+ default:
+ error_arg( SYNTAX_ERROR,
+ L"Unexpected token of type",
+ tok_get_desc( tok_last_type(tok)),
+ tok_get_pos( tok ) );
+ tok_next(tok);
+ break;
+ }
+
+ if( (is_finished) || (error_code != 0) )
+ break;
+
+ tok_next( tok );
+ }
+ return;
+}
+
+
+/**
+ Fully parse a single job. Does not call exec on it.
+
+ \param p The process structure that should be used to represent the first process in the job.
+ \param j The job structure to contain the parsed job
+ \param tok tokenizer to read from
+
+ \return 1 on success, 0 on error
+*/
+static int parse_job( process_t *p,
+ job_t *j,
+ tokenizer *tok )
+{
+ array_list_t args; // The list that will become the argc array for the program
+ int use_function = 1; // May functions be considered when checking what action this command represents
+ int use_builtin = 1; // May builtins be considered when checking what action this command represents
+ int is_new_block=0; // Does this command create a new block?
+
+ block_t *prev_block = current_block;
+
+// debug( 2, L"begin parse_job()\n" );
+ al_init( &args );
+
+ current_tokenizer_pos = tok_get_pos( tok );
+
+ while( al_get_count( &args ) == 0 )
+ {
+ wchar_t *nxt=0;
+ switch( tok_last_type( tok ))
+ {
+ case TOK_STRING:
+ nxt = expand_one( wcsdup(tok_last( tok )),
+ EXPAND_SKIP_SUBSHELL | EXPAND_SKIP_VARIABLES);
+ if( nxt == 0 )
+ {
+ error_arg( SYNTAX_ERROR,
+ L"Illegal command name ",
+ tok_last( tok ),
+ tok_get_pos( tok ) );
+ al_destroy( &args );
+ return 0;
+ }
+ break;
+
+ case TOK_ERROR:
+ {
+ error_arg( SYNTAX_ERROR,
+ TOK_ERR_MSG,
+ tok_last(tok),
+ tok_get_pos( tok ) );
+
+ al_destroy( &args );
+ return 0;
+ }
+
+ default:
+ error_arg( SYNTAX_ERROR,
+ L"Expected a command name, got token of type ",
+ tok_get_desc( tok_last_type(tok)),
+ tok_get_pos( tok ) );
+ al_destroy( &args );
+ return 0;
+ }
+
+ int mark = tok_get_pos( tok );
+
+ if( wcscmp( L"command", nxt )==0 )
+ {
+ tok_next( tok );
+ if( parser_is_help( tok_last( tok ), 0 ) )
+ {
+ tok_set_pos( tok, mark);
+ }
+ else
+ {
+ use_function = 0;
+ use_builtin=0;
+ free( nxt );
+ continue;
+ }
+ }
+ else if( wcscmp( L"builtin", nxt )==0 )
+ {
+ tok_next( tok );
+ if( tok_last(tok)[0] == L'-' )
+ {
+ tok_set_pos( tok, mark);
+ }
+ else
+ {
+ use_function = 0;
+ free( nxt );
+ continue;
+ }
+ }
+ else if( wcscmp( L"not", nxt )==0 )
+ {
+ tok_next( tok );
+ if( tok_last(tok)[0] == L'-' )
+ {
+ tok_set_pos( tok, mark);
+ }
+ else
+ {
+ j->negate=1-j->negate;
+ free( nxt );
+ continue;
+ }
+ }
+ else if( wcscmp( L"and", nxt )==0 )
+ {
+ tok_next( tok );
+ if( tok_last(tok)[0] == L'-' )
+ {
+ tok_set_pos( tok, mark);
+ }
+ else
+ {
+ parser_push_block( AND );
+ free( nxt );
+ continue;
+ }
+ }
+ else if( wcscmp( L"or", nxt )==0 )
+ {
+ tok_next( tok );
+ if( tok_last(tok)[0] == L'-' )
+ {
+ tok_set_pos( tok, mark);
+ }
+ else
+ {
+ parser_push_block( OR );
+ free( nxt );
+ continue;
+ }
+ }
+ else if( wcscmp( L"exec", nxt )==0 )
+ {
+ if( p != j->first_process )
+ {
+ error( SYNTAX_ERROR,
+ EXEC_ERR_MSG,
+ tok_get_pos( tok ) );
+ al_destroy( &args );
+ free(nxt);
+ return 0;
+ }
+
+ tok_next( tok );
+ if( tok_last(tok)[0] == L'-' )
+ {
+ tok_set_pos( tok, mark);
+ }
+ else
+ {
+ use_function = 0;
+ use_builtin=0;
+ p->type=INTERNAL_EXEC;
+ free( nxt );
+ continue;
+ }
+ }
+ else if( wcscmp( L"while", nxt ) ==0 )
+ {
+ int new_block = 0;
+ tok_next( tok );
+
+ if( (current_block->type != WHILE) )
+ {
+ new_block = 1;
+ }
+ else if( current_block->while_state == WHILE_TEST_AGAIN )
+ {
+ current_block->while_state = WHILE_TEST_FIRST;
+ }
+ else
+ {
+ new_block = 1;
+ }
+
+ if( new_block )
+ {
+ parser_push_block( WHILE );
+ current_block->while_state=WHILE_TEST_FIRST;
+ current_block->tok_pos = mark;
+ }
+
+ free( nxt );
+ is_new_block=1;
+
+ continue;
+ }
+ else if( wcscmp( L"if", nxt ) ==0 )
+ {
+ tok_next( tok );
+
+ parser_push_block( IF );
+
+ current_block->if_state=0;
+ current_block->tok_pos = mark;
+
+ free( nxt );
+ is_new_block=1;
+ continue;
+ }
+
+ if( use_function)
+ {
+ int nxt_forbidden;
+ wchar_t *forbid;
+
+ forbid = (wchar_t *)(al_get_count( &forbidden_function)?al_peek( &forbidden_function ):0);
+ nxt_forbidden = forbid && (wcscmp( forbid, nxt) == 0 );
+
+ /*
+ Make feeble attempt to avoid infinite recursion. Will at
+ least catch some accidental infinite recursion calls.
+ */
+ if( function_exists( nxt ) && !nxt_forbidden)
+ {
+ /*
+ Check if we have reached the maximum recursion depth
+ */
+ if( al_get_count( &forbidden_function ) > MAX_RECURSION_DEPTH )
+ {
+ error( SYNTAX_ERROR,
+ RECURSION_ERR_MSG,
+ tok_get_pos( tok ) );
+ }
+ else
+ {
+ p->type = INTERNAL_FUNCTION;
+ }
+ }
+ }
+ al_push( &args, nxt );
+ }
+
+ if( error_code == 0 )
+ {
+ if( !p->type )
+ {
+ if( use_builtin &&
+ builtin_exists( (wchar_t *)al_get( &args, 0 ) ) )
+ {
+ p->type = INTERNAL_BUILTIN;
+ is_new_block = parser_is_block( (wchar_t *)al_get( &args, 0 ) );
+ }
+ }
+
+ if( !p->type || (p->type == INTERNAL_EXEC) )
+ {
+ /*
+ If we are not executing the current block, allow
+ non-existent commands.
+ */
+ if( current_block->skip )
+ {
+ p->actual_cmd = wcsdup(L"");
+ }
+ else
+ {
+
+ p->actual_cmd = get_filename( (wchar_t *)al_get( &args, 0 ) );
+ /*
+ Check if the specified command exists
+ */
+ if( p->actual_cmd == 0 )
+ {
+
+ /*
+ That is not a command! Test if it is a
+ directory, in which case, we use 'cd' as the
+ implicit command.
+ */
+ wchar_t *pp =
+ parser_cdpath_get( (wchar_t *)al_get( &args, 0 ) );
+ if( pp )
+ {
+ wchar_t *tmp;
+ free( pp );
+
+ tmp = (wchar_t *)al_get( &args, 0 );
+ al_truncate( &args, 0 );
+ al_push( &args, wcsdup( L"cd" ) );
+ al_push( &args, tmp );
+ /*
+ If we have defined a wrapper around cd, use it,
+ otherwise use the cd builtin
+ */
+ if( function_exists( L"cd" ) )
+ p->type = INTERNAL_FUNCTION;
+ else
+ p->type = INTERNAL_BUILTIN;
+ }
+ else
+ {
+ error_arg( EVAL_ERROR,
+ L"Unknown command",
+ (wchar_t *)al_get( &args, 0 ),
+ tok_get_pos( tok ) );
+ }
+ }
+ }
+ }
+ }
+
+ if( is_new_block )
+ {
+ const wchar_t *end=parser_find_end( tok_string( tok ) +
+ current_tokenizer_pos );
+ tokenizer subtok;
+ int make_sub_block = j->first_process != p;
+
+ if( !end )
+ {
+ error( SYNTAX_ERROR,
+ L"Could not find end of block" ,
+ tok_get_pos( tok ) );
+ }
+
+ if( !make_sub_block )
+ {
+ tok_init( &subtok, end, 0 );
+ switch( tok_last_type( &subtok ) )
+ {
+ case TOK_END:
+ break;
+
+ case TOK_REDIRECT_OUT:
+ case TOK_REDIRECT_APPEND:
+ case TOK_REDIRECT_IN:
+ case TOK_REDIRECT_FD:
+ case TOK_PIPE:
+ {
+ make_sub_block = 1;
+ break;
+ }
+
+ default:
+ {
+ error_arg( SYNTAX_ERROR,
+ L"Expected end of command, got token of type ",
+ tok_get_desc( tok_last_type(tok)),
+ tok_get_pos( tok ) );
+ }
+ }
+ tok_destroy( &subtok );
+ }
+
+ if( make_sub_block )
+ {
+
+ int end_pos = end-tok_string( tok );
+ wchar_t *sub_block= wcsndup( tok_string( tok ) + current_tokenizer_pos,
+ end_pos - current_tokenizer_pos);
+
+ p->type = INTERNAL_BLOCK;
+ free( (void *)al_get( &args, 0 ) );
+ al_set( &args, 0, sub_block );
+
+ tok_set_pos( tok,
+ end_pos );
+
+ while( prev_block != current_block )
+ parser_pop_block();
+ }
+ else tok_next( tok );
+
+ }
+ else tok_next( tok );
+
+ if( !error_code )
+ parse_job_main_loop( p, j, tok, &args );
+
+ if( error_code )
+ {
+ /*
+ We don't know what the state of the args array and the argv
+ vector is on error, so we do an internal cleanup here.
+ */
+ al_foreach( &args,
+ (void (*)(const void *))&free );
+ free(p->argv);
+ p->argv=0;
+ /*
+ Make sure the block stack is consistent
+ */
+ while( prev_block != current_block )
+ parser_pop_block();
+
+ }
+ al_destroy( &args );
+
+// debug( 2, L"end parse_job()\n" );
+ return !error_code;
+}
+
+/**
+ Do skipped execution of command. This means that only limited
+ execution of block level commands such as end and switch should be
+ preformed.
+
+ \param j the job to execute
+
+*/
+static void skipped_exec( job_t * j )
+{
+ process_t *p;
+ for( p = j->first_process; p; p=p->next )
+ {
+ if( p->type )
+ {
+ if(( wcscmp( p->argv[0], L"for" )==0) ||
+ ( wcscmp( p->argv[0], L"switch" )==0) ||
+ ( wcscmp( p->argv[0], L"function" )==0))
+ {
+ parser_push_block( FAKE );
+ }
+ else if( wcscmp( p->argv[0], L"end" )==0)
+ {
+ if(!current_block->outer->skip )
+ {
+ exec( j );
+ return;
+ }
+ parser_pop_block();
+ }
+ else if( wcscmp( p->argv[0], L"else" )==0)
+ {
+ if( (current_block->type == IF ) &&
+ (current_block->if_state != 0))
+ {
+ exec( j );
+ return;
+ }
+ }
+ else if( wcscmp( p->argv[0], L"case" )==0)
+ {
+ if( (current_block->type == SWITCH ) )
+ {
+ exec( j );
+ return;
+ }
+ }
+ }
+ }
+ job_free( j );
+}
+
+/**
+ Evaluates a job from the specified tokenizer. First calls
+ parse_job to parse the job and then calls exec to execute it.
+
+ \param tok The tokenizer to read tokens from
+*/
+
+static void eval_job( tokenizer *tok )
+{
+ job_t *j;
+
+ int start_pos = job_start_pos = tok_get_pos( tok );
+ debug( 2, L"begin eval_job()\n" );
+ long long t1=0, t2=0, t3=0;
+ profile_element_t *p=0;
+ int skip = 0;
+
+ if( !is_block && !is_subshell )
+ env_universal_read_all();
+
+ if( profile )
+ {
+ p=malloc( sizeof(profile_element_t));
+ p->cmd=0;
+ al_push( &profile_data, p );
+ p->skipped=1;
+ t1 = get_time();
+ }
+
+ switch( tok_last_type( tok ) )
+ {
+ case TOK_STRING:
+ {
+ j = job_create();
+ j->command=0;
+ j->fg=1;
+ j->constructed=0;
+ j->skip_notification = is_subshell;
+
+ if( is_interactive )
+ {
+ if( tcgetattr (0, &j->tmodes) )
+ {
+ tok_next( tok );
+ wperror( L"tcgetattr" );
+ job_free( j );
+ break;
+ }
+ }
+
+ j->first_process = calloc( 1, sizeof( process_t ) );
+
+ /* Copy the command name */
+ if( current_block->type == OR )
+ {
+ skip = (proc_get_last_status() == 0 );
+ parser_pop_block();
+ }
+ else if( current_block->type == AND )
+ {
+ skip = (proc_get_last_status() != 0 );
+ parser_pop_block();
+ }
+
+
+ if( parse_job( j->first_process, j, tok ) &&
+ j->first_process->argv )
+ {
+ if( job_start_pos < tok_get_pos( tok ) )
+ {
+ int stop_pos = tok_get_pos( tok );
+ wchar_t *newline = wcschr( tok_string(tok)+start_pos,
+ L'\n' );
+ if( newline )
+ stop_pos = mini( stop_pos, newline - tok_string(tok) );
+
+ j->command = wcsndup( tok_string(tok)+start_pos,
+ stop_pos-start_pos );
+ }
+ else
+ j->command = wcsdup( L"" );
+
+ if( profile )
+ {
+ t2 = get_time();
+ p->cmd = wcsdup( j->command );
+ p->skipped=current_block->skip;
+ }
+
+ skip |= current_block->skip;
+
+ if(!skip )
+ {
+ exec( j );
+ }
+ else
+ {
+ skipped_exec( j );
+ }
+
+ if( profile )
+ {
+ t3 = get_time();
+ p->level=eval_level;
+ p->parse = t2-t1;
+ p->exec=t3-t2;
+ }
+
+ if( current_block->type == WHILE )
+ {
+// debug( 2, L"We are at begining of a while block\n" );
+
+ switch( current_block->while_state )
+ {
+ case WHILE_TEST_FIRST:
+ {
+ current_block->skip = proc_get_last_status()!= 0;
+ current_block->while_state=WHILE_TESTED;
+ }
+ break;
+ }
+ }
+
+ if( current_block->type == IF )
+ {
+ if( (!current_block->if_state) &&
+ (!current_block->skip) )
+ {
+ /*
+ We need to call job_do_notification,
+ since this is the function which sets
+ the status of the last process to exit
+ */
+// debug( 2, L"Result of if block is %d\n", proc_get_last_status() );
+
+ current_block->skip = proc_get_last_status()!= 0;
+ current_block->if_state++;
+ }
+ }
+
+ }
+ else
+ {
+ job_free( j );
+ }
+ break;
+ }
+
+ case TOK_END:
+ {
+ if( tok_has_next( tok ))
+ tok_next( tok );
+ break;
+ }
+
+ case TOK_ERROR:
+ {
+ error_arg( SYNTAX_ERROR,
+ TOK_ERR_MSG,
+ tok_last(tok),
+ tok_get_pos( tok ) );
+
+ return;
+ }
+
+ default:
+ {
+ error_arg( SYNTAX_ERROR,
+ L"Expected a command string, got token of type",
+ tok_get_desc( tok_last_type(tok)),
+ tok_get_pos( tok ) );
+
+ return;
+ }
+ }
+
+ if( is_subshell )
+ job_do_notification();
+// debug( 2, L"end eval_job()\n" );
+}
+
+int eval( const wchar_t *cmd, io_data_t *io, int block_type )
+{
+ int forbid_count;
+ int code;
+ tokenizer *previous_tokenizer=current_tokenizer;
+ block_t *start_current_block = current_block;
+ io_data_t *prev_io = block_io;
+ block_io = io;
+
+ debug( 2, L"Eval command %ls", cmd );
+
+ if( !cmd )
+ {
+ debug( 1,
+ L"Tried to evaluate null pointer\n"
+ L"If this error can be reproduced, please file a bug report." );
+ return 1;
+ }
+
+ if( (block_type!=TOP) &&
+ (block_type != FUNCTION_CALL) &&
+ (block_type != SUBST))
+ {
+ debug( 1,
+ L"Tried to evaluate buffer using invalid block scope of type '%ls'\n"
+ L"If this error can be reproduced, please file a bug report.",
+ parser_get_block_desc( block_type ) );
+ return 1;
+ }
+
+
+ eval_level++;
+ current_tokenizer = malloc( sizeof(tokenizer));
+
+ parser_push_block( block_type );
+
+ forbid_count = al_get_count( &forbidden_function );
+
+ tok_init( current_tokenizer, cmd, 0 );
+ error_code = 0;
+
+ while( tok_has_next( current_tokenizer ) &&
+ !error_code &&
+ !sanity_check() &&
+ !exit_status() )
+ eval_job( current_tokenizer );
+
+ int prev_block_type = current_block->type;
+ parser_pop_block();
+
+ while( start_current_block != current_block )
+ {
+ if( current_block == 0 )
+ {
+ debug( 0,
+ L"End of block mismatch\n"
+ L"Program terminating. If this error can be reproduced,\n"
+ L"please file a bug report." );
+ exit(1);
+ break;
+ }
+
+ if( (!error_code) && (!exit_status()) && (!proc_get_last_status()) )
+ {
+ char *h;
+
+ //debug( 2, L"Status %d\n", proc_get_last_status() );
+
+ switch( prev_block_type )
+ {
+ case OR:
+ case AND:
+ debug( 1,
+ COND_ERR_MSG );
+ fwprintf( stderr, L"%ls", parser_current_line() );
+
+ h = builtin_help_get( prev_block_type == OR? L"or": L"and" );
+ if( h )
+ fwprintf( stderr, L"%s", h );
+ break;
+
+ default:
+ debug( 1,
+ L"%ls", parser_get_block_desc( current_block->type ) );
+ debug( 1,
+ END_ERR_MSG );
+ fwprintf( stderr, L"%ls", parser_current_line() );
+
+ h = builtin_help_get( L"end" );
+ if( h )
+ fwprintf( stderr, L"%s", h );
+ break;
+ }
+
+ }
+ prev_block_type = current_block->type;
+ parser_pop_block();
+ }
+
+ print_errors();
+
+ tok_destroy( current_tokenizer );
+ free( current_tokenizer );
+
+ while( forbid_count < al_get_count( &forbidden_function ))
+ parser_allow_function();
+
+ current_tokenizer=previous_tokenizer;
+
+ code=error_code;
+ error_code=0;
+
+ block_io = prev_io;
+
+ eval_level--;
+
+ return code;
+}
+
+
+int parser_test( wchar_t * buff,
+ int babble )
+{
+ tokenizer tok;
+ int had_cmd=0;
+ int count = 0;
+ int err=0;
+ tokenizer *previous_tokenizer=current_tokenizer;
+ int previous_pos=current_tokenizer_pos;
+ static int block_pos[BLOCK_MAX_COUNT];
+ static int block_type[BLOCK_MAX_COUNT];
+ int is_pipeline = 0;
+ int forbid_pipeline = 0;
+ int needs_cmd=0;
+ int require_additional_commands=0;
+
+ current_tokenizer = &tok;
+
+ for( tok_init( &tok, buff, 0 );
+ tok_has_next( &tok ) && !err;
+ tok_next( &tok ) )
+ {
+ current_tokenizer_pos = tok_get_pos( &tok );
+
+ int last_type = tok_last_type( &tok );
+ switch( last_type )
+ {
+ case TOK_STRING:
+ {
+ if( !had_cmd )
+ {
+ int mark = tok_get_pos( &tok );
+ had_cmd = 1;
+
+ if( require_additional_commands )
+ {
+ if( contains_str( tok_last(&tok),
+ L"end",
+ 0 ) )
+ {
+ err=1;
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ COND_ERR_MSG,
+ tok_get_pos( &tok ) );
+ print_errors();
+ }
+ }
+
+ require_additional_commands--;
+ }
+
+ if( wcscmp(tok_last(&tok), L"end")==0)
+ {
+ tok_next( &tok );
+ count--;
+ tok_set_pos( &tok, mark );
+ }
+ else if( parser_is_block( tok_last(&tok) ) )
+ {
+ if( count >= BLOCK_MAX_COUNT )
+ {
+ error( SYNTAX_ERROR, BLOCK_ERR_MSG, tok_get_pos( &tok ) );
+ print_errors();
+ }
+ else
+ {
+ if( wcscmp( tok_last(&tok), L"while") == 0 )
+ block_type[count] = WHILE;
+ else if( wcscmp( tok_last(&tok), L"for") == 0 )
+ block_type[count] = FOR;
+ else if( wcscmp( tok_last(&tok), L"switch") == 0 )
+ block_type[count] = SWITCH;
+ else if( wcscmp( tok_last(&tok), L"if") == 0 )
+ block_type[count] = IF;
+ else if( wcscmp( tok_last(&tok), L"function") == 0 )
+ block_type[count] = FUNCTION_DEF;
+ else
+ block_type[count] = -1;
+
+// debug( 2, L"add block of type %d after cmd %ls\n", block_type[count], tok_last(&tok) );
+
+
+ block_pos[count] = current_tokenizer_pos;
+ tok_next( &tok );
+ count++;
+ tok_set_pos( &tok, mark );
+ }
+ }
+
+ if( parser_is_subcommand( tok_last( &tok ) ) )
+ {
+ needs_cmd = 1;
+ had_cmd = 0;
+ }
+
+ if( contains_str( tok_last( &tok ),
+ L"or",
+ L"and",
+ 0 ) )
+ {
+ if( is_pipeline )
+ {
+ err=1;
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ EXEC_ERR_MSG,
+ tok_get_pos( &tok ) );
+ print_errors();
+
+ }
+ }
+ require_additional_commands=2;
+ }
+
+
+ if( parser_is_pipe_forbidden( tok_last( &tok ) ) )
+ {
+ if( is_pipeline )
+ {
+ err=1;
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ EXEC_ERR_MSG,
+ tok_get_pos( &tok ) );
+ print_errors();
+
+ }
+ }
+ forbid_pipeline = 1;
+ }
+
+ if( wcscmp( L"case", tok_last( &tok ) )==0 )
+ {
+ if( !count || block_type[count-1]!=SWITCH )
+ {
+ err=1;
+
+// debug( 2, L"Error on block type %d\n", block_type[count-1] );
+
+
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ L"'case' builtin not inside of switch block",
+ tok_get_pos( &tok ) );
+ print_errors();
+ }
+ }
+ }
+ else if( wcscmp( L"break", tok_last( &tok ) )==0 ||
+ wcscmp( L"continue", tok_last( &tok ) )==0)
+ {
+ int found_loop=0;
+ int i;
+ for( i=count-1; i>=0; i-- )
+ {
+ if( (block_type[i]==WHILE) ||
+ (block_type[i]==FOR) )
+ {
+ found_loop=1;
+ break;
+ }
+ }
+
+ if( !found_loop )
+ {
+ err=1;
+
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ L"Loop control command while not inside of loop",
+ tok_get_pos( &tok ) );
+ print_errors();
+ }
+ }
+ }
+ else if( wcscmp( L"else", tok_last( &tok ) )==0 )
+ {
+ if( !count || block_type[count-1]!=IF )
+ {
+ err=1;
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ L"'else' builtin not inside of if block",
+ tok_get_pos( &tok ) );
+ print_errors();
+ }
+ }
+
+ }
+
+ if( count < 0 )
+ {
+ err = 1;
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ L"'end' command outside of block",
+ tok_get_pos( &tok ) );
+ print_errors();
+ }
+ }
+ }
+ break;
+ }
+
+ case TOK_REDIRECT_OUT:
+ case TOK_REDIRECT_IN:
+ case TOK_REDIRECT_APPEND:
+ case TOK_REDIRECT_FD:
+ {
+ if( !had_cmd )
+ {
+ err = 1;
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ L"Redirection error",
+ tok_get_pos( &tok ) );
+ print_errors();
+ }
+ }
+ break;
+ }
+
+ case TOK_END:
+ {
+ if( needs_cmd && !had_cmd )
+ {
+ err = 1;
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ CMD_ERR_MSG,
+ tok_get_pos( &tok ) );
+ print_errors();
+ }
+ }
+ needs_cmd=0;
+ had_cmd = 0;
+ is_pipeline=0;
+ forbid_pipeline=0;
+ break;
+ }
+
+ case TOK_PIPE:
+ {
+ if( forbid_pipeline )
+ {
+ err=1;
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ EXEC_ERR_MSG,
+ tok_get_pos( &tok ) );
+ print_errors();
+ }
+ }
+ needs_cmd=0;
+ is_pipeline=1;
+ }
+
+
+ case TOK_BACKGROUND:
+ {
+ if( needs_cmd && !had_cmd )
+ {
+ err = 1;
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ CMD_ERR_MSG,
+ tok_get_pos( &tok ) );
+ print_errors();
+ }
+ }
+
+ if( had_cmd )
+ {
+ had_cmd = 0;
+ }
+ break;
+ }
+
+ case TOK_ERROR:
+ default:
+ err = 1;
+ if( babble )
+ {
+ error_arg( SYNTAX_ERROR,
+ TOK_ERR_MSG,
+ tok_last(&tok),
+ tok_get_pos( &tok ) );
+ print_errors();
+ //debug( 2, tok_last( &tok) );
+ }
+ break;
+ }
+ }
+
+ if( require_additional_commands )
+ {
+ err=1;
+ if( babble )
+ {
+ error( SYNTAX_ERROR,
+ COND_ERR_MSG,
+ tok_get_pos( &tok ) );
+ print_errors();
+ }
+ }
+
+
+ if( babble && count>0 )
+ {
+ error( SYNTAX_ERROR,
+ END_ERR_MSG L"\n",
+ block_pos[count-1] );
+ print_errors();
+ }
+
+ tok_destroy( &tok );
+
+
+ current_tokenizer=previous_tokenizer;
+ current_tokenizer_pos = previous_pos;
+
+ error_code=0;
+
+ return err | ((count!=0)<<1);
+}
+
diff --git a/parser.h b/parser.h
new file mode 100644
index 00000000..4c0f343d
--- /dev/null
+++ b/parser.h
@@ -0,0 +1,261 @@
+/** \file parser.h
+ The fish parser.
+*/
+
+/**
+ block_t represents a block of commands.
+*/
+typedef struct block
+{
+ int type; /**< Type of block. Can be one of WHILE, FOR, IF and FUNCTION */
+ int skip; /**< Whether execution of the commands in this block should be skipped */
+ int tok_pos; /**< The start index of the block */
+
+ /**
+ Status for the current loop block. Can be anu of the values from the loop_status enum.
+ */
+ int loop_status;
+
+ /**
+ First block type specific variable
+ */
+ union
+ {
+ int while_state; /**< True if the loop condition has not yet been evaluated*/
+ wchar_t *for_variable; /**< Name of the variable to loop over */
+ int if_state; /**< The state of the if block */
+ wchar_t *switch_value; /**< The value to test in a switch block */
+ wchar_t *function_name; /**< The name of the function to define */
+ };
+
+ /**
+ Second block type specific variable
+ */
+ union
+ {
+ array_list_t for_vars; /**< List of values for a for block */
+ int switch_taken; /**< Whether a switch match has already been found */
+ wchar_t *function_description; /**< The description of the function to define */
+ };
+
+ /**
+ Next outer block
+ */
+ struct block *outer;
+} block_t;
+
+/**
+ Types of blocks
+*/
+enum block_type
+{
+ WHILE, /**< While loop block */
+ FOR, /**< For loop block */
+ IF, /**< If block */
+ FUNCTION_DEF, /**< Function definition block */
+ FUNCTION_CALL, /**< Function invocation block */
+ SWITCH, /**< Switch block */
+ FAKE, /**< Fake block */
+ SUBST, /**< Command substitution scope */
+ TOP, /**< Outermost block */
+ BEGIN, /**< Unconditional block */
+ AND, /**< And block */
+ OR, /**< Or block */
+}
+;
+
+/**
+ Possible states for a loop
+*/
+enum loop_status
+{
+ LOOP_NORMAL, /**< Current loop block executed as normal */
+ LOOP_BREAK, /**< Current loop block should be removed */
+ LOOP_CONTINUE, /**< Current loop block should be skipped */
+};
+
+
+/**
+ Possible states for a while block
+*/
+enum while_status
+{
+ WHILE_TEST_FIRST, /**< This is the first command of the first lap of a while loop */
+ WHILE_TEST_AGAIN, /**< This is not the first lap of the while loop, but it is the first command of the loop */
+ WHILE_TESTED, /**< This is not the first command in the loop */
+}
+;
+
+
+
+/**
+ Errors that can be generated by the parser
+*/
+enum parser_error
+{
+ NO_ERR=0,
+ SYNTAX_ERROR,
+ EVAL_ERROR,
+ OOM,
+ STACK_ERROR,
+ SUBSHELL_ERROR
+}
+;
+
+/** The current innermost block */
+extern block_t *current_block;
+
+/** The current error code */
+extern int error_code;
+
+/**
+ Current block level redirections
+*/
+extern io_data_t *block_io;
+
+/**
+ Finds the full path of an executable in a newly allocated string.
+
+ \param cmd The name of the executable.
+ \return 0 if the command can not be found, the path of the command otherwise.
+*/
+wchar_t *get_filename( const wchar_t *cmd );
+
+/**
+ Evaluate the expressions contained in cmd.
+
+ \param cmd the string to evaluate
+ \param out buffer to insert output to. May be null.
+ \param the type of block to push onto the scope stack
+ \param block_type The type of block to push on the block stack
+ \return 0 on success.
+*/
+int eval( const wchar_t *cmd, io_data_t *io, int block_type );
+
+/**
+ Evaluate line as a list of parameters, i.e. tokenize it and perform parameter expansion and subshell execution on the tokens.
+ The output is inserted into output, and should be freed by the caller.
+
+ \param line Line to evaluate
+ \param output List to insert output to
+*/
+int eval_args( const wchar_t *line,
+ array_list_t *output );
+
+/**
+ Sets the current error
+
+ \param ec The new error code
+ \param str The new error message
+ \param p The character offset at which the error occured
+*/
+void error( int ec, const wchar_t *str, int p );
+
+/**
+ Tests if the specified commands parameters should be interpreted as another command, which will be true if the command is either 'command', 'exec', 'if', 'while' or 'builtin'.
+
+ \param cmd The command name to test
+ \return 1 of the command parameter is a command, 0 otherwise
+*/
+
+int parser_is_subcommand( const wchar_t *cmd );
+
+/**
+ Tests if the specified command is a reserved word, i.e. if it is
+ the name of one of the builtin functions that change the block or
+ command scope, like 'for', 'end' or 'command' or 'exec'. These
+ functions may not be overloaded, so their names are reserved.
+
+ \param cmd The command name to test
+ \return 1 of the command parameter is a command, 0 otherwise
+*/
+int parser_is_reserved( wchar_t *word);
+
+/**
+ Returns a string describing the current parser pisition in the format 'FILENAME (line LINE_NUMBER): LINE'.
+ Example:
+
+ init.fish (line 127): ls|grep pancake
+*/
+wchar_t *parser_current_line();
+
+/**
+ Returns the current position in the latest string of the tokenizer.
+*/
+int parser_get_pos();
+
+/**
+ Returns the position where the current job started in the latest string of the tokenizer.
+*/
+int parser_get_job_pos();
+
+/**
+ Set the current position in the latest string of the tokenizer.
+*/
+void parser_set_pos( int p);
+
+/**
+ Get the string currently parsed
+*/
+const wchar_t *parser_get_buffer();
+
+/**
+ Create block of specified type
+*/
+void parser_push_block( int type);
+
+/**
+ Remove the outermost block namespace
+*/
+void parser_pop_block();
+
+/**
+ Return a description of the given blocktype
+*/
+wchar_t *parser_get_block_desc( int block );
+
+
+/**
+ Test if the specified string can be parsed, or if more bytes need to be read first.
+ The result has the first bit set if the string contains errors, and the second bit is set if the string contains an unclosed block.
+*/
+int parser_test( wchar_t * buff, int babble );
+
+/**
+ Returns the full path of the specified directory. If the \c in is a
+ full path to an existing directory, a copy of the string is
+ returned. If \c in is a directory relative to one of the
+ directories i the CDPATH, the full path is returned. If no
+ directory can be found, 0 is returned.
+*/
+wchar_t *parser_cdpath_get( wchar_t *in );
+
+/**
+ Tell the parser that the specified function may not be run if not
+ inside of a conditional block. This is to remove some possibilities
+ of infinite recursion.
+*/
+void parser_forbid_function( wchar_t *function );
+/**
+ Undo last call to parser_forbid_function().
+*/
+void parser_allow_function();
+
+/**
+ Initialize the parser
+*/
+void parser_init();
+
+/**
+ Destroy the parser
+*/
+void parser_destroy();
+
+/**
+ This function checks if the specified string is a help option.
+
+ \param s the string to test
+ \param min_match is the minimum number of characters that must match in a long style option, i.e. the longest common prefix between --help and any other option. If less than 3, 3 will be assumed.
+*/
+int parser_is_help( wchar_t *s, int min_match );
+
diff --git a/proc.c b/proc.c
new file mode 100644
index 00000000..53512fe6
--- /dev/null
+++ b/proc.c
@@ -0,0 +1,1268 @@
+/** \file proc.c
+
+Utilities for keeping track of jobs, processes and subshells, as
+well as signal handling functions for tracking children. These
+functions do not themselves launch new processes, the exec library
+will call proc to create representations of the running jobs as
+needed.
+
+Some of the code in this file is based on code from the Glibc manual.
+
+*/
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <wchar.h>
+#include <string.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <dirent.h>
+#include <sys/time.h>
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+
+#include "util.h"
+#include "wutil.h"
+#include "proc.h"
+#include "common.h"
+#include "reader.h"
+#include "sanity.h"
+#include "env.h"
+
+/**
+ Size of message buffer
+*/
+#define MESS_SIZE 256
+
+/**
+ Size of buffer for reading buffered output
+*/
+#define BUFFER_SIZE 4096
+
+/** Status of last process to exit */
+static int last_status=0;
+
+/** Signal flag */
+static sig_atomic_t got_signal=0;
+
+job_t *first_job=0;
+int is_interactive=0;
+int is_interactive_session=0;
+int is_subshell=0;
+int is_block=0;
+int is_login=0;
+
+pid_t proc_last_bg_pid = 0;
+
+io_data_t *io_add( io_data_t *list, io_data_t *element )
+{
+ io_data_t *curr = list;
+ if( curr == 0 )
+ return element;
+ while( curr->next != 0 )
+ curr = curr->next;
+ curr->next = element;
+ return list;
+}
+
+io_data_t *io_remove( io_data_t *list, io_data_t *element )
+{
+ io_data_t *curr, *prev=0;
+ for( curr=list; curr; curr = curr->next )
+ {
+ if( element == curr )
+ {
+ if( prev == 0 )
+ {
+ io_data_t *tmp = element->next;
+ element->next = 0;
+ return tmp;
+ }
+ else
+ {
+ prev->next = element->next;
+ element->next = 0;
+ return list;
+ }
+ }
+ prev = curr;
+ }
+ return list;
+}
+
+io_data_t *io_duplicate( io_data_t *l )
+{
+ io_data_t *res;
+
+ if( l == 0 )
+ return 0;
+
+ res = malloc( sizeof( io_data_t) );
+
+ if( !res )
+ {
+ die_mem();
+
+ }
+
+ memcpy( res, l, sizeof(io_data_t ));
+ res->next=io_duplicate( l->next );
+ return res;
+}
+
+io_data_t *io_get( io_data_t *io, int fd )
+{
+ if( io == 0 )
+ return 0;
+
+ io_data_t *res = io_get( io->next, fd );
+ if( res )
+ return res;
+
+ if( io->fd == fd )
+ return io;
+
+ return 0;
+}
+
+
+/**
+ Recursively free a process and those following it
+*/
+static void free_process( process_t *p )
+{
+ wchar_t **arg;
+
+ if( p==0 )
+ return;
+
+ free_process( p->next );
+ free( p->actual_cmd );
+ if( p->argv != 0 )
+ {
+ for( arg=p->argv; *arg; arg++ )
+ free( *arg );
+ free(p->argv );
+ }
+ free( p );
+}
+
+/** Remove job from list of jobs */
+
+static int job_remove( job_t *j )
+{
+ job_t *prev=0, *curr=first_job;
+ while( (curr != 0) && (curr != j) )
+ {
+ prev = curr;
+ curr = curr->next;
+ }
+
+ if( j != curr )
+ {
+ debug( 1, L"Job inconsistency" );
+ sanity_lose();
+ return 0;
+ }
+
+ if( prev == 0 )
+ first_job = j->next;
+ else
+ prev->next = j->next;
+ return 1;
+}
+
+
+/*
+ Remove job from the job list and free all memory associated with
+ it.
+*/
+void job_free( job_t * j )
+{
+ io_data_t *io, *ionext;
+
+// fwprintf( stderr, L"Remove job %d (%ls)\n", j->job_id, j->command );
+
+ job_remove( j );
+
+ /* Then free ourselves */
+ free_process( j->first_process);
+
+ if( j->command != 0 )
+ free( j->command );
+
+ for( io=j->io; io; io=ionext )
+ {
+ ionext = io->next;
+// fwprintf( stderr, L"Kill redirect %d of type %d\n", ionext, io->io_mode );
+ if( io->io_mode == IO_FILE )
+ {
+ free( io->filename );
+ }
+ free( io );
+ }
+
+ free( j );
+}
+
+void proc_destroy()
+{
+ while( first_job )
+ {
+ debug( 2, L"freeing leaked job %ls", first_job->command );
+ job_free( first_job );
+ }
+}
+
+void proc_set_last_status( int s )
+{
+ last_status = s;
+ wchar_t stat[16];
+ swprintf( stat, 16, L"%d", s );
+ env_set( L"status", stat, ENV_GLOBAL );
+ // fwprintf( stderr, L"Set last status to %d\n", s );
+}
+
+int proc_get_last_status()
+{
+ return last_status;
+}
+
+job_t *job_create()
+{
+ int free_id=0;
+ job_t *res;
+
+ while( job_get( free_id ) != 0 )
+ free_id++;
+ res = calloc( 1, sizeof(job_t) );
+ res->next = first_job;
+ res->job_id = free_id;
+ first_job = res;
+// if( res->job_id > 2 )
+// fwprintf( stderr, L"Create job %d\n", res->job_id );
+ return res;
+}
+
+
+job_t *job_get( int id )
+{
+ job_t *res = first_job;
+ if( id == -1 )
+ {
+ return res;
+ }
+
+ while( res != 0 )
+ {
+ if( res->job_id == id )
+ return res;
+ res = res->next;
+ }
+ return 0;
+}
+
+job_t *job_get_from_pid( int pid )
+{
+ job_t *res = first_job;
+
+ while( res != 0 )
+ {
+ if( res->pgid == pid )
+ return res;
+ res = res->next;
+ }
+ return 0;
+}
+
+
+/*
+ Return true if all processes in the job have stopped or completed.
+*/
+int job_is_stopped( const job_t *j )
+{
+ process_t *p;
+
+ for (p = j->first_process; p; p = p->next)
+ {
+ if (!p->completed && !p->stopped)
+ {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+/*
+ Return true if all processes in the job have completed.
+*/
+int job_is_completed( const job_t *j )
+{
+ process_t *p;
+
+ for (p = j->first_process; p; p = p->next)
+ {
+ if (!p->completed)
+ {
+// fwprintf( stderr, L"Process %ls not finished\n", p->argv[0] );
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/**
+ Return true if all processes in the job have completed.
+*/
+static int job_last_is_completed( const job_t *j )
+{
+ process_t *p;
+
+ for (p = j->first_process; p->next; p = p->next)
+ ;
+ return p->completed;
+}
+
+/**
+ Get string representation of a signal
+*/
+static wchar_t *sig2wcs( int sig )
+{
+ switch( sig )
+ {
+ case SIGHUP:
+ return L"SIGHUP";
+ case SIGINT:
+ return L"SIGINT";
+ case SIGQUIT:
+ return L"SIGQUIT";
+ case SIGILL:
+ return L"SIGILL";
+ case SIGTRAP:
+ return L"SIGTRAP";
+ case SIGABRT:
+ return L"SIGABRT";
+ case SIGBUS:
+ return L"SIGBUS";
+ case SIGFPE:
+ return L"SIGFPE";
+ case SIGKILL:
+ return L"SIGKILL";
+ case SIGUSR1:
+ return L"SIGUSR1";
+ case SIGSEGV:
+ return L"SIGSEGV";
+ case SIGUSR2:
+ return L"SIGUSR2";
+ case SIGPIPE:
+ return L"SIGPIPE";
+ case SIGALRM:
+ return L"SIGALRM";
+ case SIGTERM:
+ return L"SIGTERM";
+ case SIGCHLD:
+ return L"SIGCHLD";
+ case SIGCONT:
+ return L"SIGCONT";
+ case SIGSTOP:
+ return L"SIGSTOP";
+ case SIGTSTP:
+ return L"SIGTSTP";
+ case SIGTTIN:
+ return L"SIGTTIN";
+ case SIGTTOU:
+ return L"SIGTTOU";
+ case SIGURG:
+ return L"SIGURG";
+ case SIGXCPU:
+ return L"SIGXCPU";
+ case SIGXFSZ:
+ return L"SIGFXSZ";
+ case SIGVTALRM:
+ return L"SIGVTALRM";
+ case SIGPROF:
+ return L"SIGPROF";
+ case SIGWINCH:
+ return L"SIGWINCH";
+ case SIGIO:
+ return L"SIGIO";
+#ifdef SIGPWR
+ case SIGPWR:
+ return L"SIGPWR";
+#endif
+ case SIGSYS:
+ return L"SIGSYS";
+ default:
+ return L"Unknown";
+ }
+
+}
+
+/**
+ Returns a description of the specified signal.
+*/
+static wchar_t *sig_description( int sig )
+{
+ switch( sig )
+ {
+ case SIGHUP:
+ return L"Terminal hung up";
+ case SIGINT:
+ return L"Quit request from job control (^C)";
+ case SIGQUIT:
+ return L"Quit request from job control with core dump (^\\)";
+ case SIGILL:
+ return L"Illegal instruction";
+ case SIGTRAP:
+ return L"Trace or breakpoint trap";
+ case SIGABRT:
+ return L"Abort";
+ case SIGBUS:
+ return L"Misaligned address error";
+ case SIGFPE:
+ return L"Floating point exception";
+ case SIGKILL:
+ return L"Forced quit";
+ case SIGUSR1:
+ return L"User defined signal 1";
+ case SIGUSR2:
+ return L"User defined signal 2";
+ case SIGSEGV:
+ return L"Address boundary error";
+ case SIGPIPE:
+ return L"Broken pipe";
+ case SIGALRM:
+ return L"Timer expired";
+ case SIGTERM:
+ return L"Polite quit request";
+ case SIGCHLD:
+ return L"Child process status changed";
+ case SIGCONT:
+ return L"Continue previously stopped process";
+ case SIGSTOP:
+ return L"Forced stop";
+ case SIGTSTP:
+ return L"Stop request from job control (^Z)";
+ case SIGTTIN:
+ return L"Stop from terminal input";
+ case SIGTTOU:
+ return L"Stop from terminal output";
+ case SIGURG:
+ return L"Urgent socket condition";
+ case SIGXCPU:
+ return L"CPU time limit exceeded";
+ case SIGXFSZ:
+ return L"File size limit exceeded";
+ case SIGVTALRM:
+ return L"Virtual timer expired";
+ case SIGPROF:
+ return L"Profiling timer expired";
+ case SIGWINCH:
+ return L"Window size change";
+ case SIGIO:
+ return L"Asynchronous IO event";
+#ifdef SIGPWR
+ case SIGPWR:
+ return L"Power failure";
+#endif
+ case SIGSYS:
+ return L"Bad system call";
+ default:
+ return L"Unknown";
+ }
+
+}
+
+
+/**
+ Store the status of the process pid that was returned by waitpid.
+ Return 0 if all went well, nonzero otherwise.
+*/
+static void mark_process_status( job_t *j,
+ process_t *p,
+ int status )
+{
+ p->status = status;
+ if (WIFSTOPPED (status))
+ {
+ p->stopped = 1;
+// fwprintf( stderr, L"Proc %d (%ls) stopped\n", p->pid, p->actual_cmd );
+
+/* sprintf( mess,
+ "%ls (%d): Process stopped\n",
+ j->command,
+ (int) p->pid );
+ write( 2, mess, strlen(mess) );*/
+ }
+ else
+ {
+ p->completed = 1;
+// fwprintf( stderr, L"Proc %d (%ls) exited\n", p->pid, p->actual_cmd );
+
+ if (( !WIFEXITED( status ) ) &&
+ (! WIFSIGNALED( status )) )
+ {
+ /* This should never be reached */
+ char mess[128];
+ sprintf( mess,
+ "Process %d exited abnormally\n",
+ (int) p->pid );
+
+ write( 2, mess, strlen(mess) );
+ }
+ }
+}
+
+/**
+ Handle status update for child \c pid. This function is called by
+ the signal handler, so it mustn't use malloc or any such nonsense.
+*/
+static void handle_child_status( pid_t pid, int status )
+{
+ int found_proc = 0;
+ job_t *j;
+ process_t *p;
+// char mess[MESS_SIZE];
+ found_proc = 0;
+ /*
+ snprintf( mess,
+ MESS_SIZE,
+ "Process %d\n",
+ (int) pid );
+ write( 2, mess, strlen(mess ));
+ */
+
+ for( j=first_job; j && !found_proc; j=j->next )
+ {
+ process_t *prev=0;
+ for( p=j->first_process; p; p=p->next )
+ {
+ if( pid == p->pid )
+ {
+/* snprintf( mess,
+ MESS_SIZE,
+ "Process %d is %ls from job %ls\n",
+ (int) pid, p->actual_cmd, j->command );
+ write( 2, mess, strlen(mess ));
+*/
+
+ mark_process_status ( j, p, status);
+ if( p->completed && prev != 0 )
+ {
+ if( !prev->completed && prev->pid)
+ {
+ /* snprintf( mess,
+ MESS_SIZE,
+ "Kill previously uncompleted process %ls (%d)\n",
+ prev->actual_cmd,
+ prev->pid );
+ write( 2, mess, strlen(mess ));
+ */
+ kill(prev->pid,SIGPIPE);
+ }
+ }
+ found_proc = 1;
+ break;
+ }
+ prev = p;
+ }
+ }
+
+
+ if( !is_interactive )
+ {
+
+ if( WIFSIGNALED( status ) &&
+ ( WTERMSIG(status)==SIGINT ||
+ WTERMSIG(status)==SIGQUIT ) )
+ {
+ struct sigaction act;
+ sigemptyset( & act.sa_mask );
+ act.sa_flags=0;
+ act.sa_handler=SIG_DFL;
+ sigaction( SIGINT, &act, 0 );
+ sigaction( SIGQUIT, &act, 0 );
+ kill( getpid(), WTERMSIG(status) );
+ }
+ }
+
+ if( !found_proc )
+ {
+ /*
+ A child we lost track of?
+
+ There have been bugs in both subshell handling and in
+ builtin handling that have caused this previously...
+ */
+/* snprintf( mess,
+ MESS_SIZE,
+ "Process %d not found by %d\n",
+ (int) pid, (int)getpid() );
+
+ write( 2, mess, strlen(mess ));
+*/
+ }
+ return;
+}
+
+
+void job_handle_signal ( int signal, siginfo_t *info, void *con )
+{
+
+ int status;
+ pid_t pid;
+ int errno_old = errno;
+
+ got_signal = 1;
+
+// write( 2, "got signal\n", 11 );
+
+
+ while(1)
+ {
+ switch(pid=waitpid( -1,&status,WUNTRACED|WNOHANG ))
+ {
+ case 0:
+ case -1:
+ {
+ errno=errno_old;
+ return;
+ }
+ default:
+
+ handle_child_status( pid, status );
+ break;
+ }
+ }
+ kill( 0, SIGIO );
+ errno=errno_old;
+}
+
+/**
+ Format information about job status for the user to look at.
+*/
+static void format_job_info( const job_t *j, const wchar_t *status )
+{
+ fwprintf (stdout, L"\rJob %d, \'%ls\' has %ls", j->job_id, j->command, status);
+ fflush( stdout );
+ tputs(clr_eol,1,&writeb);
+ fwprintf (stdout, L"\n" );
+}
+
+int job_do_notification()
+{
+ job_t *j, *jnext;
+ int found=0;
+
+ for( j=first_job; j; j=jnext)
+ {
+ process_t *p;
+ jnext = j->next;
+
+
+ for( p=j->first_process; p; p=p->next )
+ {
+ if( !p->completed )
+ continue;
+
+ if( p->type )
+ continue;
+
+
+ if( WIFSIGNALED(p->status) )
+ {
+ /*
+ Ignore signal SIGPIPE.We issue it ourselves to the pipe
+ writer when the pipe reader dies.
+ */
+ if( WTERMSIG(p->status) != SIGPIPE )
+ {
+ int proc_is_job = ((p==j->first_process) && (p->next == 0));
+ if( proc_is_job )
+ j->notified = 1;
+ if( !j->skip_notification )
+ {
+ fwprintf( stdout,
+ L"fish: %ls %d, \'%ls\' terminated by signal %ls (%ls)",
+ proc_is_job?L"Job":L"Process",
+ proc_is_job?j->job_id:p->pid,
+ j->command,
+ sig2wcs(WTERMSIG(p->status)),
+ sig_description( WTERMSIG(p->status) ) );
+ tputs(clr_eol,1,&writeb);
+ fwprintf (stdout, L"\n" );
+ found=1;
+ }
+
+ /*
+ Clear status so it is not reported more than once
+ */
+ p->status = 0;
+ }
+ }
+ }
+
+ /*
+ If all processes have completed, tell the user the job has
+ completed and delete it from the active job list.
+ */
+ if( job_is_completed(j) ) {
+ if( !j->fg && !j->notified )
+ {
+ if( !j->skip_notification )
+ {
+ format_job_info (j, L"ended");
+ found=1;
+ }
+ }
+ job_free(j);
+ }
+ else if(job_is_stopped (j) && !j->notified) {
+ /*
+ Notify the user about newly stopped jobs.
+ */
+ if( !j->skip_notification )
+ {
+ format_job_info(j, L"stopped");
+ found=1;
+ }
+ j->notified = 1;
+ }
+ }
+ if( found )
+ fflush( stdout );
+ return found;
+
+}
+
+
+#ifdef HAVE__PROC_SELF_STAT
+/**
+ Get the CPU time for the specified process
+*/
+unsigned long proc_get_jiffies( process_t *p )
+{
+ wchar_t fn[256];
+ //char stat_line[1024];
+
+ char state;
+ int pid, ppid, pgrp,
+ session, tty_nr, tpgid,
+ exit_signal, processor;
+
+ long int cutime, cstime, priority,
+ nice, placeholder, itrealvalue,
+ rss;
+ unsigned long int flags, minflt, cminflt,
+ majflt, cmajflt, utime,
+ stime, starttime, vsize,
+ rlim, startcode, endcode,
+ startstack, kstkesp, kstkeip,
+ signal, blocked, sigignore,
+ sigcatch, wchan, nswap, cnswap;
+ char comm[1024];
+
+ if( p->pid <= 0 )
+ return 0;
+
+ swprintf( fn, 512, L"/proc/%d/stat", p->pid );
+
+ FILE *f = wfopen( fn, "r" );
+ if( !f )
+ return 0;
+
+ int count = fscanf( f,
+ "%d %s %c "
+ "%d %d %d "
+ "%d %d %lu "
+
+ "%lu %lu %lu "
+ "%lu %lu %lu "
+ "%ld %ld %ld "
+
+ "%ld %ld %ld "
+ "%lu %lu %ld "
+ "%lu %lu %lu "
+
+ "%lu %lu %lu "
+ "%lu %lu %lu "
+ "%lu %lu %lu "
+
+ "%lu %d %d ",
+
+ &pid, comm, &state,
+ &ppid, &pgrp, &session,
+ &tty_nr, &tpgid, &flags,
+
+ &minflt, &cminflt, &majflt,
+ &cmajflt, &utime, &stime,
+ &cutime, &cstime, &priority,
+
+ &nice, &placeholder, &itrealvalue,
+ &starttime, &vsize, &rss,
+ &rlim, &startcode, &endcode,
+
+ &startstack, &kstkesp, &kstkeip,
+ &signal, &blocked, &sigignore,
+ &sigcatch, &wchan, &nswap,
+
+ &cnswap, &exit_signal, &processor
+ );
+
+ if( count < 17 )
+ {
+ return 0;
+ }
+ fclose( f );
+ return utime+stime+cutime+cstime;
+
+}
+
+/**
+ Update the CPU time for all jobs
+*/
+void proc_update_jiffies()
+{
+ job_t *j;
+ process_t *p;
+
+ for( j=first_job; j; j=j->next )
+ {
+ for( p=j->first_process; p; p=p->next )
+ {
+ gettimeofday( &p->last_time, 0 );
+ p->last_jiffies = proc_get_jiffies( p );
+ }
+ }
+}
+
+
+#endif
+
+/**
+ Check if there are buffers associated with the job, and select on
+ them for a while if available.
+
+ \return 1 if buffers where avaialble, zero otherwise
+*/
+static int select_try( job_t *j )
+{
+ fd_set fds;
+ int maxfd=-1;
+ io_data_t *d;
+
+
+
+/* if( j->stop_reading )
+ {
+ sleep(1);
+ return;
+ }
+*/
+
+ FD_ZERO(&fds);
+
+ for( d = j->io; d; d=d->next )
+ {
+ if( d->io_mode == IO_BUFFER )
+ {
+ int fd = d->pipe_fd[0];
+// fwprintf( stderr, L"fd %d on job %ls\n", fd, j->command );
+ FD_SET( fd, &fds );
+ maxfd=maxi( maxfd, d->pipe_fd[0] );
+ debug( 3, L"select_try on %d\n", fd );
+ }
+ }
+
+ if( maxfd >= 0 )
+ {
+ int retval;
+ struct timeval tv;
+
+ tv.tv_sec=5;
+ tv.tv_usec=0;
+
+ retval =select( maxfd+1, &fds, 0, 0, &tv );
+ return retval > 0;
+ }
+
+ return -1;
+}
+
+/**
+ Read from descriptors until they are empty.
+*/
+static void read_try( job_t *j )
+{
+ io_data_t *d, *buff=0;
+
+ /*
+ Find the last buffer, which is the one we want to read from
+ */
+ for( d = j->io; d; d=d->next )
+ {
+
+ if( d->io_mode == IO_BUFFER )
+ {
+ buff=d;
+
+ }
+ }
+
+ if( buff )
+ {
+ // fwprintf( stderr, L"proc::read_try('%ls')\n", j->command );
+ while(1)
+ {
+ char b[BUFFER_SIZE];
+ int l;
+ //fwprintf( stderr, L"read...\n");
+ l=read_blocked( buff->pipe_fd[0], b, BUFFER_SIZE );
+ if( l==0 )
+ {
+ break;
+ }
+ else if( l<0 )
+ {
+ if( errno != EAGAIN )
+ {
+ debug( 1,
+ L"An error occured while reading output from code block" );
+ wperror( L"read_try" );
+ }
+
+ break;
+ }
+ else
+ {
+ b_append( buff->out_buffer, b, l );
+ }
+
+ }
+ }
+}
+
+void job_continue (job_t *j, int cont)
+{
+ /*
+ Put job first in the job list
+ */
+ job_remove( j );
+ j->next = first_job;
+ first_job = j;
+ j->notified = 0;
+
+// if( is_interactive )
+ debug( 3,
+ L"Continue on job %d (%ls), %ls, %ls",
+ j->job_id,
+ j->command,
+ job_is_completed( j )?L"COMPLETED":L"UNCOMPLETED",
+ is_interactive?L"INTERACTIVE":L"NON-INTERACTIVE" );
+
+ if( !job_is_completed( j ) )
+ {
+ if( !is_subshell && is_interactive && !is_block)
+ {
+
+ /* Put the job into the foreground. */
+ if( j->fg )
+ {
+ while( 1 )
+ {
+ if( tcsetpgrp (0, j->pgid) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1,
+ L"Could not send job %d ('%ls') to foreground",
+ j->job_id,
+ j->command );
+ wperror( L"tcsetpgrp" );
+ return;
+ }
+ }
+ else
+ break;
+ }
+
+ if( cont )
+ {
+// fwprintf( stderr, L"tcsetattr\n" );
+ while( 1 )
+ {
+ if( tcsetattr (0, TCSADRAIN, &j->tmodes))
+ {
+ if( errno != EINTR )
+ {
+ debug( 1,
+ L"Could not send job %d ('%ls') to foreground",
+ j->job_id,
+ j->command );
+ wperror( L"tcsetattr" );
+ return;
+ }
+ }
+ else
+ break;
+ }
+
+ }
+ }
+ }
+ }
+
+ /*
+ Send the job a continue signal, if necessary.
+ */
+ if( cont )
+ {
+ process_t *p;
+ for( p=j->first_process; p; p=p->next )
+ p->stopped=0;
+ for( p=j->first_process; p; p=p->next )
+ {
+ if (kill ( p->pid, SIGCONT) < 0)
+ {
+ wperror (L"kill (SIGCONT)");
+ return;
+ }
+ }
+ }
+
+ if( j->fg )
+ {
+ int quit = 0;
+
+ /*
+ Wait for job to report. Looks a bit ugly because it has to
+ handle the possibility that a signal is dispatched while
+ running job_is_stopped().
+ */
+ /*
+ fwprintf( stderr, L"Wait for %ls (%d)\n", j->command, j->pgid );
+ */
+ while( !quit )
+ {
+ do
+ {
+ got_signal = 0;
+ quit = job_is_stopped( j ) || job_last_is_completed( j );
+ }
+ while( got_signal && !quit );
+ if( !quit )
+ {
+
+ debug( 3, L"select_try()" );
+ switch( select_try(j) )
+ {
+ case 1:
+ {
+ debug( 3, L"1" );
+ read_try( j );
+ break;
+ }
+
+ case -1:
+ {
+ /*
+ If there is no funky IO magic, we can use
+ waitpid instead of handling child deaths
+ through signals. This gives a rather large
+ speed boost (A factor 3 startup time
+ improvement on my 300 MHz machine) on
+ short-lived jobs.
+ */
+ debug( 3, L"-1" );
+ int status;
+ pid_t pid = waitpid(-1, &status, WUNTRACED );
+ if( pid > 0 )
+ handle_child_status( pid, status );
+ break;
+ }
+
+ }
+ }
+ }
+ }
+
+ if( j->fg )
+ {
+
+ if( job_is_completed( j ))
+ {
+ process_t *p = j->first_process;
+ while( p->next )
+ p = p->next;
+
+ if( WIFEXITED( p->status ) )
+ {
+ /*
+ Mark process status only if we are in the foreground
+ and the last process in a pipe, and it is not a short circuted builtin
+ */
+ if( p->pid )
+ {
+ debug( 3, L"Set status of %ls to %d", j->command, WEXITSTATUS(p->status) );
+ proc_set_last_status( j->negate?(WEXITSTATUS(p->status)?0:1):WEXITSTATUS(p->status) );
+ }
+ }
+
+ }
+ /*
+ Put the shell back in the foreground.
+ */
+ if( !is_subshell && is_interactive && !is_block)
+ {
+
+ while( 1 )
+ {
+
+ if( tcsetpgrp (0, getpid()) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not return shell to foreground" );
+ wperror( L"tcsetpgrp" );
+ return;
+ }
+ }
+ else break;
+ }
+
+ /*
+ Save jobs terminal modes.
+ */
+ while( 1 )
+ {
+ if( tcgetattr (0, &j->tmodes) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not return shell to foreground" );
+ wperror( L"tcgetattr" );
+ return;
+ }
+ }
+ else
+ break;
+ }
+
+ /*
+ Restore the shell's terminal modes.
+ */
+ while( 1 )
+ {
+ if( tcsetattr (0, TCSADRAIN, &shell_modes))
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not return shell to foreground" );
+ wperror( L"tcsetattr" );
+ return;
+ }
+ }
+ else
+ break;
+ }
+ }
+ }
+// fwprintf( stderr, L"Job_continue end\n" );
+}
+
+void proc_sanity_check()
+{
+ job_t *j;
+ job_t *fg_job=0;
+
+ for( j = first_job; j ; j=j->next )
+ {
+ process_t *p;
+
+ if( !j->constructed )
+ continue;
+
+
+ validate_pointer( j->command,
+ L"Job command",
+ 0 );
+ validate_pointer( j->first_process,
+ L"Process list pointer",
+ 0 );
+ validate_pointer( j->next,
+ L"Job list pointer",
+ 1 );
+ validate_pointer( j->command,
+ L"Job command",
+ 0 );
+ /*
+ More than one foreground job?
+ */
+ if( j->fg && !(job_is_stopped(j) || job_last_is_completed(j) ) )
+ {
+ if( fg_job != 0 )
+ {
+ debug( 0,
+ L"More than one job in foreground:\n"
+ L"job 1: %ls\njob 2: %ls",
+ fg_job->command,
+ j->command );
+ sanity_lose();
+ }
+ fg_job = j;
+ }
+
+ p = j->first_process;
+ while( p )
+ {
+ validate_pointer( p->argv, L"Process argument list", 0 );
+ validate_pointer( p->argv[0], L"Process name", 0 );
+ validate_pointer( p->next, L"Process list pointer", 1 );
+ validate_pointer( p->actual_cmd, L"Process command", 1 );
+
+ if ( (p->stopped & (~0x00000001)) != 0 )
+ {
+ debug( 0,
+ L"Job %ls, process %ls "
+ L"has inconsistent state \'stopped\'=%d",
+ j->command,
+ p->argv[0],
+ p->stopped );
+ sanity_lose();
+ }
+
+ if ( (p->completed & (~0x00000001)) != 0 )
+ {
+ debug( 0,
+ L"Job %ls, process %ls "
+ L"has inconsistent state \'completed\'=%d",
+ j->command,
+ p->argv[0],
+ p->completed );
+ sanity_lose();
+ }
+
+ p=p->next;
+ }
+
+ }
+}
+
diff --git a/proc.h b/proc.h
new file mode 100644
index 00000000..213c6a4b
--- /dev/null
+++ b/proc.h
@@ -0,0 +1,264 @@
+/** \file proc.h
+
+ Prototypes for utilities for keeping track of jobs, processes and subshells, as
+ well as signal handling functions for tracking children. These
+ functions do not themselves launch new processes, the exec library
+ will call proc to create representations of the running jobs as
+ needed.
+
+*/
+
+/**
+ Describes what type of IO operation an io_data_t represents
+*/
+enum io_mode
+{
+ IO_FILE, IO_PIPE, IO_FD, IO_BUFFER, IO_CLOSE
+}
+;
+
+/**
+ Types of internal processes
+*/
+enum
+{
+ EXTERNAL,
+ INTERNAL_BUILTIN,
+ INTERNAL_FUNCTION,
+ INTERNAL_BLOCK,
+ INTERNAL_EXEC
+}
+ ;
+
+
+/** Represents an FD redirection */
+typedef struct io_data
+{
+ /** Type of redirect */
+ int io_mode;
+ /** FD to redirect */
+ int fd;
+ /** parameter for redirection */
+ union
+ {
+ /** Fds for IO_PIPE and for IO_BUFFER */
+ int pipe_fd[2];
+ /** Filename IO_FILE */
+ wchar_t *filename;
+ /** fd to redirect specified fd to, for IO_FD*/
+ int old_fd;
+ }
+ ;
+ union
+ {
+ /** file creation flags to send to open for IO_FILE */
+ int flags;
+ /** buffer to save output in for IO_BUFFER */
+ buffer_t *out_buffer;
+ /** Whether to close old_fd for IO_FD */
+ int close_old;
+
+ }
+ ;
+
+ /** Pointer to the next IO redirection */
+ struct io_data *next;
+}
+io_data_t;
+
+
+/**
+ A structore representing a single process. Contains variables for
+ tracking process state and the process argument list.
+*/
+typedef struct process{
+ /** argv parameter for for execv */
+ wchar_t **argv;
+ /** actual command to pass to exec */
+ wchar_t *actual_cmd;
+ /** process ID */
+ pid_t pid;
+ /**
+ Type of process. Can be one of \c EXTERNAL, \c
+ INTERNAL_BUILTIN, \c INTERNAL_FUNCTION, \c INTERNAL_BLOCK
+ */
+ int type;
+ /** true if process has completed */
+ volatile int completed;
+ /** true if process has stopped */
+ volatile int stopped;
+ /** reported status value */
+ volatile int status;
+ /** next process in pipeline */
+ struct process *next;
+#ifdef HAVE__PROC_SELF_STAT
+ /** Last time of cpu time check */
+ struct timeval last_time;
+ /** Number of jiffies spent in process at last cpu time check */
+ unsigned long last_jiffies;
+#endif
+} process_t;
+
+
+/** Represents a pipeline of one or more processes. */
+typedef struct job
+{
+ /** command line, used for messages */
+ wchar_t *command;
+ /** list of processes in this job */
+ process_t *first_process;
+ /** process group ID */
+ pid_t pgid;
+ /** true if user was told about stopped job */
+ int notified;
+ /** saved terminal modes */
+ struct termios tmodes;
+ /** The job id of the job*/
+ int job_id;
+ /** Whether this job is in the foreground */
+ int fg;
+ /**
+ Whether the specified job is completely constructed,
+ i.e. completely parsed, and every process in the job has been
+ forked
+ */
+ int constructed;
+ /**
+ Whether the specified job is a part of a subshell or some other form of special job that should not be reported
+ */
+ int skip_notification;
+
+ /** List of IO redrections for the job */
+ io_data_t *io;
+
+ /** Should the exit status be negated */
+ int negate;
+ /** Is this a conditional short circut thing? If so, is it an COND_OR or a COND_AND */
+ struct job *next;
+} job_t;
+
+/** Whether we are running a subshell command */
+extern int is_subshell;
+/** Whether we are running a block of commands */
+extern int is_block;
+/** Whether we are reading from the keyboard right now*/
+extern int is_interactive;
+/** Whether this shell is attached to the keyboard at all*/
+extern int is_interactive_session;
+/** Whether we are a login shell*/
+extern int is_login;
+/** Linked list of all jobs */
+extern job_t *first_job;
+
+
+extern pid_t proc_last_bg_pid;
+
+/**
+ Join two chains of io redirections
+*/
+io_data_t *io_add( io_data_t *first_chain, io_data_t *decond_chain );
+
+/**
+ Remove the specified io redirection from the chain
+*/
+io_data_t *io_remove( io_data_t *list, io_data_t *element );
+
+/**
+ Make a copy of the specified chain of redirections
+*/
+io_data_t *io_duplicate( io_data_t *l );
+
+/**
+ Return the last io redirection in ht e chain for the specified file descriptor.
+*/
+io_data_t *io_get( io_data_t *io, int fd );
+
+/**
+ Sets the status of the last process to exit
+*/
+void proc_set_last_status( int s );
+/**
+ Returns the status of the last process to exit
+*/
+int proc_get_last_status();
+
+/**
+ Remove the specified job
+*/
+void job_free( job_t* j );
+/**
+ Create a new job
+*/
+job_t *job_create();
+
+/**
+ Return the job with the specified job id.
+ If id is -1, return the last job used.
+*/
+job_t *job_get(int id);
+
+/**
+ Return the job with the specified pid.
+*/
+job_t *job_get_from_pid(int pid);
+
+/**
+ Tests if the job is stopped
+ */
+int job_is_stopped( const job_t *j );
+
+/**
+ Tests if the job has completed
+ */
+int job_is_completed( const job_t *j );
+
+/**
+ Reassume a (possibly) stopped job. Put job j in the foreground. If
+ cont is nonzero, restore the saved terminal modes and send the
+ process group a SIGCONT signal to wake it up before we block.
+
+ \param j The job
+ \param cont Whether the function should wait for the job to complete before returning
+*/
+void job_continue( job_t *j, int cont );
+/**
+ Notify user of nog events. Notify the user about stopped or
+ terminated jobs. Delete terminated jobs from the active job list.
+*/
+int job_do_notification();
+/**
+ Signal handler for SIGCHLD. Mark any processes with relevant
+ information.
+
+*/
+void job_handle_signal( int signal, siginfo_t *info, void *con );
+
+/**
+ Clean up before exiting
+*/
+void proc_destroy();
+
+
+#ifdef HAVE__PROC_SELF_STAT
+/**
+ Use the procfs filesystem to look up how many jiffies of cpu time
+ was used by this process. This function is only available on
+ systems with the procfs file entry 'stat', i.e. Linux.
+*/
+unsigned long proc_get_jiffies( process_t *p );
+
+/**
+ Update process time usage for all processes by calling the
+ proc_get_jiffies function for every process of every job.
+*/
+void proc_update_jiffies();
+
+#endif
+
+/**
+ Perform a set of simple sanity checks on the job list. This
+ includes making sure that only one job is in the foreground, that
+ every process is in a valid state, etc.
+*/
+void proc_sanity_check();
+
diff --git a/reader.c b/reader.c
new file mode 100644
index 00000000..54c29f19
--- /dev/null
+++ b/reader.c
@@ -0,0 +1,3023 @@
+/** \file reader.c
+
+Functions for reading data from stdin and passing to the
+parser. If stdin is a keyboard, it supplies a killring, history,
+syntax highlighting, tab-completion and various other interactive features.
+
+Internally the interactive mode functions rely in the functions of the
+input library to read individual characters of input.
+
+
+Token search is handled incrementally. Actual searches are only done
+on when searching backwards, since the previous results are saved. The
+last search position is remembered and a new search continues from the
+last search position. All search results are saved in the list
+'search_prev'. When the user searches forward, i.e. presses Alt-down,
+the list is consulted for previous search result, and subsequent
+backwards searches are also handled by consultiung the list up until
+the end of the list is reached, at which point regular searching will
+commence.
+
+*/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <wctype.h>
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <time.h>
+#include <wchar.h>
+
+#include "util.h"
+#include "wutil.h"
+#include "highlight.h"
+#include "reader.h"
+#include "proc.h"
+#include "parser.h"
+#include "complete.h"
+#include "history.h"
+#include "common.h"
+#include "sanity.h"
+#include "env.h"
+#include "exec.h"
+#include "expand.h"
+#include "tokenizer.h"
+#include "kill.h"
+#include "input_common.h"
+#include "input.h"
+#include "function.h"
+#include "output.h"
+
+/**
+ Maximum length of prefix string when printing completion
+ list. Longer prefixes will be ellipsized.
+*/
+#define PREFIX_MAX_LEN 8
+
+/**
+ A simple prompt for reading shell commands that does not rely on
+ fish specific commands, meaning it will work even if fish is not
+ installed. This is used by read_i.
+*/
+#define DEFAULT_PROMPT L"whoami; echo @; hostname|cut -d . -f 1; echo \" \"; pwd; printf '> ';"
+
+/**
+ The default title for the reader. This is used by reader_readline.
+*/
+#define DEFAULT_TITLE L"echo $_ \" \"; pwd"
+
+/**
+ A struct describing the state of the interactive reader. These
+ states can be stacked, in case reader_readline is called from
+ input_read().
+*/
+typedef struct reader_data
+{
+ /**
+ Buffer containing the current commandline
+ */
+ wchar_t *buff;
+
+ /**
+ The output string, may be different than buff if buff can't fit on one line.
+ */
+ wchar_t *output;
+
+ /**
+ The number of characters used by the prompt
+ */
+ int prompt_width;
+
+ /**
+ Buffer containing the current search item
+ */
+ wchar_t *search_buff;
+ /**
+ Saved position used by token history search
+ */
+ int token_history_pos;
+
+ /**
+ Saved search string for token history search. Not handled by check_size.
+ */
+ const wchar_t *token_history_buff;
+
+ /**
+ List for storing previous search results. Used to avoid duplicates.
+ */
+ array_list_t search_prev;
+
+ /**
+ The current position in search_prev
+ */
+
+ int search_pos;
+
+ /**
+ Current size of the buffers
+ */
+ size_t buff_sz;
+
+ /**
+ Length of the command in buff. (Not the length of buff itself)
+ */
+
+ size_t buff_len;
+
+ /**
+ The current position of the cursor in buff.
+ */
+ size_t buff_pos;
+
+ /**
+ The current position of the cursor in output buffer.
+ */
+ size_t output_pos;
+
+ /**
+ Name of the current application
+ */
+ wchar_t *name;
+
+ /** The prompt text */
+ wchar_t *prompt;
+
+ /**
+ Color is the syntax highlighting for buff. The format is that
+ color[i] is the classification (according to the enum in
+ highlight.h) of buff[i].
+ */
+ int *color;
+
+ /**
+ New color buffer, used for syntax highlighting.
+ */
+ int *new_color;
+
+ /**
+ Color for the actual output string.
+ */
+ int *output_color;
+
+ /**
+ Should the prompt command be reexecuted on the next repaint
+ */
+ int exec_prompt;
+
+ /**
+ Function for tab completion
+ */
+ void (*complete_func)( const wchar_t *,
+ array_list_t * );
+
+ /**
+ Function for syntax highlighting
+ */
+ void (*highlight_func)( wchar_t *,
+ int *,
+ int,
+ array_list_t * );
+
+ /**
+ Function for testing if the string can be returned
+ */
+ int (*test_func)( wchar_t * );
+
+ /**
+ When this is true, the reader will exit
+ */
+ int end_loop;
+
+ /**
+ Pointer to previous reader_data
+ */
+ struct reader_data *next;
+}
+ reader_data_t;
+
+/**
+ The current interactive reading context
+*/
+static reader_data_t *data=0;
+
+
+/**
+ Flag for ending non-interactive shell
+*/
+static int end_loop = 0;
+
+
+/**
+ This struct should be continually updated by signals as the term resizes, and as such always contain the correct current size.
+*/
+static struct winsize termsize;
+
+/**
+ This flag is set when a WINCH signal was recieved.
+*/
+static int new_size=0;
+
+
+/**
+ The list containing names of files that are being parsed
+*/
+static array_list_t current_filename;
+
+/**
+ These status buffers are used to check if any output has occurred
+ other than from fish's main loop, in which case we need to redraw.
+*/
+static struct stat prev_buff_1, prev_buff_2, post_buff_1, post_buff_2;
+
+
+
+/**
+ List containing strings which make up the prompt
+*/
+static array_list_t prompt_list;
+
+/**
+ Stores the previous termios mode so we can reset the modes when
+ we execute programs and when the shell exits.
+*/
+static struct termios saved_modes;
+
+
+/**
+ Store the pid of the parent process, so the exit function knows whether it should reset the terminal or not.
+*/
+static pid_t original_pid;
+
+/**
+ Interrupted flag. Set to 1 when the user presses \^C.
+*/
+static int interupted;
+
+/*
+ Prototypes for a bunch of functions defined later on.
+*/
+
+static void reader_save_status();
+static void reader_check_status();
+static void reader_super_highlight_me_plenty( wchar_t * buff, int *color, int pos, array_list_t *error );
+static void handle_winch( int sig );
+
+
+
+
+static struct termios old_modes;
+
+static void term_donate()
+{
+ tcgetattr(0,&old_modes); /* get the current terminal modes */
+
+ set_color(FISH_COLOR_NORMAL, FISH_COLOR_NORMAL);
+
+ while( 1 )
+ {
+ if( tcsetattr(0,TCSANOW,&saved_modes) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not set terminal mode for new job" );
+ wperror( L"tcsetattr" );
+ break;
+ }
+ }
+ else
+ break;
+ }
+
+
+}
+
+static void term_steal()
+{
+
+ while( 1 )
+ {
+ if( tcsetattr(0,TCSANOW,&shell_modes) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not set terminal mode for shell" );
+ wperror( L"tcsetattr" );
+ break;
+ }
+ }
+ else
+ break;
+ }
+
+ handle_winch( 0 );
+
+ if( tcsetattr(0,TCSANOW,&old_modes)) /* return to previous mode */
+ {
+ wperror(L"tcsetattr");
+ exit(1);
+ }
+
+}
+
+/**
+ Test if there is space between the time fields of struct stat to
+ use for sub second information. If so, we assume this space
+ contains the desired information.
+*/
+static int room_for_usec(struct stat *st)
+{
+ int res = ((&(st->st_atime) + 2) == &(st->st_mtime) &&
+ (&(st->st_atime) + 4) == &(st->st_ctime));
+ return res;
+}
+
+/**
+ string_buffer used as temporary storage for the reader_readline function
+*/
+static string_buffer_t *readline_buffer=0;
+
+int reader_get_width()
+{
+ return termsize.ws_col;
+}
+
+
+int reader_get_height()
+{
+ return termsize.ws_row;
+}
+
+
+wchar_t *reader_current_filename()
+{
+ return (wchar_t *)al_peek( &current_filename );
+}
+
+
+void reader_push_current_filename( wchar_t *fn )
+{
+ al_push( &current_filename, fn );
+}
+
+
+wchar_t *reader_pop_current_filename()
+{
+ return (wchar_t *)al_pop( &current_filename );
+}
+
+
+/**
+ Make sure buffers are large enough to hold current data plus one extra character.
+*/
+static int check_size()
+{
+ if( data->buff_sz < data->buff_len + 2 )
+ {
+ data->buff_sz = maxi( 128, data->buff_len*2 );
+
+ data->buff = realloc( data->buff,
+ sizeof(wchar_t)*data->buff_sz);
+ data->search_buff = realloc( data->search_buff,
+ sizeof(wchar_t)*data->buff_sz);
+ data->output = realloc( data->output,
+ sizeof(wchar_t)*data->buff_sz);
+
+ data->color = realloc( data->color,
+ sizeof(int)*data->buff_sz);
+ data->new_color = realloc( data->new_color,
+ sizeof(int)*data->buff_sz);
+ data->output_color = realloc( data->output_color,
+ sizeof(int)*data->buff_sz);
+
+ if( data->buff==0 ||
+ data->search_buff==0 ||
+ data->color==0 ||
+ data->new_color == 0 )
+ {
+ die_mem();
+
+ }
+ }
+ return 1;
+}
+
+/**
+ Check if the screen is not wide enough for the buffer, which means
+ the buffer must be scrolled on input and cursor movement.
+*/
+static int force_repaint()
+{
+ int max_width = reader_get_width() - data->prompt_width;
+ int pref_width = my_wcswidth( data->buff ) + (data->buff_pos==data->buff_len);
+ return pref_width >= max_width;
+}
+
+
+/**
+ Calculate what part of the buffer should be visible
+
+ \return returns 1 screen needs repainting, 0 otherwise
+*/
+static int calc_output()
+{
+ int max_width = reader_get_width() - data->prompt_width;
+ int pref_width = my_wcswidth( data->buff ) + (data->buff_pos==data->buff_len);
+ if( pref_width <= max_width )
+ {
+ wcscpy( data->output, data->buff );
+ memcpy( data->output_color, data->color, sizeof(int) * data->buff_len );
+ data->output_pos=data->buff_pos;
+
+ return 1;
+ }
+ else
+ {
+ int offset = data->buff_pos;
+ int offset_end = data->buff_pos;
+ int w = 0;
+ wchar_t *pos=data->output;
+ *pos=0;
+
+
+ w = (data->buff_pos==data->buff_len)?1:wcwidth( data->buff[offset] );
+ while( 1 )
+ {
+ int inc=0;
+ int ellipsis_width;
+
+ ellipsis_width = wcwidth(ellipsis_char)*((offset?1:0)+(offset_end<data->buff_len?1:0));
+
+ if( offset > 0 && (ellipsis_width + w + wcwidth( data->buff[offset-1] ) <= max_width ) )
+ {
+ inc=1;
+ offset--;
+ w+= wcwidth( data->buff[offset]);
+ }
+
+ ellipsis_width = wcwidth(ellipsis_char)*((offset?1:0)+(offset_end<data->buff_len?1:0));
+
+ if( offset_end < data->buff_len && (ellipsis_width + w + wcwidth( data->buff[offset_end+1] ) <= max_width ) )
+ {
+ inc = 1;
+ offset_end++;
+ w+= wcwidth( data->buff[offset_end]);
+ }
+
+ if( !inc )
+ break;
+
+ }
+
+ data->output_pos = data->buff_pos - offset + (offset?1:0);
+
+ if( offset )
+ {
+ data->output[0]=ellipsis_char;
+ data->output[1]=0;
+
+ }
+
+ wcsncat( data->output,
+ data->buff+offset,
+ offset_end-offset );
+
+ if( offset_end<data->buff_len )
+ {
+ int l = wcslen(data->output);
+
+ data->output[l]=ellipsis_char;
+ data->output[l+1]=0;
+
+ }
+
+ *data->output_color=HIGHLIGHT_NORMAL;
+
+ memcpy( data->output_color+(offset?1:0),
+ data->color+offset,
+ sizeof(int) * (data->buff_len-offset) );
+ return 1;
+ }
+}
+
+
+/**
+ Compare two completions, ignoring their description.
+*/
+static int fldcmp( wchar_t *a, wchar_t *b )
+{
+ while( 1 )
+ {
+ if( *a != *b )
+ return *a-*b;
+ if( ( (*a == COMPLETE_SEP) || (*a == L'\0') ) &&
+ ( (*b == COMPLETE_SEP) || (*b == L'\0') ) )
+ return 0;
+ a++;
+ b++;
+ }
+
+}
+
+/**
+ Remove any duplicate completions in the list. This relies on the
+ list first beeing sorted.
+*/
+static void remove_duplicates( array_list_t *l )
+{
+ int in, out;
+ wchar_t *prev;
+ if( al_get_count( l ) == 0 )
+ return;
+
+ prev = (wchar_t *)al_get( l, 0 );
+ for( in=1, out=1; in < al_get_count( l ); in++ )
+ {
+ wchar_t *curr = (wchar_t *)al_get( l, in );
+ if( fldcmp( prev, curr )==0 )
+ {
+ free( curr );
+ }
+ else
+ {
+ al_set( l, out++, curr );
+ prev = curr;
+ }
+ }
+ al_truncate( l, out );
+}
+
+
+/**
+ Translate a highlighting code ()Such as as returned by the highlight function
+ into a color code which is then passed on to set_color.
+*/
+static void set_color_translated( int c )
+{
+ set_color( highlight_get_color( c & 0xff ),
+ highlight_get_color( (c>>8)&0xff ) );
+}
+
+int reader_interupted()
+{
+ int res=interupted;
+ if( res )
+ interupted=0;
+ return res;
+}
+
+void reader_write_title()
+{
+ wchar_t *title;
+ array_list_t l;
+ wchar_t *term = env_get( L"TERM" );
+
+ /*
+ This is a pretty lame heuristic for detecting terminals that do
+ not support setting the title. If we recognise the terminal name
+ as that of a virtual terminal, we assume it supports setting the
+ title. Otherwise we check the ttyname.
+ */
+ if( !term || !contains_str( term, L"xterm", L"screen", L"nxterm", L"rxvt", 0 ) )
+ {
+ char *n = ttyname( STDIN_FILENO );
+ if( strstr( n, "tty" ) || strstr( n, "/vc/") )
+ return;
+ }
+
+ title = function_exists( L"fish_title" )?L"fish_title":DEFAULT_TITLE;
+
+ if( wcslen( title ) ==0 )
+ return;
+
+ al_init( &l );
+
+ if( exec_subshell( title, &l ) != -1 )
+ {
+ int i;
+ writestr( L"\e]2;" );
+ for( i=0; i<al_get_count( &l ); i++ )
+ {
+ writestr( (wchar_t *)al_get( &l, i ) );
+ }
+ writestr( L"\7" );
+ }
+ al_foreach( &l, (void (*)(const void *))&free );
+ al_destroy( &l );
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+}
+
+/**
+ Write the prompt to screen. If data->exec_prompt is set, the prompt
+ command is first evaluated, and the title will be reexecuted as
+ well.
+*/
+static void write_prompt()
+{
+ int i;
+ set_color( FISH_COLOR_NORMAL, FISH_COLOR_NORMAL );
+
+ /*
+ Check if we need to reexecute the prompt command
+ */
+ if( data->exec_prompt )
+ {
+
+ al_foreach( &prompt_list, (void (*)(const void *))&free );
+ al_truncate( &prompt_list, 0 );
+
+ if( data->prompt )
+ {
+ if( exec_subshell( data->prompt, &prompt_list ) == -1 )
+ {
+ /* If executing the prompt fails, make sure we at least don't print any junk */
+ al_foreach( &prompt_list, (void (*)(const void *))&free );
+ al_destroy( &prompt_list );
+ al_init( &prompt_list );
+ }
+ }
+ data->prompt_width=0;
+ for( i=0; i<al_get_count( &prompt_list ); i++ )
+ {
+ wchar_t *next = (wchar_t *)al_get( &prompt_list, i );
+ if( *next == L'\e' )
+ continue;
+ data->prompt_width += my_wcswidth( next );
+ }
+
+ data->exec_prompt = 0;
+ reader_write_title();
+ }
+
+ /*
+ Write out the prompt strings
+ */
+
+ for( i=0; i<al_get_count( &prompt_list); i++ )
+ {
+ writestr( (wchar_t *)al_get( &prompt_list, i ) );
+ }
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+
+}
+
+/**
+ Write the whole command line (but not the prompt) to the screen. Do
+ not set the cursor correctly afterwards.
+*/
+static void write_cmdline()
+{
+ int i;
+
+ for( i=0; data->output[i]; i++ )
+ {
+ set_color_translated( data->output_color[i] );
+ writech( data->output[i] );
+ }
+}
+
+/**
+ perm_left_cursor and parm_right_cursor don't seem to be defined as
+ often as cursor_left and cursor_right, so we use this workalike.
+*/
+static void move_cursor( int steps )
+{
+ int i;
+
+ if( steps < 0 ){
+ for( i=0; i>steps; i--)
+ {
+ writembs(cursor_left);
+ }
+ }
+ else
+ for( i=0; i<steps; i++)
+ writembs(cursor_right);
+}
+
+
+void reader_init()
+{
+ al_init( &current_filename);
+}
+
+
+void reader_destroy()
+{
+ al_destroy( &current_filename);
+ if( readline_buffer )
+ {
+ sb_destroy( readline_buffer );
+ free( readline_buffer );
+ readline_buffer=0;
+ }
+}
+
+
+void reader_exit( int do_exit )
+{
+ if( is_interactive )
+ data->end_loop=do_exit;
+ else
+ end_loop=do_exit;
+}
+
+void repaint()
+{
+ int steps;
+
+ calc_output();
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+ writech('\r');
+ writembs(clr_eol);
+ write_prompt();
+ write_cmdline();
+
+/*
+ fwprintf( stderr, L"Width of \'%ls\' (length is %d): ",
+ &data->buff[data->buff_pos],
+ wcslen(&data->buff[data->buff_pos]));
+ fwprintf( stderr, L"%d\n", my_wcswidth(&data->buff[data->buff_pos]));
+*/
+
+ steps = my_wcswidth( &data->output[data->output_pos]);
+ if( steps )
+ move_cursor( -steps );
+
+ set_color( FISH_COLOR_NORMAL, -1 );
+ reader_save_status();
+}
+
+/**
+ Make sure color values are correct, and repaint if they are not.
+*/
+static void check_colors()
+{
+ reader_super_highlight_me_plenty( data->buff, data->new_color, data->buff_pos, 0 );
+ if( memcmp( data->new_color, data->color, sizeof(int)*data->buff_len )!=0 )
+ {
+ memcpy( data->color, data->new_color, sizeof(int)*data->buff_len );
+
+ repaint();
+ }
+}
+
+/**
+ Stat stdout and stderr and save result.
+
+ This should be done before calling a function that may cause output.
+*/
+
+static void reader_save_status()
+{
+
+#if (defined(__FreeBSD__) || defined(__NetBSD__))
+ /*
+ This futimes call tries to trick the system into using st_mtime
+ as a tampering flag. This of course only works on systems where
+ futimes is defined, but it should make the status saving stuff
+ failsafe.
+ */
+ struct timeval t=
+ {
+ time(0)-1,
+ 0
+ }
+ ;
+
+ if( futimes( 1, &t ) || futimes( 2, &t ) )
+ {
+ wperror( L"futimes" );
+ }
+#endif
+
+ fstat( 1, &prev_buff_1 );
+ fstat( 2, &prev_buff_2 );
+}
+
+/**
+ Stat stdout and stderr and compare result to previous result in
+ reader_save_status. Repaint if modification time has changed.
+
+ Unfortunately, for some reason this call seems to give a lot of
+ false positives, at least under Linux.
+*/
+
+static void reader_check_status()
+{
+ fflush( stdout );
+ fflush( stderr );
+
+ fstat( 1, &post_buff_1 );
+ fstat( 2, &post_buff_2 );
+
+ int changed = ( prev_buff_1.st_mtime != post_buff_1.st_mtime ) ||
+ ( prev_buff_2.st_mtime != post_buff_2.st_mtime );
+
+ if (room_for_usec( &post_buff_1))
+ {
+ changed = changed || ( (&prev_buff_1.st_mtime)[1] != (&post_buff_1.st_mtime)[1] ) ||
+ ( (&prev_buff_2.st_mtime)[1] != (&post_buff_2.st_mtime)[1] );
+ }
+
+ if( changed )
+ {
+ repaint();
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+ }
+}
+
+/**
+ Remove the previous character in the character buffer and on the
+ screen using syntax highlighting, etc.
+*/
+static void remove_backward()
+{
+ int wdt;
+
+ if( data->buff_pos <= 0 )
+ return;
+
+ if( data->buff_pos < data->buff_len )
+ {
+ memmove( &data->buff[data->buff_pos-1],
+ &data->buff[data->buff_pos],
+ sizeof(wchar_t)*(data->buff_len-data->buff_pos+1) );
+
+ memmove( &data->color[data->buff_pos-1],
+ &data->color[data->buff_pos],
+ sizeof(wchar_t)*(data->buff_len-data->buff_pos+1) );
+ }
+ data->buff_pos--;
+ data->buff_len--;
+
+ wdt=wcwidth(data->buff[data->buff_pos]);
+ move_cursor(-wdt);
+ data->buff[data->buff_len]='\0';
+// wcscpy(data->search_buff,data->buff);
+
+ reader_super_highlight_me_plenty( data->buff,
+ data->new_color,
+ data->buff_pos,
+ 0 );
+ if( (!force_repaint()) && ( memcmp( data->new_color,
+ data->color,
+ sizeof(int)*data->buff_len )==0 ) &&
+ ( delete_character != 0) && (wdt==1) )
+ {
+ /*
+ Only do this if delete mode functions, and only for a column
+ wide characters, since terminfo seems to break for other
+ characters. This last check should be removed when terminfo
+ is fixed.
+ */
+ if( enter_delete_mode != 0 )
+ writembs(enter_delete_mode);
+ writembs(delete_character);
+ if( exit_delete_mode != 0 )
+ writembs(exit_delete_mode);
+ }
+ else
+ {
+ memcpy( data->color,
+ data->new_color,
+ sizeof(int) * data->buff_len );
+
+ repaint();
+ }
+}
+
+/**
+ Remove the current character in the character buffer and on the
+ screen using syntax highlighting, etc.
+*/
+static void remove_forward()
+{
+ if( data->buff_pos >= data->buff_len )
+ return;
+
+ move_cursor(wcwidth(data->buff[data->buff_pos]));
+ data->buff_pos++;
+
+ remove_backward();
+}
+
+/**
+ Insert the character into the command line buffer and print it to
+ the screen using syntax highlighting, etc.
+*/
+static int insert_char( int c )
+{
+
+ if( !check_size() )
+ return 0;
+
+ /* Insert space for extra character at the right position */
+ if( data->buff_pos < data->buff_len )
+ {
+ memmove( &data->buff[data->buff_pos+1],
+ &data->buff[data->buff_pos],
+ sizeof(wchar_t)*(data->buff_len-data->buff_pos) );
+
+ memmove( &data->color[data->buff_pos+1],
+ &data->color[data->buff_pos],
+ sizeof(int)*(data->buff_len-data->buff_pos) );
+ }
+ /* Set character */
+ data->buff[data->buff_pos]=c;
+
+ /* Update lengths, etc */
+ data->buff_pos++;
+ data->buff_len++;
+ data->buff[data->buff_len]='\0';
+
+ /* Syntax highlight */
+
+ reader_super_highlight_me_plenty( data->buff,
+ data->new_color,
+ data->buff_pos-1,
+ 0 );
+ data->color[data->buff_pos-1] = data->new_color[data->buff_pos-1];
+
+ /* Check if the coloring has changed */
+ if( (!force_repaint()) && ( memcmp( data->new_color,
+ data->color,
+ sizeof(int)*data->buff_len )==0 ) &&
+ ( insert_character ||
+ ( data->buff_pos == data->buff_len ) ||
+ enter_insert_mode) )
+ {
+ /*
+ Colors look ok, so we set the right color and insert a
+ character
+ */
+ set_color_translated( data->color[data->buff_pos-1] );
+ if( data->buff_pos < data->buff_len )
+ {
+ if( enter_insert_mode != 0 )
+ writembs(enter_insert_mode);
+ else
+ writembs(insert_character);
+ writech(c);
+ if( insert_padding != 0 )
+ writembs(insert_padding);
+ if( exit_insert_mode != 0 )
+ writembs(exit_insert_mode);
+ }
+ else
+ writech(c);
+ set_color( FISH_COLOR_NORMAL, -1 );
+ }
+ else
+ {
+ /* Nope, colors are off, so we repaint the entire command line */
+ memcpy( data->color, data->new_color, sizeof(int) * data->buff_len );
+
+ repaint();
+ }
+// wcscpy(data->search_buff,data->buff);
+ return 1;
+}
+
+/**
+ Insert the characters of the string into the command line buffer
+ and print them to the screen using syntax highlighting, etc.
+*/
+static int insert_str(wchar_t *str)
+{
+ while( (*str)!=0 )
+ if(!insert_char( *str++ ))
+ return 0;
+ return 1;
+}
+
+/**
+ Calculate the length of the common prefix substring of two strings.
+*/
+static int comp_len( wchar_t *a, wchar_t *b )
+{
+ int i;
+ for( i=0;
+ a[i] != '\0' && b[i] != '\0' && a[i]==b[i];
+ i++ )
+ ;
+ return i;
+}
+
+/**
+ Find the outermost quoting style of current token. Returns 0 if token is not quoted.
+
+*/
+static wchar_t get_quote( wchar_t *cmd, int l )
+{
+ int i=0;
+ wchar_t res=0;
+
+// fwprintf( stderr, L"Woot %ls\n", cmd );
+
+ while( 1 )
+ {
+ if( !cmd[i] )
+ break;
+
+ if( cmd[i] == L'\'' || cmd[i] == L'\"' )
+ {
+ wchar_t *end = quote_end( &cmd[i] );
+ //fwprintf( stderr, L"Jump %d\n", end-cmd );
+ if(( end == 0 ) || (!*end) || (end-cmd > l))
+ {
+ res = cmd[i];
+ break;
+ }
+ i = end-cmd+1;
+ }
+ else
+ i++;
+
+ }
+ return res;
+}
+
+/**
+ Calculates information on the parameter at the specified index.
+
+ \param cmd The command to be analyzed
+ \param pos An index in the string which is inside the parameter
+ \param quote If not 0, store the type of quote this parameter has, can be either ', " or \\0, meaning the string is not quoted.
+ \param offset If not 0, get_param will store a pointer to the beginning of the parameter.
+ \param string If not o, get_parm will store a copy of the parameter string as returned by the tokenizer.
+ \param type If not 0, get_param will store the token type as returned by tok_last.
+*/
+static void get_param( wchar_t *cmd,
+ int pos,
+ wchar_t *quote,
+ wchar_t **offset,
+ wchar_t **string,
+ int *type )
+{
+ int prev_pos=0;
+ wchar_t last_quote = '\0';
+ int unfinished;
+
+ tokenizer tok;
+ tok_init( &tok, cmd, TOK_ACCEPT_UNFINISHED );
+
+ for( ; tok_has_next( &tok ); tok_next( &tok ) )
+ {
+ if( tok_get_pos( &tok ) > pos )
+ break;
+
+ if( tok_last_type( &tok ) == TOK_STRING )
+ last_quote = get_quote( tok_last( &tok ),
+ pos - tok_get_pos( &tok ) );
+
+ if( type != 0 )
+ *type = tok_last_type( &tok );
+ if( string != 0 )
+ wcscpy( *string, tok_last( &tok ) );
+ prev_pos = tok_get_pos( &tok );
+ }
+
+ tok_destroy( &tok );
+
+ wchar_t c = cmd[pos];
+ cmd[pos]=0;
+ int cmdlen = wcslen( cmd );
+ unfinished = (cmdlen==0);
+ if( !unfinished )
+ {
+ unfinished = (quote != 0);
+
+ if( !unfinished )
+ {
+ if( wcschr( L" \t\n\r", cmd[cmdlen-1] ) != 0 )
+ {
+ if( ( cmdlen == 1) || (cmd[cmdlen-2] != L'\\') )
+ {
+ unfinished=1;
+ }
+ }
+ }
+ }
+
+ if( quote )
+ *quote = last_quote;
+
+ if( offset != 0 )
+ {
+ if( !unfinished )
+ {
+ while( (cmd[prev_pos] != 0) && (wcschr( L";|",cmd[prev_pos])!= 0) )
+ prev_pos++;
+
+ *offset = cmd + prev_pos;
+ }
+ else
+ {
+ *offset = cmd + pos;
+ }
+ }
+ cmd[pos]=c;
+}
+
+/**
+ Insert the string at the current cursor position. The function
+ checks if the string is quoted or not and correctly escapes the
+ string.
+
+ \param val the string to insert
+ \param is_complete Whether this completion is the whole string or
+ just the common prefix of several completions. If the former, end by
+ printing a space (and an end quote if the parameter is quoted).
+*/
+static void completion_insert( wchar_t *val, int is_complete )
+{
+ wchar_t *replaced;
+
+ wchar_t quote;
+
+ get_param( data->buff,
+ data->buff_pos,
+ &quote,
+ 0, 0, 0 );
+
+ if( quote == L'\0' )
+ {
+ replaced = expand_escape( wcsdup(val), 1 );
+ }
+ else
+ {
+ int unescapable=0;
+
+ wchar_t *pin, *pout;
+
+ replaced = pout =
+ malloc( sizeof(wchar_t)*(wcslen(val) + 1) );
+
+ for( pin=val; *pin; pin++ )
+ {
+ switch( *pin )
+ {
+ case L'\n':
+ case L'\t':
+ case L'\b':
+ case L'\r':
+ unescapable=1;
+ break;
+ default:
+ *pout++ = *pin;
+ break;
+ }
+ }
+ if( unescapable )
+ {
+ free( replaced );
+ wchar_t *tmp = expand_escape( wcsdup(val), 1 );
+ replaced = wcsdupcat( L" ", tmp );
+ free( tmp);
+ replaced[0]=quote;
+ }
+ else
+ *pout = 0;
+ }
+
+ if( insert_str( replaced ) )
+ {
+
+ if( is_complete ) /* Print trailing space since this is the only completion */
+ {
+
+ if( (quote) &&
+ (data->buff[data->buff_pos] != quote ) ) /* This is a quoted parameter, first print a quote */
+ {
+ insert_char( quote );
+ }
+ insert_char( L' ' );
+ }
+ }
+
+ free(replaced);
+}
+
+/**
+ Run the fish_pager command to display the completion list, and
+ insert the result into the backbuffer.
+*/
+
+static void run_pager( wchar_t *prefix, int is_quoted, array_list_t *comp )
+{
+ int i;
+ string_buffer_t cmd;
+ wchar_t * prefix_esc;
+
+ if( !prefix || (wcslen(prefix)==0))
+ prefix_esc = wcsdup(L"\"\"");
+ else
+ prefix_esc = escape( wcsdup(prefix),1);
+
+ sb_init( &cmd );
+ sb_printf( &cmd,
+ L"fish_pager %d %ls",
+ is_quoted,
+ prefix_esc );
+
+ free( prefix_esc );
+
+ for( i=0; i<al_get_count( comp); i++ )
+ {
+ wchar_t *el = escape( wcsdup((wchar_t*)al_get( comp, i )),1);
+ sb_printf( &cmd, L" %ls", el );
+ free(el);
+ }
+
+ term_donate();
+
+ io_data_t *out = exec_make_io_buffer();
+
+ eval( (wchar_t *)cmd.buff, out, TOP);
+ term_steal();
+
+ int nil=0;
+ b_append( out->out_buffer, &nil, 1 );
+
+ wchar_t *tmp;
+ wchar_t *str = str2wcs((char *)out->out_buffer->buff);
+
+ if( str )
+ {
+ for( tmp = str + wcslen(str)-1; tmp >= str; tmp-- )
+ {
+ input_unreadch( *tmp );
+ }
+ free( str );
+ }
+
+ exec_free_io_buffer( out);
+
+}
+
+/**
+ Handle the list of completions. This means the following:
+
+ - If the list is empty, flash the terminal.
+ - If the list contains one element, write the whole element, and if
+ the element does not end on a '/', '@', ':', or a '=', also write a trailing
+ space.
+ - If the list contains multiple elements with a common prefix, write
+ the prefix.
+ - If the list contains multiple elements without
+ a common prefix, call run_pager to display a list of completions
+
+ \param comp the list of completion strings
+*/
+
+
+static int handle_completions( array_list_t *comp )
+{
+ int i;
+
+ if( al_get_count( comp ) == 0 )
+ {
+ if( flash_screen != 0 )
+ writembs( flash_screen );
+ return 0;
+ }
+ else if( al_get_count( comp ) == 1 )
+ {
+ wchar_t *comp_str = wcsdup((wchar_t *)al_get( comp, 0 ));
+ wchar_t *woot = wcschr( comp_str, COMPLETE_SEP );
+ if( woot != 0 )
+ *woot = L'\0';
+ completion_insert( comp_str,
+ ( wcslen(comp_str) == 0 ) ||
+ ( wcschr( L"/=@:",
+ comp_str[wcslen(comp_str)-1] ) == 0 ) );
+ free( comp_str );
+ return 1;
+ }
+ else
+ {
+ wchar_t *base = wcsdup( (wchar_t *)al_get( comp, 0 ) );
+ int len = wcslen( base );
+ for( i=1; i<al_get_count( comp ); i++ )
+ {
+ int new_len = comp_len( base, (wchar_t *)al_get( comp, i ) );
+ len = new_len < len ? new_len: len;
+ }
+ if( len > 0 )
+ {
+ base[len]=L'\0';
+ wchar_t *woot = wcschr( base, COMPLETE_SEP );
+ if( woot != 0 )
+ *woot = L'\0';
+ completion_insert(base, 0);
+ }
+ else
+ {
+ /*
+ There is no common prefix in the completions, and show_list
+ is true, so we print the list
+ */
+ int len;
+ wchar_t * prefix;
+ wchar_t * prefix_start;
+ get_param( data->buff,
+ data->buff_pos,
+ 0,
+ &prefix_start,
+ 0,
+ 0 );
+
+
+ len = &data->buff[data->buff_pos]-prefix_start;
+
+ if( len <= PREFIX_MAX_LEN )
+ {
+ prefix = malloc( sizeof(wchar_t)*(len+1) );
+ wcsncpy( prefix, prefix_start, len );
+ prefix[len]=L'\0';
+ }
+ else
+ {
+ wchar_t tmp[2]=
+ {
+ ellipsis_char,
+ 0
+ }
+ ;
+
+ prefix = wcsdupcat( tmp,
+ prefix_start + (len - PREFIX_MAX_LEN+1) );
+ }
+
+ {
+ int is_quoted;
+
+ wchar_t quote;
+ get_param( data->buff, data->buff_pos, &quote, 0, 0, 0 );
+ is_quoted = (quote != L'\0');
+
+ writech(L'\n');
+
+ run_pager( prefix, is_quoted, comp );
+
+
+ /*
+ Try to print a list of completions. First try with five
+ columns, then four, etc. completion_try_print always
+ succeeds with one column.
+ */
+/*
+*/
+ }
+
+ free( prefix );
+
+ repaint();
+
+ }
+
+ free( base );
+ return len;
+ }
+}
+
+/**
+ Respond to a winch signal by checking the terminal size
+*/
+static void handle_winch( int sig )
+{
+ if (ioctl(1,TIOCGWINSZ,&termsize)!=0)
+ {
+ return;
+ }
+ new_size=1;
+}
+
+
+void check_winch()
+{
+ if( new_size )
+ {
+ wchar_t tmp[64];
+ new_size=0;
+ swprintf(tmp, 64, L"%d", termsize.ws_row );
+ env_set( L"LINES", tmp, ENV_GLOBAL );
+ swprintf(tmp, 64, L"%d", termsize.ws_col );
+ env_set( L"COLUMNS", tmp, ENV_GLOBAL );
+ }
+}
+
+/**
+ Interactive mode ^C handler. Respond to int signal by setting
+ interrupted-flag and stopping all loops and conditionals.
+*/
+static void handle_int( int sig )
+{
+ interupted=1;
+
+ block_t *c = current_block;
+ while( c )
+ {
+ c->skip=1;
+ c=c->outer;
+ }
+
+}
+
+/**
+ Reset the terminal. This function is placed in the list of
+ functions to call when exiting by using the atexit function. It
+ checks whether it is the original parent process that is exiting
+ and not a subshell, and if it is the parent, it restores the
+ terminal.
+*/
+static void exit_func()
+{
+ if( getpid() == original_pid )
+ tcsetattr(0, TCSANOW, &saved_modes);
+}
+
+/**
+ Sets appropriate signal handlers.
+*/
+static void set_signal_handlers()
+{
+ struct sigaction act;
+ sigemptyset( & act.sa_mask );
+ act.sa_flags=0;
+ act.sa_handler=SIG_DFL;
+
+ /*
+ First reset everything
+ */
+ sigaction( SIGINT, &act, 0);
+ sigaction( SIGQUIT, &act, 0);
+ sigaction( SIGTSTP, &act, 0);
+ sigaction( SIGTTIN, &act, 0);
+ sigaction( SIGTTOU, &act, 0);
+ sigaction( SIGCHLD, &act, 0);
+
+ if( is_interactive )
+ {
+
+ /*
+ Interactive mode. Ignore interactive signals. We are a
+ shell, we know whats best for the user. ;-)
+ */
+
+ act.sa_handler=SIG_IGN;
+
+ sigaction( SIGINT, &act, 0);
+ sigaction( SIGQUIT, &act, 0);
+ sigaction( SIGTSTP, &act, 0);
+ sigaction( SIGTTIN, &act, 0);
+ sigaction( SIGTTOU, &act, 0);
+
+ act.sa_handler = &handle_int;
+ act.sa_flags = 0;
+ if( sigaction( SIGINT, &act, 0) )
+ {
+ wperror( L"sigaction" );
+ exit(1);
+ }
+
+ act.sa_sigaction = &job_handle_signal;
+ act.sa_flags = SA_SIGINFO;
+ if( sigaction( SIGCHLD, &act, 0) )
+ {
+ wperror( L"sigaction" );
+ exit(1);
+ }
+
+ act.sa_flags = 0;
+ act.sa_handler= &handle_winch;
+ if( sigaction( SIGWINCH, &act, 0 ) )
+ {
+ wperror( L"sigaction" );
+ exit(1);
+ }
+
+ }
+ else
+ {
+ /*
+ Non-interactive. Ignore interrupt, check exit status of
+ processes to determine result instead.
+ */
+ act.sa_handler=SIG_IGN;
+
+ sigaction( SIGINT, &act, 0);
+ sigaction( SIGQUIT, &act, 0);
+
+ act.sa_handler=SIG_DFL;
+
+ act.sa_sigaction = &job_handle_signal;
+ act.sa_flags = SA_SIGINFO;
+ if( sigaction( SIGCHLD, &act, 0) )
+ {
+ wperror( L"sigaction" );
+ exit(1);
+ }
+ }
+}
+
+
+/**
+ Initialize data for interactive use
+*/
+static void reader_interactive_init()
+{
+ /* See if we are running interactively. */
+ pid_t shell_pgid;
+
+ input_init();
+ kill_init();
+ shell_pgid = getpgrp ();
+
+ /* Loop until we are in the foreground. */
+ while (tcgetpgrp( 0 ) != shell_pgid)
+ {
+ kill (- shell_pgid, SIGTTIN);
+ }
+
+ /* Put ourselves in our own process group. */
+ shell_pgid = getpid ();
+ if( getpgrp() != shell_pgid )
+ {
+ if (setpgid (shell_pgid, shell_pgid) < 0)
+ {
+ debug( 1,
+ L"Couldn't put the shell in its own process group");
+ wperror( L"setpgid" );
+ exit (1);
+ }
+ }
+
+ /* Grab control of the terminal. */
+ if( tcsetpgrp (STDIN_FILENO, shell_pgid) )
+ {
+ debug( 1,
+ L"Couldn't grab control of terminal" );
+ wperror( L"tcsetpgrp" );
+ exit(1);
+ }
+
+
+ al_init( &prompt_list );
+ history_init();
+
+
+ handle_winch( 0 ); /* Set handler for window change events */
+ check_winch();
+
+ tcgetattr(0,&shell_modes); /* get the current terminal modes */
+ memcpy( &saved_modes,
+ &shell_modes,
+ sizeof(saved_modes)); /* save a copy so we can reset the terminal later */
+
+ shell_modes.c_lflag &= ~ICANON; /* turn off canonical mode */
+ shell_modes.c_lflag &= ~ECHO; /* turn off echo mode */
+ shell_modes.c_cc[VMIN]=1;
+ shell_modes.c_cc[VTIME]=0;
+
+ if( tcsetattr(0,TCSANOW,&shell_modes)) /* set the new modes */
+ {
+ wperror(L"tcsetattr");
+ exit(1);
+ }
+
+ /* We need to know the parents pid so we'll know if we are a subshell */
+ original_pid = getpid();
+
+ if( atexit( &exit_func ) )
+ debug( 1, L"Could not set exit function" );
+
+ env_set( L"_", L"fish", ENV_GLOBAL );
+}
+
+/**
+ Destroy data for interactive use
+*/
+static void reader_interactive_destroy()
+{
+ kill_destroy();
+ al_foreach( &prompt_list, (void (*)(const void *))&free );
+ al_destroy( &prompt_list );
+ history_destroy();
+
+ writestr( L"\n" );
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+ input_destroy();
+}
+
+
+void reader_sanity_check()
+{
+ if( is_interactive)
+ {
+ if(!( data->buff_pos <= data->buff_len ))
+ sanity_lose();
+ if(!( data->buff_len == wcslen( data->buff ) ))
+ sanity_lose();
+ }
+}
+
+void reader_current_subshell_extent( wchar_t **a, wchar_t **b )
+{
+ wchar_t *buffcpy = wcsdup( data->buff );
+ wchar_t *begin, *end;
+
+ if( a )
+ *a=0;
+ if( b )
+ *b = 0;
+
+ if( !data )
+ return;
+
+ while( 1 )
+ {
+ int bc, ec;
+
+ if( expand_locate_subshell( buffcpy,
+ &begin,
+ &end,
+ 1 ) <= 0)
+ {
+ begin=buffcpy;
+ end = buffcpy + wcslen(data->buff);
+ break;
+ }
+ bc = begin-buffcpy;
+ ec = end-buffcpy;
+ if(( bc < data->buff_pos ) && (ec >= data->buff_pos) )
+ {
+ begin++;
+
+ //fwprintf( stderr, L"Subshell!\n" );
+ break;
+ }
+ *begin=0;
+ }
+ if( a )
+ *a = data->buff + (begin-buffcpy);
+ if( b )
+ *b = data->buff + (end-buffcpy);
+ free( buffcpy );
+}
+
+static void reader_current_job_or_process_extent( wchar_t **a,
+ wchar_t **b,
+ int process )
+{
+ wchar_t *begin, *end;
+ int pos;
+ wchar_t *buffcpy;
+ int finished=0;
+
+ tokenizer tok;
+
+ if( a )
+ *a=0;
+ if( b )
+ *b = 0;
+
+ reader_current_subshell_extent( &begin, &end );
+ if( !end || !begin )
+ return;
+
+ pos = data->buff_pos - (begin - data->buff);
+// fwprintf( stderr, L"Subshell extent: %d %d %d\n", begin-data->buff, end-data->buff, pos );
+
+ if( a )
+ {
+ *a = begin;
+ }
+
+ if( b )
+ {
+ *b = end;
+ }
+
+ buffcpy = wcsndup( begin, end-begin );
+
+ if( !buffcpy )
+ {
+ die_mem();
+ }
+// fwprintf( stderr, L"Strlen: %d\n", wcslen(buffcpy ) );
+
+ for( tok_init( &tok, buffcpy, TOK_ACCEPT_UNFINISHED );
+ tok_has_next( &tok ) && !finished;
+ tok_next( &tok ) )
+ {
+ int tok_begin = tok_get_pos( &tok );
+// fwprintf( stderr, L".");
+
+ switch( tok_last_type( &tok ) )
+ {
+ case TOK_PIPE:
+ if( !process )
+ break;
+
+ case TOK_END:
+ case TOK_BACKGROUND:
+ {
+
+// fwprintf( stderr, L"New cmd at %d\n", tok_begin );
+
+ if( tok_begin >= pos )
+ {
+ finished=1;
+ if( b )
+ *b = data->buff + tok_begin;
+ }
+ else
+ {
+ if( a )
+ *a = data->buff + tok_begin+1;
+ }
+ break;
+
+ }
+ }
+ }
+
+// fwprintf( stderr, L"Res: %d %d\n", *a-data->buff, *b-data->buff );
+ free( buffcpy);
+
+ tok_destroy( &tok );
+
+}
+
+void reader_current_process_extent( wchar_t **a, wchar_t **b )
+{
+ reader_current_job_or_process_extent( a, b, 1 );
+}
+
+void reader_current_job_extent( wchar_t **a, wchar_t **b )
+{
+ reader_current_job_or_process_extent( a, b, 0 );
+}
+
+
+void reader_current_token_extent( wchar_t **tok_begin,
+ wchar_t **tok_end,
+ wchar_t **prev_begin,
+ wchar_t **prev_end )
+{
+ wchar_t *begin, *end;
+ int pos;
+ wchar_t *buffcpy;
+
+ tokenizer tok;
+
+ wchar_t *a, *b, *pa, *pb;
+
+
+ a = b = pa = pb = 0;
+
+ reader_current_subshell_extent( &begin, &end );
+
+// fwprintf( stderr, L"Lalala: %d %d %d\n", begin-data->buff, end-data->buff, pos );
+
+ if( !end || !begin )
+ return;
+
+ pos = data->buff_pos - (begin - data->buff);
+
+ a = data->buff + pos;
+ b = a;
+ pa = data->buff + pos;
+ pb = pa;
+
+ buffcpy = wcsndup( begin, end-begin );
+
+ if( !buffcpy )
+ {
+ die_mem();
+ }
+
+ for( tok_init( &tok, buffcpy, TOK_ACCEPT_UNFINISHED );
+ tok_has_next( &tok );
+ tok_next( &tok ) )
+ {
+ int tok_begin = tok_get_pos( &tok );
+ int tok_end=tok_begin;
+
+ if( tok_last_type( &tok ) == TOK_STRING )
+ tok_end +=wcslen(tok_last(&tok));
+
+ if( tok_begin > pos )
+ {
+ a = b = data->buff + pos;
+ break;
+ }
+
+ if( tok_end >= pos )
+ {
+ a = begin + tok_get_pos( &tok );
+ b = a + wcslen(tok_last(&tok));
+
+// fwprintf( stderr, L"Whee %ls\n", *a );
+
+ break;
+ }
+ pa = begin + tok_get_pos( &tok );
+ pb = pa + wcslen(tok_last(&tok));
+ }
+
+// fwprintf( stderr, L"Res: %d %d\n", *a-data->buff, *b-data->buff );
+ free( buffcpy);
+
+ tok_destroy( &tok );
+
+ if( tok_begin )
+ *tok_begin = a;
+ if( tok_end )
+ *tok_end = b;
+ if( prev_begin )
+ *prev_begin = pa;
+ if( prev_end )
+ *prev_end = pb;
+
+// fwprintf( stderr, L"w00t\n" );
+
+}
+
+
+void reader_replace_current_token( wchar_t *new_token )
+{
+
+ wchar_t *begin, *end;
+ string_buffer_t sb;
+ int new_pos;
+
+ /*
+ Find current token
+ */
+ reader_current_token_extent( &begin, &end, 0, 0 );
+
+ if( !begin || !end )
+ return;
+
+// fwprintf( stderr, L"%d %d, %d\n", begin-data->buff, end-data->buff, data->buff_len );
+
+ /*
+ Make new string
+ */
+ sb_init( &sb );
+ sb_append_substring( &sb, data->buff, begin-data->buff );
+ sb_append( &sb, new_token );
+ sb_append( &sb, end );
+
+ new_pos = (begin-data->buff) + wcslen(new_token);
+
+ reader_set_buffer( (wchar_t *)sb.buff, new_pos );
+ sb_destroy( &sb );
+
+}
+
+
+/**
+ Set the specified string from the history as the current buffer. Do
+ not modify prefix_width.
+*/
+static void handle_history( const wchar_t *new_str )
+{
+ data->buff_len = wcslen( new_str );
+ check_size();
+ wcscpy( data->buff, new_str );
+ data->buff_pos=wcslen(data->buff);
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+
+ repaint();
+}
+
+/**
+ Check if the specified string is contained in the list, using
+ wcscmp as a comparison function
+*/
+static int contains( const wchar_t *needle,
+ array_list_t *haystack )
+{
+ int i;
+ for( i=0; i<al_get_count( haystack ); i++ )
+ {
+ if( !wcscmp( needle, al_get( haystack, i ) ) )
+ return 1;
+ }
+ return 0;
+
+}
+
+/**
+ Handles a token search command.
+
+ \param forward if the search should be forward or reverse
+ \param reset whether the current token should be made the new search token
+*/
+static void handle_token_history( int forward, int reset )
+{
+ wchar_t *str=0;
+ int current_pos;
+ tokenizer tok;
+
+ if(reset )
+ {
+ /*
+ Start a new token search using the current token
+ */
+
+ wchar_t *begin, *end;
+
+ reader_current_token_extent( &begin, &end, 0, 0 );
+ if( begin )
+ {
+ wcslcpy(data->search_buff, begin, end-begin+1);
+ }
+ else
+ data->search_buff[0]=0;
+
+ data->token_history_pos = -1;
+ data->search_pos=0;
+ al_foreach( &data->search_prev, (void (*)(const void *))&free );
+ al_truncate( &data->search_prev, 0 );
+ al_push( &data->search_prev, wcsdup( data->search_buff ) );
+ }
+
+ current_pos = data->token_history_pos;
+
+ if( forward || data->search_pos < (al_get_count( &data->search_prev )-1) )
+ {
+ if( forward )
+ {
+ if( data->search_pos > 0 )
+ {
+ data->search_pos--;
+ }
+ str = (wchar_t *)al_get( &data->search_prev, data->search_pos );
+ }
+ else
+ {
+ data->search_pos++;
+ str = (wchar_t *)al_get( &data->search_prev, data->search_pos );
+ }
+
+ reader_replace_current_token( str );
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+ repaint();
+ }
+ else
+ {
+ if( current_pos == -1 )
+ {
+ /*
+ Move to previous line
+ */
+ free( (void *)data->token_history_buff );
+ data->token_history_buff = wcsdup( history_prev_match(L"") );
+ current_pos = wcslen(data->token_history_buff);
+ }
+
+ if( ! wcslen( data->token_history_buff ) )
+ {
+ /*
+ We have reached the end of the history - check if the
+ history already contains the search string itself, if so
+ return, otherwise add it.
+ */
+ const wchar_t *last = al_get( &data->search_prev, al_get_count( &data->search_prev ) -1 );
+ if( wcscmp( last, data->search_buff ) )
+ {
+ str = wcsdup(data->search_buff);
+ }
+ else
+ {
+ return;
+ }
+ }
+ else
+ {
+ for( tok_init( &tok, data->token_history_buff, TOK_ACCEPT_UNFINISHED );
+ tok_has_next( &tok);
+ tok_next( &tok ))
+ {
+ switch( tok_last_type( &tok ) )
+ {
+ case TOK_STRING:
+ {
+ if( wcsstr( tok_last( &tok ), data->search_buff ) )
+ {
+// fwprintf( stderr, L"Found token at pos %d\n", tok_get_pos( &tok ) );
+ if( tok_get_pos( &tok ) >= current_pos )
+ {
+ break;
+ }
+
+ if( !contains( tok_last( &tok ), &data->search_prev ) )
+ {
+ free(str);
+ data->token_history_pos = tok_get_pos( &tok );
+ str = wcsdup(tok_last( &tok ));
+ }
+
+ }
+ }
+ }
+ }
+
+ tok_destroy( &tok );
+ }
+
+ if( str )
+ {
+ reader_replace_current_token( str );
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+ repaint();
+ al_push( &data->search_prev, str );
+ data->search_pos = al_get_count( &data->search_prev )-1;
+ }
+ else
+ {
+ data->token_history_pos=-1;
+ handle_token_history( 0, 0 );
+ }
+ }
+}
+
+
+/**
+ Move buffer position one word or erase one word. This function
+ updates both the internal buffer and the screen. It is used by
+ M-left, M-right and ^W to do block movement or block erase.
+
+ \param dir Direction to move/erase. 0 means move left, 1 means move right.
+ \param erase Whether to erase the characters along the way or only move past them.
+
+*/
+static void move_word( int dir, int erase )
+{
+ int end_buff_pos=data->buff_pos;
+ int mode=0;
+ int step = dir?1:-1;
+
+ while( mode < 2 )
+ {
+ if( !dir )
+ {
+ if( end_buff_pos == 0 )
+ break;
+ }
+ else
+ {
+ if( end_buff_pos == data->buff_len )
+ break;
+ }
+ end_buff_pos+=step;
+
+ if( end_buff_pos < data->buff_len )
+ {
+ switch( mode )
+ {
+ case 0:
+ if( iswalnum(data->buff[end_buff_pos] ) )
+ mode++;
+ break;
+
+ case 1:
+ if( !iswalnum(data->buff[end_buff_pos] ) )
+ {
+ if( !dir )
+ end_buff_pos -= step;
+ mode++;
+ }
+ break;
+/*
+ case 2:
+ if( !iswspace(data->buff[end_buff_pos] ) )
+ {
+ mode++;
+ if( !dir )
+ end_buff_pos-=step;
+ }
+ break;
+*/
+ }
+ }
+
+ if( mode==2)
+ break;
+
+ }
+
+ if( erase )
+ {
+ int remove_count = abs(data->buff_pos - end_buff_pos);
+ int first_char = mini( data->buff_pos, end_buff_pos );
+ wchar_t *woot = wcsndup( data->buff + first_char, remove_count);
+// fwprintf( stderr, L"Remove from %d to %d\n", first_char, first_char+remove_count );
+
+ kill_add( woot );
+ free( woot );
+ memmove( data->buff + first_char, data->buff + first_char+remove_count, sizeof(wchar_t)*(data->buff_len-first_char-remove_count) );
+ data->buff_len -= remove_count;
+ data->buff_pos = first_char;
+ data->buff[data->buff_len]=0;
+
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+
+ repaint();
+ }
+ else
+ {
+/* move_cursor(end_buff_pos-data->buff_pos);
+ data->buff_pos = end_buff_pos;
+*/
+ if( end_buff_pos < data->buff_pos )
+ {
+ while( data->buff_pos != end_buff_pos )
+ {
+ data->buff_pos--;
+ move_cursor( -wcwidth(data->buff[data->buff_pos]));
+ }
+ }
+ else
+ {
+ while( data->buff_pos != end_buff_pos )
+ {
+ move_cursor( wcwidth(data->buff[data->buff_pos]));
+ data->buff_pos++;
+ check_colors();
+ }
+ }
+
+ repaint();
+// check_colors();
+ }
+}
+
+
+wchar_t *reader_get_buffer()
+{
+ return data?data->buff:0;
+}
+
+void reader_set_buffer( wchar_t *b, int p )
+{
+ int l = wcslen( b );
+
+ if( !data )
+ return;
+
+ data->buff_len = l;
+ check_size();
+ wcscpy( data->buff, b );
+
+ if( p>=0 )
+ {
+ data->buff_pos=p;
+ }
+ else
+ {
+ data->buff_pos=l;
+// fwprintf( stderr, L"Pos %d\n", l );
+ }
+
+ reader_super_highlight_me_plenty( data->buff,
+ data->color,
+ data->buff_pos,
+ 0 );
+}
+
+
+int reader_get_cursor_pos()
+{
+ if( !data )
+ return -1;
+
+ return data->buff_pos;
+}
+
+
+void reader_run_command( wchar_t *cmd )
+{
+
+ wchar_t *ft;
+
+ ft= tok_first( cmd );
+
+ if( ft != 0 )
+ env_set( L"_", ft, ENV_GLOBAL );
+ free(ft);
+
+ reader_write_title();
+
+ term_donate();
+
+ if( eval( cmd, 0, TOP ) == 0 )
+ {
+ job_do_notification();
+ }
+
+ term_steal();
+
+ env_set( L"_", L"fish", ENV_GLOBAL );
+
+#ifdef HAVE__PROC_SELF_STAT
+ proc_update_jiffies();
+#endif
+
+
+}
+
+
+/**
+ Test if the given shell command contains errors. Uses parser_test
+ for testing.
+*/
+
+static int shell_test( wchar_t *b )
+{
+ return !wcslen(b);
+}
+
+/**
+ Test if the given string contains error. Since this is the error
+ detection for general purpose, there are no invalid strings, so
+ this function always returns false.
+*/
+static int default_test( wchar_t *b )
+{
+ return 0;
+}
+
+void reader_push( wchar_t *name )
+{
+ reader_data_t *n = calloc( 1, sizeof( reader_data_t ) );
+ n->name = wcsdup( name );
+ n->next = data;
+ data=n;
+ check_size();
+ data->buff[0]=data->search_buff[0]=0;
+ data->exec_prompt=1;
+
+ if( data->next == 0 )
+ {
+ reader_interactive_init();
+ }
+ reader_set_highlight_function( &highlight_universal );
+ reader_set_test_function( &default_test );
+ reader_set_prompt( L"" );
+ history_set_mode( name );
+
+ al_init( &data->search_prev );
+ data->token_history_buff=0;
+
+}
+
+void reader_pop()
+{
+ reader_data_t *n = data;
+
+ if( data == 0 )
+ {
+ debug( 0, L"Pop null reader block" );
+ sanity_lose();
+ return;
+ }
+
+ data=data->next;
+
+ free(n->name );
+ free( n->prompt );
+ free( n->buff );
+ free( n->color );
+ free( n->new_color );
+ free( n->search_buff );
+ free( n->output );
+ free( n->output_color );
+
+ /*
+ Clean up after history search
+ */
+ al_foreach( &n->search_prev, (void (*)(const void *))&free );
+ al_destroy( &n->search_prev );
+ free( (void *)n->token_history_buff);
+
+ free(n);
+
+ if( data == 0 )
+ {
+ reader_interactive_destroy();
+ }
+ else
+ {
+ history_set_mode( data->name );
+ data->exec_prompt=1;
+ }
+}
+
+void reader_set_prompt( wchar_t *new_prompt )
+{
+ free( data->prompt );
+ data->prompt=wcsdup(new_prompt);
+}
+
+void reader_set_complete_function( void (*f)( const wchar_t *,
+ array_list_t * ) )
+{
+ data->complete_func = f;
+}
+
+void reader_set_highlight_function( void (*f)( wchar_t *,
+ int *,
+ int,
+ array_list_t * ) )
+{
+ data->highlight_func = f;
+}
+
+void reader_set_test_function( int (*f)( wchar_t * ) )
+{
+ data->test_func = f;
+}
+
+/**
+ Call specified external highlighting function and then do search
+ highlighting.
+*/
+static void reader_super_highlight_me_plenty( wchar_t * buff, int *color, int pos, array_list_t *error )
+{
+ data->highlight_func( buff, color, pos, error );
+ if( wcslen(data->search_buff) )
+ {
+ wchar_t * match = wcsstr( buff, data->search_buff );
+ if( match )
+ {
+ int start = match-buff;
+ int count = wcslen(data->search_buff );
+ int i;
+// fwprintf( stderr, L"WEE color from %d to %d\n", start, start+count );
+
+ for( i=0; i<count; i++ )
+ {
+ /*
+ Do not overwrite previous highlighting color
+ */
+ if( color[start+i]>>8 == 0 )
+ {
+ color[start+i] |= HIGHLIGHT_SEARCH_MATCH<<8;
+ }
+ }
+ }
+ }
+}
+
+
+int exit_status()
+{
+ if( is_interactive )
+ return first_job == 0 && data->end_loop;
+ else
+ return end_loop;
+}
+
+/**
+ Read interactively. Read input from stdin while providing editing
+ facilities.
+*/
+static int read_i()
+{
+ int prev_end_loop=0;
+
+ reader_push(L"fish");
+ reader_set_complete_function( &complete );
+ reader_set_highlight_function( &highlight_shell );
+ reader_set_test_function( &shell_test );
+
+ data->prompt_width=60;
+
+ while( (!data->end_loop) && (!sanity_check()) )
+ {
+ wchar_t *tmp;
+
+ if( function_exists( L"fish_prompt" ) )
+ reader_set_prompt( L"fish_prompt" );
+ else
+ reader_set_prompt( DEFAULT_PROMPT );
+
+ /*
+ Put buff in temporary string and clear buff, so
+ that we can handle a call to reader_set_buffer
+ during evaluation.
+ */
+
+ tmp = wcsdup( reader_readline() );
+
+ data->buff_pos=data->buff_len=0;
+ data->buff[data->buff_len]=L'\0';
+ if( function_exists(L"fish_on_exec"))
+ {
+ eval( L"fish_on_exec", 0, TOP );
+ }
+ reader_run_command( tmp );
+ free( tmp );
+ if( function_exists(L"fish_on_return"))
+ {
+ eval( L"fish_on_return", 0, TOP );
+ }
+
+ if( data->end_loop)
+ {
+ if( !prev_end_loop && first_job != 0 )
+ {
+ writestr(L"There are stopped jobs\n");
+ write_prompt();
+ data->end_loop = 0;
+ prev_end_loop=1;
+ }
+ }
+ else
+ {
+ prev_end_loop=0;
+ }
+
+ error_reset();
+ }
+ reader_pop();
+ return 0;
+}
+
+
+
+
+wchar_t *reader_readline()
+{
+
+ wchar_t c;
+ int i;
+ int last_char=0, yank=0;
+ wchar_t *yank_str;
+ array_list_t comp;
+ int comp_empty=1;
+ int finished=0;
+ struct termios old_modes;
+
+ check_size();
+ data->search_buff[0]=data->buff[data->buff_len]='\0';
+
+
+ al_init( &comp );
+
+ data->exec_prompt=1;
+
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+ repaint();
+
+ tcgetattr(0,&old_modes); /* get the current terminal modes */
+ if( tcsetattr(0,TCSANOW,&shell_modes)) /* set the new modes */
+ {
+ wperror(L"tcsetattr");
+ exit(1);
+ }
+
+ while( !finished && !data->end_loop)
+ {
+
+ /*
+ Save the terminal status so we know if we have to redraw
+ */
+
+ reader_save_status();
+
+ /*
+ Sometimes strange input sequences seem to generate a zero
+ byte. I believe these simply mean a character was pressed
+ but it should be ignored. (Example: Trying to add a tilde
+ (~) to digit)
+ */
+ check_winch();
+ while( (c=input_readch()) == 0 )
+ ;
+
+ check_winch();
+ reader_check_status();
+
+ if( (last_char == R_COMPLETE) && (c != R_COMPLETE) && (!comp_empty) )
+ {
+ al_foreach( &comp, (void (*)(const void *))&free );
+ al_truncate( &comp, 0 );
+ comp_empty = 1;
+ }
+
+ if( last_char != R_YANK && last_char != R_YANK_POP )
+ yank=0;
+ switch (c)
+ {
+
+ /* go to beginning of line*/
+ case R_BEGINNING_OF_LINE:
+ {
+ data->buff_pos = 0;
+
+ repaint();
+ break;
+ }
+
+ /* go to EOL*/
+ case R_END_OF_LINE:
+ {
+ data->buff_pos = data->buff_len;
+
+ repaint();
+ break;
+ }
+
+ case R_NULL:
+ {
+ data->exec_prompt=1;
+ repaint();
+ break;
+ }
+
+ /* complete */
+ case R_COMPLETE:
+ {
+
+// fwprintf( stderr, L"aaa\n" );
+ if( !data->complete_func )
+ break;
+
+ if( !comp_empty && last_char == R_COMPLETE )
+ break;
+
+ if( comp_empty )
+ {
+ wchar_t *begin, *end;
+ wchar_t *buffcpy;
+
+ reader_current_subshell_extent( &begin, &end );
+
+ int len = data->buff_pos - (data->buff - begin);
+ buffcpy = wcsndup( begin, len );
+
+ //fwprintf( stderr, L"String is %ls\n", buffcpy );
+
+ reader_save_status();
+ data->complete_func( buffcpy, &comp );
+ reader_check_status();
+
+ sort_list( &comp );
+ remove_duplicates( &comp );
+
+ free( buffcpy );
+ }
+ if( (comp_empty =
+ handle_completions( &comp ) ) )
+ {
+ al_foreach( &comp, (void (*)(const void *))&free );
+ al_truncate( &comp, 0 );
+ }
+
+ break;
+ }
+
+ /* kill*/
+ case R_KILL_LINE:
+ {
+ kill_add( &data->buff[data->buff_pos] );
+ data->buff_len = data->buff_pos;
+ data->buff[data->buff_len]=L'\0';
+
+
+ repaint();
+// wcscpy(data->search_buff,data->buff);
+ break;
+ }
+
+ case R_BACKWARD_KILL_LINE:
+ {
+ wchar_t prev = data->buff[data->buff_pos];
+ data->buff[data->buff_pos]=0;
+ kill_add( data->buff );
+ data->buff[data->buff_pos]=prev;
+ data->buff_len = wcslen(data->buff +data->buff_pos);
+ memmove( data->buff, data->buff +data->buff_pos, sizeof(wchar_t)*data->buff_len );
+ data->buff[data->buff_len]=L'\0';
+ data->buff_pos=0;
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+
+ repaint();
+// wcscpy(data->search_buff,data->buff);
+ break;
+ }
+
+ case R_KILL_WHOLE_LINE:
+ {
+ kill_add( data->buff );
+ data->buff_len = data->buff_pos = 0;
+ data->buff[data->buff_len]=L'\0';
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+
+ repaint();
+// wcscpy(data->search_buff,data->buff);
+ break;
+ }
+
+ /* yank*/
+ case R_YANK:
+ yank_str = kill_yank();
+ insert_str( yank_str );
+ yank = wcslen( yank_str );
+// wcscpy(data->search_buff,data->buff);
+ break;
+
+ /* rotate killring*/
+ case R_YANK_POP:
+ if( yank )
+ {
+ for( i=0; i<yank; i++ )
+ remove_backward();
+
+ yank_str = kill_yank_rotate();
+ insert_str(yank_str);
+ yank = wcslen(yank_str);
+ }
+ break;
+
+ /* Escape was pressed */
+ case L'\e':
+ if( *data->search_buff )
+ {
+ history_reset();
+ wcscpy( data->buff, data->search_buff );
+ data->buff_pos = data->buff_len = wcslen(data->buff);
+ *data->search_buff=0;
+ check_colors();
+
+ }
+
+ break;
+
+ /* delete backward*/
+ case R_BACKWARD_DELETE_CHAR:
+ remove_backward();
+ break;
+
+ /* delete forward*/
+ case R_DELETE_CHAR:
+ remove_forward();
+ break;
+
+ /* exit, but only if line is empty */
+ case R_EXIT:
+
+ if( data->buff_len == 0 )
+ {
+ writestr( L"\n" );
+ data->end_loop=1;
+ }
+ break;
+
+ /* Newline, evaluate*/
+ case L'\n':
+ {
+ data->buff[data->buff_len]=L'\0';
+
+ if( !data->test_func( data->buff ) )
+ {
+
+ if( wcslen( data->buff ) )
+ {
+// wcscpy(data->search_buff,L"");
+ history_add( data->buff );
+ }
+ finished=1;
+ data->buff_pos=data->buff_len;
+ check_colors();
+ writestr( L"\n" );
+ }
+ else
+ repaint();
+
+ break;
+ }
+
+ /* History up*/
+ case R_HISTORY_SEARCH_BACKWARD:
+// fwprintf( stderr, L"Search history for \'%ls\' %d long\n", data->search_buff, wcslen(data->search_buff) );
+ if( (last_char != R_HISTORY_SEARCH_BACKWARD) &&
+ (last_char != R_HISTORY_SEARCH_FORWARD) )
+ {
+ wcscpy(data->search_buff, data->buff );
+ data->search_buff[data->buff_pos]=0;
+ }
+
+ handle_history(history_prev_match(data->search_buff));
+ break;
+
+ /* History down*/
+ case R_HISTORY_SEARCH_FORWARD:
+ if( (last_char != R_HISTORY_SEARCH_BACKWARD) &&
+ (last_char != R_HISTORY_SEARCH_FORWARD) )
+ {
+ wcscpy(data->search_buff, data->buff );
+ data->search_buff[data->buff_pos]=0;
+ }
+
+ handle_history(history_next_match(data->search_buff));
+ break;
+
+ case R_HISTORY_TOKEN_SEARCH_BACKWARD:
+ {
+ int reset=0;
+ if( (last_char != R_HISTORY_TOKEN_SEARCH_BACKWARD) &&
+ (last_char != R_HISTORY_TOKEN_SEARCH_FORWARD) )
+ {
+ reset=1;
+
+ }
+
+ handle_token_history( 0, reset );
+
+ break;
+ }
+
+ case R_HISTORY_TOKEN_SEARCH_FORWARD:
+ {
+ int reset=0;
+
+ if( (last_char != R_HISTORY_TOKEN_SEARCH_BACKWARD) &&
+ (last_char != R_HISTORY_TOKEN_SEARCH_FORWARD) )
+ {
+ reset=1;
+ }
+
+ handle_token_history( 1, reset );
+
+ break;
+ }
+
+
+
+ /* Move left*/
+ case R_BACKWARD_CHAR:
+ if( data->buff_pos > 0 )
+ {
+ data->buff_pos--;
+ if( !force_repaint() )
+ {
+ move_cursor( -wcwidth(data->buff[data->buff_pos]));
+ check_colors();
+ }
+ else
+ {
+ repaint();
+ }
+ }
+ break;
+
+ /* Move right*/
+ case R_FORWARD_CHAR:
+ if( data->buff_pos < data->buff_len )
+ {
+ if( !force_repaint() )
+ {
+ move_cursor( wcwidth(data->buff[data->buff_pos]));
+ data->buff_pos++;
+ check_colors();
+ }
+ else
+ {
+ data->buff_pos++;
+
+ repaint();
+ }
+ }
+ break;
+
+ case R_DELETE_LINE:
+ data->buff[0]=0;
+ data->buff_len=0;
+ data->buff_pos=0;
+ repaint();
+
+ /* kill one word left */
+ case R_BACKWARD_KILL_WORD:
+ move_word(0,1);
+ break;
+
+ /* kill one word right */
+ case R_KILL_WORD:
+ move_word(1,1);
+ break;
+
+ /* move one word left*/
+ case R_BACKWARD_WORD:
+ move_word(0,0);
+ break;
+
+ /* move one word right*/
+ case R_FORWARD_WORD:
+ move_word( 1,0);
+ break;
+
+ case R_CLEAR_SCREEN:
+ {
+ writembs( clear_screen );
+
+ repaint();
+ break;
+ }
+
+ case R_BEGINNING_OF_HISTORY:
+ {
+ history_first();
+ break;
+ }
+
+ case R_END_OF_HISTORY:
+ {
+ history_reset();
+
+ break;
+ }
+
+ /* Other, if a normal character, we add it to the command */
+ default:
+ {
+ if( (c< WCHAR_END) && (c>31) && (c != 127) )
+ {
+ insert_char( c );
+ }
+ break;
+ }
+
+ }
+
+ if( (c != R_HISTORY_SEARCH_BACKWARD) &&
+ (c != R_HISTORY_SEARCH_FORWARD) &&
+ (c != R_HISTORY_TOKEN_SEARCH_BACKWARD) &&
+ (c != R_HISTORY_TOKEN_SEARCH_FORWARD) )
+ {
+ data->search_buff[0]=0;
+ history_reset();
+ }
+
+
+ last_char = c;
+ }
+
+ al_destroy( &comp );
+ if( tcsetattr(0,TCSANOW,&old_modes)) /* return to previous mode */
+ {
+ wperror(L"tcsetattr");
+ exit(1);
+ }
+
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+
+ return data->buff;
+}
+
+/**
+ Read non-interactively. Read input from stdin without displaying
+ the prompt, using syntax highlighting. This is used for reading
+ scripts and init files.
+*/
+static int read_ni()
+{
+ FILE *in_stream;
+ wchar_t *buff=0;
+ buffer_t acc;
+
+ int des = dup( 0 );
+ int res=0;
+
+ if (des == -1)
+ {
+ wperror( L"dup" );
+ return 1;
+ }
+
+ b_init( &acc );
+
+ in_stream = fdopen( des, "r" );
+ if( in_stream != 0 )
+ {
+ wchar_t *str;
+
+ while(!feof( in_stream ))
+ {
+ char buff[4096];
+ int c = fread(buff, 1, 4096, in_stream);
+ b_append( &acc, buff, c );
+ }
+ b_append( &acc, "\0", 1 );
+ str = str2wcs( acc.buff );
+ b_destroy( &acc );
+
+// fwprintf( stderr, L"Woot is %d chars\n", wcslen( acc.buff ) );
+
+ if( str )
+ {
+ if( !parser_test( str, 1 ) )
+ {
+ //fwprintf( stderr, L"We parse it\n" );
+ eval( str, 0, TOP );
+ }
+ else
+ {
+ /*
+ No error reporting - parser_test did that for us
+ */
+ res = 1;
+ }
+ free( str );
+ }
+ else
+ {
+ if( acc.used > 1 )
+ {
+ debug( 1,
+ L"Could not convert input. Read %d bytes.",
+ acc.used-1 );
+ }
+ else
+ {
+ debug( 1,
+ L"Could not read input stream" );
+ }
+ res=1;
+ }
+
+ if( fclose( in_stream ))
+ {
+ debug( 1,
+ L"Error while closing input" );
+ wperror( L"fclose" );
+ res = 1;
+ }
+
+ }
+ else
+ {
+ debug( 1,
+ L"Error while opening input" );
+ wperror( L"fdopen" );
+ free( buff );
+ res=1;
+ }
+ error_reset();
+ return res;
+}
+
+int reader_read()
+{
+ int res;
+ /*
+ If reader_read is called recursively through the '.' builtin,
+ we need to preserve is_interactive, so we save the
+ original state. We also update the signal handlers.
+ */
+ int shell_was_interactive = is_interactive;
+ is_interactive = isatty(STDIN_FILENO);
+ set_signal_handlers();
+
+ res= is_interactive?read_i():read_ni();
+
+ /*
+ If the exit command was called in a script, only exit the
+ script, not the program
+ */
+ end_loop = 0;
+
+ is_interactive = shell_was_interactive;
+ set_signal_handlers();
+ return res;
+}
diff --git a/reader.h b/reader.h
new file mode 100644
index 00000000..b9e95fef
--- /dev/null
+++ b/reader.h
@@ -0,0 +1,186 @@
+/** \file reader.h
+
+ Prototypes for functions for reading data from stdin and passing
+ to the parser. If stdin is a keyboard, it supplies a killring,
+ history, syntax highlighting, tab-completion and various other
+ features.
+*/
+
+/**
+ Read commands from fd 0 until encountering EOF
+*/
+int reader_read();
+
+/**
+ Tell the shell that it should exit after the currently running command finishes.
+*/
+void reader_exit( int do_exit );
+
+/**
+ Check that the reader is in a sane state
+*/
+void reader_sanity_check();
+
+/**
+ Initialize the reader
+*/
+void reader_init();
+
+/**
+ Destroy and free resources used by the reader
+*/
+void reader_destroy();
+
+/**
+ Returns the filename of the file currently read
+*/
+wchar_t *reader_current_filename();
+
+/**
+ Push a new filename on the stack of read files
+
+ \param fn The fileanme to push
+*/
+void reader_push_current_filename( wchar_t *fn );
+/**
+ Pop the current filename from the stack of read files
+ */
+wchar_t *reader_pop_current_filename();
+
+/**
+ Returns the width of the terminal window, so that not all
+ functions that use these values continually have to keep track of
+ it.
+*/
+int reader_get_width();
+/**
+ Returns the height of the terminal window, so that not all
+ functions that use these values continually have to keep track of
+ it.
+*/
+int reader_get_height();
+
+/**
+ Write the title to the titlebar. This function is called just
+ before a new application starts executing and just after it
+ finishes.
+*/
+void reader_write_title();
+
+/**
+ Repaint the entire commandline. This means reset and clear the
+ commandline, write the prompt, perform syntax highlighting, write
+ the commandline and move the cursor.
+*/
+void repaint();
+
+/**
+ Run the specified command with the correct terminal modes, and
+ while taking care to perform job notification, set the title, etc.
+*/
+void reader_run_command( wchar_t *buff );
+
+/**
+ Get the string of character currently entered into the command
+ buffer, or 0 if interactive mode is uninitialized.
+*/
+wchar_t *reader_get_buffer();
+
+/**
+ Set the string of characters in the command buffer, as well as the cursor position.
+
+ \param b the new buffer value
+ \param p the cursor position. If \c p is less than zero, the cursor is placed on the last character.
+*/
+void reader_set_buffer( wchar_t *b, int p );
+
+/**
+ Get the current cursor position in the command line. If interactive
+ mode is uninitialized, return -1.
+*/
+int reader_get_cursor_pos();
+
+/**
+ Return the value of the interupted flag, which is set by the sigint
+ handler, and clear it if it was set.
+*/
+int reader_interupted();
+
+/**
+ Read one line of input. Before calling this function, reader_push()
+ must have been called in order to set up a valid reader
+ environment.
+*/
+wchar_t *reader_readline();
+
+/**
+ Push a new reader environment.
+*/
+void reader_push( wchar_t *name );
+
+/**
+ Return to previous reader environment
+*/
+void reader_pop();
+
+/**
+ Specify function to use for finding possible tab completions. The function must take these arguments:
+
+ - The command to be completed as a null terminated array of wchar_t
+ - An array_list_t in which completions will be inserted.
+*/
+void reader_set_complete_function( void (*f)( const wchar_t *, array_list_t * ) );
+
+/**
+ Specify function for syntax highlighting. The function must take these arguments:
+
+ - The command to be highlighted as a null terminated array of wchar_t
+ - The color code of each character as an array of ints
+ - The cursor position
+ - An array_list_t used for storing error messages
+*/
+void reader_set_highlight_function( void (*f)( wchar_t *, int *, int, array_list_t * ) );
+
+/**
+ Specify function for testing if the command buffer contains syntax
+ errors that must be corrected before returning.
+*/
+void reader_set_test_function( int (*f)( wchar_t * ) );
+
+/**
+ Specify string of shell commands to be run in order to generate the
+ prompt.
+*/
+void reader_set_prompt( wchar_t *prompt );
+
+/**
+ Returns true if the shell is exiting, 0 otherwise.
+*/
+int exit_status();
+
+/**
+ Find the beginning and the end of the current subshell
+*/
+void reader_current_subshell_extent( wchar_t **a, wchar_t **b );
+
+/**
+ Find the beginning and the end of the job under the cursor
+*/
+void reader_current_job_extent( wchar_t **a, wchar_t **b );
+
+/**
+ Find the beginning and the end of the process under the cursor
+*/
+void reader_current_process_extent( wchar_t **a, wchar_t **b );
+
+/**
+ Find the beginning and the end of the token under the curor and the token before the cursor
+*/
+
+void reader_current_token_extent( wchar_t **a, wchar_t **b, wchar_t **pa, wchar_t **pb );
+
+/**
+ Replace the current token with the specified string
+*/
+void reader_replace_current_token( wchar_t *new_token );
+
diff --git a/sanity.c b/sanity.c
new file mode 100644
index 00000000..bd0b892a
--- /dev/null
+++ b/sanity.c
@@ -0,0 +1,72 @@
+/** \file sanity.c
+ Functions for performing sanity checks on the program state
+*/
+#include <stdlib.h>
+#include <wchar.h>
+#include <stdio.h>
+#include <errno.h>
+#include <termios.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+
+#include "config.h"
+#include "util.h"
+#include "common.h"
+#include "sanity.h"
+#include "proc.h"
+#include "history.h"
+#include "reader.h"
+#include "kill.h"
+#include "wutil.h"
+
+/**
+ Status from earlier sanity checks
+*/
+static int insane;
+
+void sanity_lose()
+{
+ debug( 0, L"Errors detected, shutting down" );
+ insane = 1;
+}
+
+int sanity_check()
+{
+ if( !insane )
+ if( is_interactive )
+ history_sanity_check();
+ if( !insane )
+ reader_sanity_check();
+ if( !insane )
+ kill_sanity_check();
+ if( !insane )
+ proc_sanity_check();
+
+ return insane;
+}
+
+void validate_pointer( const void *ptr, const wchar_t *err, int null_ok )
+{
+
+ /*
+ Test if the pointer data crosses a segment boundary.
+ */
+
+ if( (0x00000003 & (int)ptr) != 0 )
+ {
+ debug( 0, L"The pointer '\%ls\' is invalid", err );
+ sanity_lose();
+ }
+
+ if((!null_ok) && (ptr==0))
+ {
+ debug( 0, L"The pointer '\%ls\' is null", err );
+ sanity_lose();
+ }
+}
+
+
diff --git a/sanity.h b/sanity.h
new file mode 100644
index 00000000..5cb22353
--- /dev/null
+++ b/sanity.h
@@ -0,0 +1,22 @@
+/** \file sanity.h
+ Prototypes for functions for performing sanity checks on the program state
+*/
+
+/**
+ Call this function to tell the program it is not in a sane state.
+*/
+void sanity_lose();
+
+/**
+ Perform sanity checks, return 1 if program is in a sane state 0 otherwise.
+*/
+int sanity_check();
+
+/**
+ Try and determine if ptr is a valid pointer. If not, loose sanity.
+
+ \param ptr The pointer to validate
+ \param err A description of what the pointer refers to, for use in error messages
+ \param null_ok Wheter the pointer is allowed to point to 0
+*/
+void validate_pointer( const void *ptr, const wchar_t *err, int null_ok );
diff --git a/set_color.c b/set_color.c
new file mode 100644
index 00000000..a50a15ae
--- /dev/null
+++ b/set_color.c
@@ -0,0 +1,235 @@
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+#include <errno.h>
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+/*
+ Small utility for setting the color.
+ Usage: set_color COLOR
+ where COLOR is either an integer from 0 to seven or one of the strings in the col array.
+*/
+
+#define COLORS (sizeof(col)/sizeof(char *))
+
+char *col[]=
+{
+ "black",
+ "red",
+ "green",
+ "brown",
+ "yellow",
+ "blue",
+ "magenta",
+ "purple",
+ "cyan",
+ "white",
+ "normal"
+}
+;
+
+int col_idx[]=
+{
+ 0,
+ 1,
+ 2,
+ 3,
+ 3,
+ 4,
+ 5,
+ 5,
+ 6,
+ 7,
+ 8
+}
+;
+
+void print_help();
+
+int translate_color( char *str )
+{
+ char *endptr;
+ int color;
+
+ if( !str )
+ return -1;
+
+
+ color = strtol( str, &endptr, 10 );
+ if(endptr<=str)
+ {
+ int i;
+ color = -1;
+ for( i=0; i<COLORS; i++ )
+ {
+
+ if( strcasecmp( col[i], str ) == 0 )
+ {
+ color = col_idx[i];
+ break;
+ }
+ }
+ }
+ return color;
+
+}
+
+int main( int argc, char **argv )
+{
+ char *bgcolor=0;
+ char *fgcolor=0;
+ int fg, bg;
+
+ while( 1 )
+ {
+#ifdef __GLIBC__
+ static struct option
+ long_options[] =
+ {
+ {
+ "background", required_argument, 0, 'b'
+ }
+ ,
+ {
+ "help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ "version", no_argument, 0, 'v'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ int opt_index = 0;
+
+ int opt = getopt_long( argc,
+ argv,
+ "b:hv",
+ long_options,
+ &opt_index );
+#else
+ int opt = getopt( argc,
+ argv,
+ "b:hv" );
+#endif
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ break;
+
+ case 'b':
+ bgcolor = optarg;
+ break;
+ case 'h':
+ print_help();
+ exit(0);
+
+ case 'v':
+ fprintf( stderr, "set_color, version %s\n", PACKAGE_VERSION );
+ exit( 0 );
+
+ case '?':
+ return 1;
+
+ }
+
+ }
+
+ switch( argc-optind)
+ {
+ case 0:
+// printf( "no fg\n" );
+ break;
+
+ case 1:
+ fgcolor=argv[optind];
+// printf( "fg %s\n", fgcolor );
+ break;
+
+ default:
+ printf( "set_color: Too many arguments\n" );
+ return 1;
+ }
+
+ if( !fgcolor && !bgcolor )
+ {
+ print_help();
+ return 1;
+ }
+
+
+ fg = translate_color(fgcolor);
+ if( fgcolor && (fg==-1))
+ {
+ fprintf( stderr, "set_color: Unknown color %s\n", fgcolor );
+ return 1;
+ }
+
+ bg = translate_color(bgcolor);
+ if( bgcolor && (bg==-1))
+ {
+ fprintf( stderr, "set_color: Unknown color %s\n", bgcolor );
+ return 1;
+ }
+
+ setupterm( 0, STDOUT_FILENO, 0);
+ if( bgcolor )
+ {
+ if( bg == 8 )
+ {
+ putp( tparm( set_a_background, 0) );
+ putp( tparm(exit_attribute_mode) );
+ }
+ }
+
+ if( fgcolor )
+ {
+ if( fg == 8 )
+ {
+ putp( tparm( set_a_foreground, 0) );
+ putp( tparm(exit_attribute_mode) );
+ }
+ else
+ {
+ putp( tparm( set_a_foreground, fg) );
+ }
+ }
+
+ if( bgcolor )
+ {
+ if( bg != 8 )
+ {
+ putp( tparm( set_a_background, bg) );
+ }
+ }
+ del_curterm( cur_term );
+}
diff --git a/tokenize.c b/tokenize.c
new file mode 100644
index 00000000..bad79a9f
--- /dev/null
+++ b/tokenize.c
@@ -0,0 +1,138 @@
+/** file tokenize.c
+ Small utility command for tokenizing an argument.
+ \c tokenize is used for splitting a text string into separate parts (i.e. tokenizing) with a user supplied delimiter character.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+/**
+ Print help message
+*/
+void print_help();
+
+/**
+ Main program
+*/
+int main( int argc, char **argv )
+{
+ char *delim = " \t";
+ int empty_ok = 0;
+ int i;
+ extern int optind;
+
+ while( 1 )
+ {
+#ifdef __GLIBC__
+ static struct option
+ long_options[] =
+ {
+ {
+ "with-empty", no_argument, 0, 'e'
+ }
+ ,
+ {
+ "no-empty", no_argument, 0, 'n'
+ }
+ ,
+ {
+ "delimiter", required_argument, 0, 'd'
+ }
+ ,
+ {
+ "help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ "version", no_argument, 0, 'v'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ int opt_index = 0;
+
+ int opt = getopt_long( argc,
+ argv,
+ "end:hv",
+ long_options,
+ &opt_index );
+#else
+ int opt = getopt( argc,
+ argv,
+ "end:hv" );
+#endif
+ if( opt == -1 )
+ break;
+
+ switch( opt )
+ {
+ case 0:
+ break;
+
+ case 'e':
+ empty_ok = 1;
+ break;
+
+ case 'n':
+ empty_ok = 0;
+ break;
+
+ case 'd':
+ delim = optarg;
+ break;
+ case 'h':
+ print_help();
+ exit(0);
+
+ case 'v':
+ printf( "tokenize, version %s\n", PACKAGE_VERSION );
+ exit( 0 );
+
+ case '?':
+ return 1;
+
+ }
+
+ }
+
+ for( i=optind; i<argc; i++ )
+ {
+ char *curr;
+ int printed=0;
+ for( curr = argv[i]; *curr; curr++ )
+ {
+ if( strchr( delim, *curr )==0 )
+ {
+ printed = 1;
+ putchar( *curr );
+ }
+ else
+ {
+ if( empty_ok || printed )
+ putchar( '\n' );
+ printed=0;
+ }
+ }
+ if( printed )
+ putchar( '\n' );
+ }
+
+}
+
+
+
+
+
diff --git a/tokenizer.c b/tokenizer.c
new file mode 100644
index 00000000..a5bb9790
--- /dev/null
+++ b/tokenizer.c
@@ -0,0 +1,625 @@
+/** \file tokenizer.c
+
+ A specialized tokenizer for tokenizing the fish language. In the
+ future, the tokenizer should be extended to support marks,
+ tokenizing multiple strings and disposing of unused string
+ segments.
+*/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "wutil.h"
+#include "tokenizer.h"
+#include "common.h"
+#include "wildcard.h"
+
+/**
+ Error string for unexpected end of string
+*/
+#define EOL_ERROR L"Unexpected end of token"
+/**
+ Error string for mismatched parenthesis
+*/
+#define PARAN_ERROR L"Parenthesis mismatch"
+/**
+ Error string for invalid redirections
+*/
+#define REDIRECT_ERROR L"Invalid redirection"
+/**
+ Error string for invalid input
+*/
+#define INPUT_ERROR L"Invalid input"
+
+/**
+ Characters that separate tokens. They are ordered by frequency of occurrence to increase parsing speed.
+*/
+#define SEP L" \n;|#\t\r<>^&"
+/**
+ Tests if the tokenizer buffer is large enough to hold contents of
+ the specified length, and if not, reallocates the tokenizer buffer.
+
+ \return 0 if the system could not provide the memory needed, and 1 otherwise.
+*/
+const static wchar_t *tok_desc[] =
+{
+ L"Tokenizer not yet initialized",
+ L"Tokenizer error",
+ L"Invalid token",
+ L"String",
+ L"Pipe",
+ L"End of command",
+ L"Redirect output to file",
+ L"Append output to file",
+ L"Redirect input to file",
+ L"Redirect to file descriptor",
+ L"Run job in background",
+ L"Comment"
+}
+;
+
+/**
+ Make sure the tokenizer buffer have room for a token of the specified size.
+*/
+static int check_size( tokenizer *tok, size_t len )
+{
+ if( tok->last_len <= len )
+ {
+ wchar_t *tmp;
+ tok->last_len = len +1;
+ tmp = realloc( tok->last, sizeof(wchar_t)*tok->last_len );
+ if( tmp == 0 )
+ {
+ wperror( L"realloc" );
+ return 0;
+ }
+ tok->last = tmp;
+ }
+ return 1;
+}
+
+/**
+ Set the latest tokens string to be the specified error message
+*/
+static void tok_error( tokenizer *tok, const wchar_t *err )
+{
+ tok->last_type = TOK_ERROR;
+ if( !check_size( tok, wcslen( err)+1 ))
+ {
+ if( tok->last != 0 )
+ *tok->last=0;
+ return;
+ }
+
+ wcscpy( tok->last, err );
+}
+
+void tok_init( tokenizer *tok, const wchar_t *b, int flags )
+{
+// fwprintf( stderr, L"CREATE: \'%ls\'\n", b );
+
+
+ memset( tok, 0, sizeof( tokenizer) );
+
+ tok ->last = 0;
+ tok ->last_len = 0;
+ tok->accept_unfinished = flags & TOK_ACCEPT_UNFINISHED;
+ tok->show_comments = flags & TOK_SHOW_COMMENTS;
+ tok->has_next=1;
+
+
+ /*
+ Before we copy the buffer we need to check that it is not
+ null. But before that, we need to init the tokenizer far enough
+ so that errors can be properly flagged
+ */
+ if( !b )
+ {
+ tok_error( tok, INPUT_ERROR );
+ return;
+ }
+
+ tok->has_next = (*b != L'\0');
+ tok->orig_buff = tok->buff = wcsdup(b);
+
+ if( !tok->orig_buff )
+ {
+ die_mem();
+
+ }
+
+ if( tok->accept_unfinished )
+ {
+ int l = wcslen( tok->orig_buff );
+ if( l != 0 )
+ {
+ if( tok->orig_buff[l-1] == L'\\' )
+ tok->orig_buff[l-1] = L'\0';
+ }
+ }
+
+ tok_next( tok );
+
+}
+
+void tok_destroy( tokenizer *tok )
+{
+ free( tok->last );
+ free( tok->orig_buff );
+}
+
+int tok_last_type( tokenizer *tok )
+{
+ return tok->last_type;
+}
+
+wchar_t *tok_last( tokenizer *tok )
+{
+ return tok->last;
+}
+
+int tok_has_next( tokenizer *tok )
+{
+/* fwprintf( stderr, L"has_next is %ls \n", tok->has_next?L"true":L"false" );*/
+ return tok->has_next;
+}
+
+/**
+ Tests if this character can be a part of a string
+*/
+
+static int is_string_char( wchar_t c )
+{
+ if( !c || wcschr( SEP, c ) )
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ Read the next token as a string
+*/
+static void read_string( tokenizer *tok )
+{
+ const wchar_t *start;
+ int len;
+ int mode=0;
+ wchar_t prev;
+ int do_loop=1;
+ int paran_count=0;
+
+ start = tok->buff;
+
+ while( 1 )
+ {
+ if( *tok->buff == L'\\' )
+ {
+ tok->buff++;
+ if( *tok->buff == L'\0' )
+ {
+ tok_error( tok, EOL_ERROR );
+ return;
+ }
+ tok->buff++;
+ continue;
+ }
+
+ /*
+ The modes are as follows:
+
+ 0: regular text
+ 1: inside of subshell
+ 2: inside of array brackets
+ 3: inside of array brackets and subshell, like in '$foo[(ech'
+ */
+ switch( mode )
+ {
+ case 0:
+ {
+ switch( *tok->buff )
+ {
+ case L'(':
+ {
+ paran_count=1;
+ mode = 1;
+ break;
+ }
+
+ case L'[':
+ {
+ if( tok->buff != start )
+ mode=2;
+ break;
+ }
+
+ case L'\'':
+ case L'"':
+ {
+
+ wchar_t *end = quote_end( tok->buff );
+ tok->last_quote = *tok->buff;
+ if( end )
+ {
+ tok->buff=end;
+ }
+ else
+ {
+ tok->buff += wcslen( tok->buff );
+
+ if( (!tok->accept_unfinished) )
+ {
+ tok_error( tok, EOL_ERROR );
+ return;
+ }
+ do_loop = 0;
+
+ }
+ break;
+ }
+
+ default:
+ {
+ if( !is_string_char(*(tok->buff)) )
+ {
+ do_loop=0;
+ }
+ }
+ }
+ break;
+ }
+
+ case 3:
+ case 1:
+ switch( *tok->buff )
+ {
+ case L'\'':
+ case L'\"':
+ {
+ wchar_t *end = quote_end( tok->buff );
+ if( end )
+ {
+ tok->buff=end;
+ }
+ else
+ do_loop = 0;
+ break;
+ }
+
+ case L'(':
+ paran_count++;
+ break;
+ case L')':
+ paran_count--;
+ if( paran_count == 0 )
+ {
+ mode--;
+ }
+ break;
+ case L'\0':
+ do_loop = 0;
+ break;
+ }
+ break;
+ case 2:
+ switch( *tok->buff )
+ {
+ case L'(':
+ paran_count=1;
+ mode = 3;
+ break;
+
+ case L']':
+ mode=0;
+ break;
+
+ case L'\0':
+ do_loop = 0;
+ break;
+ }
+ break;
+ }
+
+ if( !do_loop )
+ break;
+
+ prev = *tok->buff;
+ tok->buff++;
+ }
+
+ if( (!tok->accept_unfinished) && (mode!=0) )
+ {
+ tok_error( tok, PARAN_ERROR );
+ return;
+ }
+
+
+ len = tok->buff - start;
+
+ if( !check_size( tok, len ))
+ return;
+
+ memcpy( tok->last, start, sizeof(wchar_t)*len );
+ tok->last[len] = L'\0';
+ tok->last_type = TOK_STRING;
+}
+
+/**
+ Read the next token as a comment.
+*/
+static void read_comment( tokenizer *tok )
+{
+ const wchar_t *start;
+ int len;
+
+ start = tok->buff;
+ while( *(tok->buff)!= L'\n' && *(tok->buff)!= L'\0' )
+ tok->buff++;
+
+ len = tok->buff - start;
+ if( !check_size( tok, len ))
+ return;
+
+ memcpy( tok->last, start, sizeof(wchar_t)*len );
+ tok->last[len] = L'\0';
+ tok->last_type = TOK_COMMENT;
+}
+
+/**
+ Read a FD redirect.
+*/
+static void read_redirect( tokenizer *tok, int fd )
+{
+ int mode = -1;
+
+ if( (*tok->buff == L'>') ||
+ (*tok->buff == L'^') )
+ {
+ tok->buff++;
+ if( *tok->buff == *(tok->buff-1) )
+ {
+ tok->buff++;
+ mode = 1;
+ }
+ else
+ {
+ mode = 0;
+ }
+ }
+ else if( *tok->buff == L'<' )
+ {
+ tok->buff++;
+ mode = 2;
+ }
+ else
+ {
+ tok_error( tok, REDIRECT_ERROR);
+ }
+
+ if( !check_size( tok, 2 ))
+ {
+ return;
+ }
+
+ swprintf( tok->last, tok->last_len, L"%d", fd );
+
+ if( *tok->buff == L'&' )
+ {
+ tok->buff++;
+ tok->last_type = TOK_REDIRECT_FD;
+ }
+ else
+ {
+ tok->last_type = TOK_REDIRECT_OUT + mode;
+ }
+}
+
+wchar_t tok_last_quote( tokenizer *tok )
+{
+ return tok->last_quote;
+}
+
+/**
+ Test if a character is whitespace. Differs from iswspace in that it
+ does not consider a newline to be whitespace.
+*/
+static int my_iswspace( wchar_t c )
+{
+ if( c == L'\n' )
+ return 0;
+ else
+ return iswspace( c );
+}
+
+
+const wchar_t *tok_get_desc( int type )
+{
+ return tok_desc[type];
+}
+
+
+void tok_next( tokenizer *tok )
+{
+// fwprintf( stderr, L"tok_next on %ls (prev=%ls)\n", tok->orig_buff, tok_desc[tok->last_type] );
+
+ if( tok_last_type( tok ) == TOK_ERROR )
+ {
+ tok->has_next=0;
+ return;
+ }
+
+ if( !tok->has_next )
+ {
+/* wprintf( L"EOL\n" );*/
+ tok->last_type = TOK_END;
+ return;
+ }
+
+ while( my_iswspace(*(tok->buff) ) )
+ tok->buff++;
+
+ if( *tok->buff == L'#')
+ {
+ if( tok->show_comments )
+ {
+ tok->last_pos = tok->buff - tok->orig_buff;
+ read_comment( tok );
+ return;
+ }
+ else
+ {
+ while( *(tok->buff)!= L'\n' && *(tok->buff)!= L'\0' )
+ tok->buff++;
+ }
+
+ while( my_iswspace(*(tok->buff) ) )
+ tok->buff++;
+ }
+
+ tok->last_pos = tok->buff - tok->orig_buff;
+
+ switch( *tok->buff )
+ {
+
+ case L'|':
+ tok->last_type = TOK_PIPE;
+ tok->buff++;
+ break;
+ case L'\0':
+ tok->last_type = TOK_END;
+ /*fwprintf( stderr, L"End of string\n" );*/
+ tok->has_next = 0;
+ break;
+ case 13:
+ case L'\n':
+ case L';':
+ tok->last_type = TOK_END;
+ tok->buff++;
+ break;
+ case L'&':
+ tok->last_type = TOK_BACKGROUND;
+ tok->buff++;
+ break;
+
+ case L'>':
+ return read_redirect( tok, 1 );
+ case L'<':
+ return read_redirect( tok, 0 );
+ case L'^':
+ return read_redirect( tok, 2 );
+
+ default:
+ if( iswdigit( *tok->buff ) )
+ {
+ int fd = *tok->buff - L'0';
+ switch( *(tok->buff+1))
+ {
+ case L'>':
+ case L'<':
+ tok->buff++;
+ read_redirect( tok, fd );
+ return;
+
+ }
+ }
+ read_string( tok );
+ }
+
+}
+
+wchar_t *tok_string( tokenizer *tok )
+{
+ return tok->orig_buff;
+}
+
+wchar_t *tok_first( const wchar_t *str )
+{
+ tokenizer t;
+ wchar_t *res=0;
+
+ tok_init( &t, str, 0 );
+
+ switch( tok_last_type( &t ) )
+ {
+ case TOK_STRING:
+// fwprintf( stderr, L"Got token %ls\n", tok_last( &t ));
+ res = wcsdup(tok_last( &t ));
+ break;
+ default:
+ break;
+ }
+
+ tok_destroy( &t );
+ return res;
+}
+
+
+int tok_get_pos( tokenizer *tok )
+{
+ return tok->last_pos;
+}
+
+
+void tok_set_pos( tokenizer *tok, int pos )
+{
+ tok->buff = tok->orig_buff + pos;
+ tok->has_next = 1;
+ tok_next( tok );
+}
+
+
+#ifdef TOKENIZER_TEST
+
+/**
+ This main function is used for compiling the tokenizer_test command, used for testing the tokenizer.
+*/
+int main( int argc, char **argv )
+{
+ tokenizer tok;
+ int i;
+ for ( i=1; i<argc; i++ )
+ {
+ wprintf( L"Tokenizing string %s\n", argv[i] );
+ for( tok_init( &tok, str2wcs(argv[i]), 0 ); tok_has_next( &tok ); tok_next( &tok ) )
+ {
+ switch( tok_last_type( &tok ) )
+ {
+ case TOK_INVALID:
+ wprintf( L"Type: INVALID\n" );
+ break;
+ case TOK_STRING:
+ wprintf( L"Type: STRING\t Value: %ls\n", tok_last( &tok ) );
+ break;
+ case TOK_PIPE:
+ wprintf( L"Type: PIPE\n" );
+ break;
+ case TOK_END:
+ wprintf( L"Type: END\n" );
+ break;
+ case TOK_ERROR:
+ wprintf( L"Type: ERROR\n" );
+ break;
+ default:
+ wprintf( L"Type: Unknown\n" );
+ break;
+ }
+ }
+ tok_destroy( &tok );
+
+ }
+}
+
+
+#endif
diff --git a/tokenizer.h b/tokenizer.h
new file mode 100644
index 00000000..45bbb5cd
--- /dev/null
+++ b/tokenizer.h
@@ -0,0 +1,146 @@
+/** \file tokenizer.h
+
+ A specialized tokenizer for tokenizing the fish language. In the
+ future, the tokenizer should be extended to support marks,
+ tokenizing multiple strings and disposing of unused string
+ segments.
+*/
+
+/**
+ Token types
+*/
+enum token_type
+{
+ TOK_NONE, /**< Tokenizer not yet constructed */
+ TOK_ERROR, /**< Error reading token */
+ TOK_INVALID,/**< Invalid token */
+ TOK_STRING,/**< String token */
+ TOK_PIPE,/**< Pipe token */
+ TOK_END,/**< End token */
+ TOK_REDIRECT_OUT, /**< redirection token */
+ TOK_REDIRECT_APPEND,/**< redirection append token */
+ TOK_REDIRECT_IN,/**< input redirection token */
+ TOK_REDIRECT_FD,/**< redirection to new fd token */
+ TOK_BACKGROUND,/**< send job to bg token */
+ TOK_COMMENT/**< comment token */
+}
+;
+
+/**
+ Flag telling the tokenizer to accept incomplete parameters,
+ i.e. parameters with mismatching paranthesis, etc. This is useful
+ for tab-completion.
+*/
+#define TOK_ACCEPT_UNFINISHED 1
+
+/**
+ Flag telling the tokenizer not to remove comments. Useful for
+ syntax highlighting.
+*/
+#define TOK_SHOW_COMMENTS 2
+
+
+/**
+ The tokenizer struct.
+*/
+typedef struct
+{
+ /** A pointer into the original string, showing where the next token begins */
+ wchar_t *buff;
+ /** A copy of the original string */
+ wchar_t *orig_buff;
+ /** A pointer to the last token*/
+ wchar_t *last;
+
+ /** Type of last token*/
+ int last_type;
+ /** Length of last token*/
+ int last_len;
+ /** Offset of last token*/
+ int last_pos;
+ /** Whether there are more tokens*/
+ int has_next;
+ /** Whether incomplete tokens are accepted*/
+ int accept_unfinished;
+ /** Whether commants should be returned*/
+ int show_comments;
+ /** Type of last quote, can be either ' or ".*/
+ wchar_t last_quote;
+}
+tokenizer;
+
+/**
+ Initialize the tokenizer. b is the string that is to be
+ tokenized. It is not copied, and should not be freed by the caller
+ until after the tokenizer is destroyed.
+
+ \param tok The tokenizer to initialize
+ \param b The string to tokenize
+ \param flags Flags to the tokenizer. Setting TOK_ACCEPT_UNFINISHED will cause the tokenizer
+ to accept incomplete tokens, such as a subshell without a closing
+ parenthesis, as a valid token. Setting TOK_SHOW_COMMENTS will return comments as tokens
+
+*/
+void tok_init( tokenizer *tok, const wchar_t *b, int flags );
+
+/**
+ Jump to the next token.
+*/
+void tok_next( tokenizer *tok );
+
+/**
+ Returns the type of the last token. Must be one of the values in the token_type enum.
+*/
+int tok_last_type( tokenizer *tok );
+
+/**
+ Returns the last token string. The string should not be freed by the caller.
+*/
+wchar_t *tok_last( tokenizer *tok );
+
+/**
+ Returns the type of quote from the last TOK_QSTRING
+*/
+wchar_t tok_last_quote( tokenizer *tok );
+
+/**
+ Returns true as long as there are more tokens left
+*/
+int tok_has_next( tokenizer *tok );
+
+/**
+ Returns the position of the beginning of the current token in the original string
+*/
+int tok_get_pos( tokenizer *tok );
+
+/**
+ Destroy the tokenizer and free asociated memory
+*/
+void tok_destroy( tokenizer *tok );
+
+
+/**
+ Returns the original string to tokenizer
+ */
+wchar_t *tok_string( tokenizer *tok );
+
+
+/**
+ Returns only the first token from the specified string. This is a
+ convenience function, used to retrieve the first token of a
+ string. This can be useful for error messages, etc.
+
+ The string should be freed. After use.
+*/
+wchar_t *tok_first( const wchar_t *str );
+
+/**
+ Move tokenizer position
+*/
+void tok_set_pos( tokenizer *tok, int pos );
+
+/**
+ Returns a string description of the specified token type
+*/
+const wchar_t *tok_get_desc( int type );
+
diff --git a/user_doc.head.html b/user_doc.head.html
new file mode 100644
index 00000000..268e951d
--- /dev/null
+++ b/user_doc.head.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+<title>fish user documentation</title>
+<link href="doxygen.css" rel="stylesheet" type="text/css">
+</head>
+<body>
+
+<div class="qindex">
+
+ <a class="qindex" href="http://roo.no-ip.org/fish/index.html"><tt>fish</tt> home</a>
+|
+ <a class="qindex" href="index.html">Main documentation page</a>
+|
+<a class="qindex" href="about.html">About fish</a>
+|
+<a class="qindex" href="commands.html">External commands</a>
+|
+<a class="qindex" href="difference.html">How fish differs from other shells</a>
+|
+<a class="qindex" href="builtins.html">Builtin commands</a>
+|
+<a class="qindex" href="license.html">License</a>
+
+</div>
+
diff --git a/util.c b/util.c
new file mode 100644
index 00000000..3ea19ea4
--- /dev/null
+++ b/util.c
@@ -0,0 +1,986 @@
+/** \file util.c
+ Generic utilities library.
+
+ Contains datastructures such as hash tables, automatically growing array lists, priority queues, etc.
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+#include <math.h>
+#include <sys/time.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <wctype.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+
+
+#include "util.h"
+#include "common.h"
+#include "wutil.h"
+
+/**
+ Minimum allocated size for data structures. Used to avoid excessive
+ memory allocations for lists, hash tables, etc, which are nearly
+ empty.
+*/
+#define MIN_SIZE 128
+
+/**
+ Maximum number of characters that can be inserted using a single
+ call to sb_printf. This is needed since vswprintf doesn't tell us
+ what went wrong. We don't know if we ran out of space or something
+ else went wrong. Therefore we assume that any error is an out of
+ memory-error and try again until we reach this size.
+*/
+#define SB_MAX_SIZE 32767
+
+float minf( float a,
+ float b )
+{
+ return a<b?a:b;
+}
+
+
+float maxf( float a,
+ float b )
+{
+ return a>b?a:b;
+}
+
+int mini( int a,
+ int b )
+{
+ return a<b?a:b;
+}
+
+
+int maxi( int a,
+ int b )
+{
+ return a>b?a:b;
+}
+
+
+/* Queue functions */
+
+
+void q_init( queue_t *q )
+{
+ q->start = (void **)malloc( sizeof(void*)*1 );
+ q->stop = &q->start[1];
+ q->put_pos = q->get_pos = q->start;
+}
+
+void q_destroy( queue_t *q )
+{
+ free( q->start );
+}
+
+/*
+ static q_print( queue_t *q )
+ {
+ int i;
+ int size = (q->stop-q->start);
+
+ printf( "Storlek: %d\n", size );
+ for( i=0; i< size; i++ )
+ {
+ printf( " %c%c %d: %d\n",
+ &q->start[i]==q->get_pos?'g':' ',
+ &q->start[i]==q->put_pos?'p':' ',
+ i,
+ q->start[i] );
+ }
+ }
+
+*/
+
+/**
+ Reallocate the queue_t
+*/
+static int q_realloc( queue_t *q )
+{
+ void **old_start = q->start;
+ void **old_stop = q->stop;
+ int diff;
+ int new_size;
+
+ new_size = 2*(q->stop-q->start);
+
+ q->start=(void**)realloc( q->start, sizeof(void*)*new_size );
+ if( q->start == 0 )
+ {
+ q->start = old_start;
+ return 0;
+ }
+
+ diff = q->start - old_start;
+ q->get_pos += diff;
+ q->stop = &q->start[new_size];
+ memcpy( old_stop + diff, q->start, sizeof(void*)*(q->get_pos-q->start));
+ q->put_pos = old_stop + diff + (q->get_pos-q->start);
+
+ return 1;
+}
+
+int q_put( queue_t *q, void *e )
+{
+ *q->put_pos = e;
+
+// fprintf( stderr, "Put element %d to queue %d\n", e, q );
+
+ if( ++q->put_pos == q->stop )
+ q->put_pos = q->start;
+ if( q->put_pos == q->get_pos )
+ return q_realloc( q );
+ return 1;
+}
+
+void *q_get( queue_t *q)
+{
+ void *e = *q->get_pos;
+ if( ++q->get_pos == q->stop )
+ q->get_pos = q->start;
+ return e;
+}
+
+void *q_peek( queue_t *q )
+{
+ return *q->get_pos;
+}
+
+int q_empty( queue_t *q )
+{
+// fprintf( stderr, "Queue %d is %s\n", q, (q->put_pos == q->get_pos)?"empty":"non-empty" );
+ return q->put_pos == q->get_pos;
+}
+
+/* Stack functions */
+
+
+
+
+/* Hash table functions */
+
+void hash_init2( hash_table_t *h,
+ int (*hash_func)(const void *key),
+ int (*compare_func)(const void *key1, const void *key2),
+ size_t capacity)
+{
+ int i;
+ size_t sz = capacity*4/3+1;
+
+ h->arr = malloc( sizeof(hash_struct_t)*sz );
+ h->size = sz;
+ for( i=0; i< sz; i++ )
+ h->arr[i].key = 0;
+ h->count=0;
+ h->hash_func = hash_func;
+ h->compare_func = compare_func;
+}
+
+void hash_init( hash_table_t *h,
+ int (*hash_func)(const void *key),
+ int (*compare_func)(const void *key1, const void *key2) )
+{
+ hash_init2( h, hash_func, compare_func, 31 );
+}
+
+
+void hash_destroy( hash_table_t *h )
+{
+ free( h->arr );
+}
+
+/**
+ Search for the specified hash key in the table
+ \return index in the table, or to the first free index if the key is not in the table
+*/
+static int hash_search( hash_table_t *h,
+ const void *key )
+{
+ int hv = h->hash_func( key );
+ int pos = abs(hv) % h->size;
+ while(1)
+ {
+ if( (h->arr[pos].key == 0 ) ||
+ ( h->compare_func( h->arr[pos].key, key ) ) )
+ {
+ return pos;
+ }
+ pos++;
+ pos %= h->size;
+ }
+}
+
+/**
+ Reallocate the hash array. This is quite expensive, as every single entry has to be rehashed and moved.
+*/
+static int hash_realloc( hash_table_t *h,
+ int sz )
+{
+
+ /* Avoid reallocating when using pathetically small tables */
+ if( ( sz < h->size ) && (h->size < MIN_SIZE))
+ return 1;
+ sz = maxi( sz, MIN_SIZE );
+
+ hash_struct_t *old_arr = h->arr;
+ int old_size = h->size;
+
+ int i;
+
+ h->arr = malloc( sizeof( hash_struct_t) * sz );
+ if( h->arr == 0 )
+ {
+ h->arr = old_arr;
+ return 0;
+ }
+
+ memset( h->arr,
+ 0,
+ sizeof( hash_struct_t) * sz );
+ h->size = sz;
+
+ for( i=0; i<old_size; i++ )
+ {
+ if( old_arr[i].key != 0 )
+ {
+ int pos = hash_search( h, old_arr[i].key );
+ h->arr[pos].key = old_arr[i].key;
+ h->arr[pos].data = old_arr[i].data;
+ }
+ }
+ free( old_arr );
+
+ return 1;
+}
+
+
+int hash_put( hash_table_t *h,
+ const void *key,
+ const void *data )
+{
+ int pos;
+
+ if( (float)(h->count+1)/h->size > 0.75f )
+ {
+ if( !hash_realloc( h, (h->size+1) * 2 -1 ) )
+ {
+ return 0;
+ }
+ }
+
+ pos = hash_search( h, key );
+
+ if( h->arr[pos].key == 0 )
+ {
+ h->count++;
+ }
+
+ h->arr[pos].key = key;
+ h->arr[pos].data = data;
+ return 1;
+}
+
+const void *hash_get( hash_table_t *h,
+ const void *key )
+{
+ int pos = hash_search( h, key );
+ if( h->arr[pos].key == 0 )
+ return 0;
+ else
+ return h->arr[pos].data;
+}
+
+const void *hash_get_key( hash_table_t *h,
+ const void *key )
+{
+ int pos = hash_search( h, key );
+ if( h->arr[pos].key == 0 )
+ return 0;
+ else
+ return h->arr[pos].key;
+}
+
+int hash_get_count( hash_table_t *h)
+{
+ return h->count;
+}
+
+void hash_remove( hash_table_t *h,
+ const void *key,
+ const void **old_key,
+ const void **old_val )
+{
+ int pos = hash_search( h, key );
+ int next_pos;
+
+ if( h->arr[pos].key == 0 )
+ {
+
+ if( old_key != 0 )
+ *old_key = 0;
+ if( old_val != 0 )
+ *old_val = 0;
+ return;
+ }
+
+ h->count--;
+
+ if( old_key != 0 )
+ *old_key = h->arr[pos].key;
+ if( old_val != 0 )
+ *old_val = h->arr[pos].data;
+
+ h->arr[pos].key = 0;
+
+ next_pos = pos+1;
+ next_pos %= h->size;
+
+ while( h->arr[next_pos].key != 0 )
+ {
+
+ int hv = h->hash_func( h->arr[next_pos].key );
+ int ideal_pos = abs( hv ) % h->size;
+ int dist_old = (next_pos - ideal_pos + h->size)%h->size;
+ int dist_new = (pos - ideal_pos + h->size)%h->size;
+ if ( dist_new < dist_old )
+ {
+ h->arr[pos].key = h->arr[next_pos].key;
+ h->arr[pos].data = h->arr[next_pos].data;
+ h->arr[next_pos].key = 0;
+ pos = next_pos;
+ }
+ next_pos++;
+
+ next_pos %= h->size;
+
+ }
+
+ if( (float)(h->count+1)/h->size < 0.2f && h->count < 63 )
+ {
+ hash_realloc( h, (h->size+1) / 2 -1 );
+ }
+
+ return;
+}
+
+int hash_contains( hash_table_t *h,
+ const void *key )
+{
+ int pos = hash_search( h, key );
+ return h->arr[pos].key != 0;
+}
+
+/**
+ Push hash value into array_list_t
+*/
+static void hash_put_data( const void *key,
+ const void *data,
+ void *al )
+{
+ al_push( (array_list_t *)al,
+ data );
+}
+
+
+void hash_get_data( hash_table_t *h,
+ array_list_t *arr )
+{
+ hash_foreach2( h, &hash_put_data, arr );
+}
+
+/**
+ Push hash key into array_list_t
+*/
+static void hash_put_key( const void *key, const void *data, void *al )
+{
+ al_push( (array_list_t *)al, key );
+}
+
+
+void hash_get_keys( hash_table_t *h,
+ array_list_t *arr )
+{
+ hash_foreach2( h, &hash_put_key, arr );
+}
+
+void hash_foreach( hash_table_t *h,
+ void (*func)(const void *, const void *) )
+{
+ int i;
+ for( i=0; i<h->size; i++ )
+ {
+ if( h->arr[i].key != 0 )
+ {
+ func( h->arr[i].key, h->arr[i].data );
+ }
+ }
+}
+
+void hash_foreach2( hash_table_t *h,
+ void (*func)( const void *, const void *, void * ),
+ void *aux )
+{
+ int i;
+ for( i=0; i<h->size; i++ )
+ {
+ if( h->arr[i].key != 0 )
+ {
+ func( h->arr[i].key, h->arr[i].data, aux );
+ }
+ }
+}
+
+
+int hash_str_cmp( const void *a, const void *b )
+{
+ return strcmp((char *)a,(char *)b) == 0;
+}
+
+/**
+ Helper function for hash_wcs_func
+*/
+static uint rotl5( uint in )
+{
+ return (in<<5|in>>27);
+}
+
+
+int hash_str_func( const void *data )
+{
+ int res = 0x67452301u;
+ const char *str = data;
+
+ while( *str )
+ res = (18499*rotl5(res)) ^ *str++;
+
+ return res;
+}
+
+int hash_wcs_func( const void *data )
+{
+ int res = 0x67452301u;
+ const wchar_t *str = data;
+
+ while( *str )
+ res = (18499*rotl5(res)) ^ *str++;
+
+ return res;
+}
+
+
+int hash_wcs_cmp( const void *a, const void *b )
+{
+ return wcscmp((wchar_t *)a,(wchar_t *)b) == 0;
+}
+
+void pq_init( priority_queue_t *q,
+ int (*compare)(void *e1, void *e2) )
+{
+ q->arr=0;
+ q->size=0;
+ q->count=0;
+ q->compare = compare;
+}
+
+/**
+ Check that the priority queue is in a valid state
+*/
+/*
+ static void pq_check( priority_queue_t *q, int i )
+ {
+ int l,r;
+ if( q->count <= i )
+ return;
+
+ l=i*2+1;
+ r=i*2+2;
+
+
+ if( (q->count > l) && (q->compare(q->arr[i], q->arr[l]) < 0) )
+ {
+ printf( "ERROR: Place %d less than %d\n", i, l );
+ }
+ if( (q->count > r) && (q->compare(q->arr[i], q->arr[r]) < 0) )
+ {
+ printf( "ERROR: Place %d less than %d\n", i, r );
+ }
+ pq_check( q, l );
+ pq_check( q, r );
+ }
+*/
+
+int pq_put( priority_queue_t *q,
+ void *e )
+{
+ int i;
+
+ if( q->size == q->count )
+ {
+ void **old_arr = q->arr;
+ int old_size = q->size;
+ q->size = maxi( 4, 2*q->size );
+ q->arr = (void **)realloc( q->arr, sizeof(void*)*q->size );
+ if( q->arr == 0 )
+ {
+ q->arr = old_arr;
+ q->size = old_size;
+ return 0;
+ }
+ }
+
+ i = q->count;
+ while( (i>0) && (q->compare( q->arr[(i-1)/2], e )<0 ) )
+ {
+ q->arr[i] = q->arr[(i-1)/2];
+ i = (i-1)/2;
+ }
+ q->arr[i]=e;
+
+ q->count++;
+
+ return 1;
+
+}
+
+/**
+ Make a valid head
+*/
+static void pq_heapify( priority_queue_t *q, int i )
+{
+ int l, r, largest;
+ l = 2*(i)+1;
+ r = 2*(i)+2;
+ if( (l < q->count) && (q->compare(q->arr[l],q->arr[i])>0) )
+ {
+ largest = l;
+ }
+ else
+ {
+ largest = i;
+ }
+ if( (r < q->count) && (q->compare( q->arr[r],q->arr[largest])>0) )
+ {
+ largest = r;
+ }
+
+ if( largest != i )
+ {
+ void *tmp = q->arr[largest];
+ q->arr[largest]=q->arr[i];
+ q->arr[i]=tmp;
+ pq_heapify( q, largest );
+ }
+}
+
+void *pq_get( priority_queue_t *q )
+{
+ void *result = q->arr[0];
+ q->arr[0] = q->arr[--q->count];
+ pq_heapify( q, 0 );
+
+/* pq_check(q, 0 ); */
+/* pq_print( q ); */
+
+ return result;
+}
+
+void *pq_peek( priority_queue_t *q )
+{
+ return q->arr[0];
+}
+
+int pq_empty( priority_queue_t *q )
+{
+ return q->count == 0;
+}
+
+int pq_get_count( priority_queue_t *q )
+{
+ return q->count;
+}
+
+void pq_destroy( priority_queue_t *q )
+{
+ free( q->arr );
+}
+
+
+void al_init( array_list_t *l )
+{
+ memset( l, 0, sizeof( array_list_t ) );
+}
+
+void al_destroy( array_list_t *l )
+{
+ free( l->arr );
+}
+
+int al_push( array_list_t *l, const void *o )
+{
+ if( l->pos >= l->size )
+ {
+ int new_size = l->pos == 0 ? MIN_SIZE : 2 * l->pos;
+ void *tmp = realloc( l->arr, sizeof( void *)*new_size );
+ if( tmp == 0 )
+ return 0;
+ l->arr = tmp;
+ l->size = new_size;
+ }
+ l->arr[l->pos++] = o;
+ return 1;
+}
+
+int al_push_all( array_list_t *a, array_list_t *b )
+{
+ int k;
+ for( k=0; k<al_get_count( b ); k++ )
+ {
+ if( !al_push( a, al_get( b, k ) ) )
+ return 0;
+ }
+ return 1;
+}
+
+
+int al_set( array_list_t *l, int pos, const void *o )
+{
+ int old_pos;
+
+ if( pos < 0 )
+ return 0;
+ if( pos < l->pos )
+ {
+ l->arr[pos] = o;
+ return 1;
+ }
+ old_pos=l->pos;
+
+ l->pos = pos;
+ if( al_push( l, o ) )
+ {
+/* fwprintf( stderr, L"Clearing from index %d to index %d\n",
+ old_pos, pos );
+*/
+ memset( &l->arr[old_pos],
+ 0,
+ sizeof(void *) * (pos - old_pos) );
+ return 1;
+ }
+ return 0;
+}
+
+const void *al_get( array_list_t *l, int pos )
+{
+ if( pos < 0 )
+ return 0;
+ if( pos >= l->pos )
+ return 0;
+ return l->arr[pos];
+}
+
+void al_truncate( array_list_t *l, int new_sz )
+{
+ l->pos = new_sz;
+}
+
+const void *al_pop( array_list_t *l )
+{
+ const void *e = l->arr[--l->pos];
+ if( (l->pos*3 < l->size) && (l->size < MIN_SIZE) )
+ {
+ const void ** old_arr = l->arr;
+ int old_size = l->size;
+ l->size = l->size/2;
+ l->arr = realloc( l->arr, sizeof(void*)*l->size );
+ if( l->arr == 0 )
+ {
+ l->arr = old_arr;
+ l->size = old_size;
+ }
+ }
+ return e;
+}
+
+const void *al_peek( array_list_t *l )
+{
+
+ return l->pos>0?l->arr[l->pos-1]:0;
+}
+
+int al_empty( array_list_t *l )
+{
+ return l->pos == 0;
+}
+
+int al_get_count( array_list_t *l )
+
+{
+ return l->pos;
+}
+
+void al_foreach( array_list_t *l, void (*func)( const void * ))
+{
+ int i;
+ for( i=0; i<l->pos; i++ )
+ func( l->arr[i] );
+}
+
+void al_foreach2( array_list_t *l, void (*func)( const void *, void *), void *aux)
+{
+ int i;
+ for( i=0; i<l->pos; i++ )
+ func( l->arr[i], aux );
+}
+
+int wcsfilecmp( const wchar_t *a, const wchar_t *b )
+{
+ if( *a==0 )
+ {
+ if( *b==0)
+ return 0;
+ return -1;
+ }
+ if( *b==0 )
+ {
+ return 1;
+ }
+ int secondary_diff=0;
+ if( iswdigit( *a ) && iswdigit( *b ) )
+ {
+ wchar_t *aend, *bend;
+ long al = wcstol( a, &aend, 10 );
+ long bl = wcstol( b, &bend, 10 );
+ int diff = al - bl;
+ if( diff )
+ return diff>0?2:-2;
+
+ secondary_diff = (aend-a) - (bend-b);
+
+ a=aend-1;
+ b=bend-1;
+ }
+ else
+ {
+ int diff = towlower(*a) - towlower(*b);
+ if( diff != 0 )
+ return (diff>0)?2:-2;
+
+ secondary_diff = *a-*b;
+ }
+
+ int res = wcsfilecmp( a+1, b+1 );
+ switch( abs(res) )
+ {
+ case 2:
+ return res;
+ default:
+ if( secondary_diff )
+ return secondary_diff>0?1:-1;
+ }
+ return 0;
+
+}
+
+void sb_init( string_buffer_t * b)
+{
+ wchar_t c=0;
+ memset( b, 0, sizeof(string_buffer_t) );
+ b_append( b, &c, sizeof( wchar_t));
+ b->used -= sizeof(wchar_t);
+}
+
+void sb_append( string_buffer_t *b, const wchar_t * s)
+{
+// fwprintf( stderr, L"Append string \'%ls\'\n", s );
+
+ if( !s )
+ return;
+
+
+ b_append( b, s, sizeof(wchar_t)*(wcslen(s)+1) );
+ b->used -= sizeof(wchar_t);
+}
+
+void sb_append_substring( string_buffer_t *b, const wchar_t *s, size_t l )
+{
+ wchar_t tmp=0;
+
+ if( !s )
+ return;
+
+ b_append( b, s, sizeof(wchar_t)*l );
+ b_append( b, &tmp, sizeof(wchar_t) );
+ b->used -= sizeof(wchar_t);
+}
+
+
+void sb_append_char( string_buffer_t *b, wchar_t c )
+{
+ wchar_t buff[2]=
+ {
+ c, 0
+ }
+ ;
+ sb_append( b, buff );
+
+}
+
+void sb_append2( string_buffer_t *b, ... )
+{
+ va_list va;
+ wchar_t *arg;
+
+ va_start( va, b );
+ while( (arg=va_arg(va, wchar_t *) )!= 0 )
+ {
+ sb_append( b, arg );
+ }
+ va_end( va );
+}
+
+int sb_printf( string_buffer_t *buffer, const wchar_t *format, ... )
+{
+ va_list va;
+ int res;
+
+ if( !buffer->length )
+ {
+ buffer->length = MIN_SIZE;
+ buffer->buff = malloc( MIN_SIZE );
+ if( !buffer->buff )
+ die_mem();
+ }
+
+
+ while( 1 )
+ {
+
+ va_start( va, format );
+ res = vswprintf( (wchar_t *)((char *)buffer->buff+buffer->used),
+ (buffer->length-buffer->used)/sizeof(wchar_t),
+ format,
+ va );
+
+ if( res >= 0 )
+ {
+ buffer->used+= res*sizeof(wchar_t);
+ break;
+ }
+
+ /*
+ As far as I know, there is no way to check if a
+ vswprintf-call failed because of a badly formated string
+ option or because the supplied destination string was to
+ small. In GLIBC, errno seems to be set to EINVAL either way.
+
+ Because of this, sb_printf will on failiure try to
+ increase the buffer size until the free space is larger than
+ SB_MAX_SIZE, at which point it will conclude that the error
+ was probably due to a badly formated string option, and
+ return an error.
+ */
+
+ if( buffer->length - buffer->used > SB_MAX_SIZE )
+ break;
+
+ buffer->buff = realloc( buffer->buff, 2*buffer->length );
+ if( !buffer->buff )
+ die_mem();
+ buffer->length *= 2;
+ }
+ va_end( va );
+ return res;
+}
+
+
+
+
+void sb_destroy( string_buffer_t * b )
+{
+ free( b->buff );
+}
+
+void sb_clear( string_buffer_t * b )
+{
+ free( b->buff );
+ sb_init( b );
+}
+
+
+void b_init( buffer_t *b)
+{
+ memset( b,0,sizeof(buffer_t));
+}
+
+
+
+void b_destroy( buffer_t *b )
+{
+ free( b->buff );
+}
+
+
+void b_append( buffer_t *b, const void *d, ssize_t len )
+{
+ if( len<=0 )
+ return;
+
+ if( !b )
+ {
+ debug( 2, L"Copy to null buffer" );
+ return;
+ }
+
+ if( !d )
+ {
+ debug( 2, L"Copy from null pointer" );
+ return;
+ }
+
+ if( len < 0 )
+ {
+ debug( 2, L"Negative number of characters to be copied" );
+ return;
+ }
+
+
+ if( b->length <= (b->used + len) )
+ {
+ size_t l = maxi( b->length*2,
+ maxi( b->used+len+MIN_SIZE,MIN_SIZE));
+
+ void *d = realloc( b->buff, l );
+ if( !d )
+ {
+ die_mem();
+
+ }
+ b->buff=d;
+ b->length = l;
+ }
+ memcpy( ((char*)b->buff)+b->used,
+ d,
+ len );
+
+// fwprintf( stderr, L"Copy %s, new value %s\n", d, b->buff );
+ b->used+=len;
+}
+
+long long get_time()
+{
+ struct timeval time_struct;
+ gettimeofday( &time_struct, 0 );
+ return 1000000ll*time_struct.tv_sec+time_struct.tv_usec;
+}
+
diff --git a/util.h b/util.h
new file mode 100644
index 00000000..ef14fb4f
--- /dev/null
+++ b/util.h
@@ -0,0 +1,463 @@
+/** \file util.h
+ Generic utilities library.
+*/
+
+/**
+ Data structure for an automatically resizing dynamically allocated queue,
+*/
+typedef struct queue
+{
+ /** Start of the array */
+ void **start;
+ /** End of the array*/
+ void **stop;
+ /** Where to insert elements */
+ void **put_pos;
+ /** Where to remove elements */
+ void **get_pos;
+}
+queue_t;
+
+/**
+ Internal struct used by hash_table_t.
+*/
+typedef struct
+{
+ /** Hash key*/
+ const void *key;
+ /** Value */
+ const void *data;
+}
+hash_struct_t;
+
+/**
+ Data structure for the hash table implementaion. A hash table allows for
+ retrieval and removal of any element in O(1), so long as a proper
+ hash function is supplied.
+
+ The hash table is implemented using a single hash function and
+ element storage directly in the array. When a collision occurs, the
+ hashtable iterates until a zero element is found. When the table is
+ 75% full, it will automatically reallocate itself. This
+ reallocation takes O(n) time. The table is guaranteed to never be
+ more than 75% full or less than 30% full (Unless the table is
+ nearly empty). Its size is always a Mersenne number.
+
+*/
+
+typedef struct hash_table
+{
+ /** The array containing the data */
+ hash_struct_t *arr;
+ /** Number of elements */
+ int count;
+ /** Length of array */
+ int size;
+ /** Hash function */
+ int (*hash_func)( const void *key );
+ /** Comparison function */
+ int (*compare_func)( const void *key1, const void *key2 );
+}
+hash_table_t;
+
+/**
+ Data structure for an automatically resizing dynamically allocated
+ priority queue. A priority queue allows quick retrieval of the
+ smallest element of a set (This implementation uses O(log n) time).
+ This implementation uses a heap for storing the queue.
+*/
+typedef struct priority_queue
+{
+ /** Array contining the data */
+ void **arr;
+ /** Number of elements*/
+ int count;
+ /** Length of array */
+ int size;
+ /** Comparison function */
+ int (*compare)(void *e1, void *e2);
+}
+priority_queue_t;
+
+/**
+ Array list struct.
+ A dynamically growing list that supports stack operations.
+*/
+typedef struct array_list
+{
+ /** Array containing the data */
+ const void **arr;
+ /** Position to append elements at*/
+ int pos;
+ /** Length of array */
+ int size;
+}
+array_list_t;
+
+/**
+ Linked list node.
+*/
+typedef struct _ll_node
+{
+ /** Next node */
+ struct _ll_node *next, /** Previous node */ *prev;
+ /** Node data */
+ void *data;
+}
+ll_node_t;
+
+/**
+ Buffer for concatenating arbitrary data.
+*/
+typedef struct buffer
+{
+ char *buff; /**<data buffer*/
+ size_t length; /**< Size of buffer */
+ size_t used; /**< Size of data in buffer */
+}
+buffer_t;
+
+
+/**
+ String buffer struct. An autoallocating buffer used for
+ concatenating strings. This is really just a buffer_t.
+*/
+typedef buffer_t string_buffer_t;
+
+
+
+/**
+ Returns the larger of two ints
+*/
+int maxi( int a, int b );
+/**
+ Returns the smaller of two ints
+ */
+int mini( int a, int b );
+/**
+ Returns the larger of two floats
+*/
+float maxf( float a, float b );
+/**
+ Returns the smaller of two floats
+*/
+float minf( float a, float b );
+
+/*
+ All the datastuctures below autoresize. The queue, stack and
+ priority queue are all impemented using an array and are guaranteed
+ to never be less than 50% full.
+*/
+
+/**
+ Initialize the queue. A queue is a FIFO buffer, i.e. the first
+ element to be inserted into the buffer is the first element to be
+ returned.
+*/
+void q_init( queue_t *q );
+/** Destroy the queue */
+void q_destroy( queue_t *q );
+/** Insert element into queue */
+int q_put( queue_t *q, void *e );
+/** Remove and return next element from queue */
+void *q_get( queue_t *q);
+/** Return next element from queue without removing it */
+void *q_peek( queue_t *q);
+/** Returns 1 if the queue is empty, 0 otherwise */
+int q_empty( queue_t *q );
+
+/**
+ Initialize a hash table. The hash function must never return the value 0.
+*/
+void hash_init( hash_table_t *h,
+ int (*hash_func)(const void *key),
+ int (*compare_func)(const void *key1, const void *key2) );
+
+/**
+ Initialize a hash table. The hash function must never return the value 0.
+*/
+void hash_init2( hash_table_t *h,
+ int (*hash_func)(const void *key),
+ int (*compare_func)(const void *key1, const void *key2),
+ size_t capacity);
+
+/**
+ Destroy the hash table and free associated memory.
+*/
+void hash_destroy( hash_table_t *h );
+/**
+ Set the key/value pair for the hashtable.
+*/
+int hash_put( hash_table_t *h,
+ const void *key,
+ const void *data );
+/**
+ Returns the data with the associated key, or 0 if no such key is in the hashtable
+*/
+const void *hash_get( hash_table_t *h,
+ const void *key );
+/**
+ Returns the hash tables version of the specified key
+*/
+const void *hash_get_key( hash_table_t *h,
+ const void *key );
+
+/**
+ Returns the number of key/data pairs in the table.
+*/
+int hash_get_count( hash_table_t *h);
+/**
+ Remove the specified key from the hash table
+
+ \param h The hashtable
+ \param key The key
+ \param old_key If not 0, a pointer to the old key will be stored at the specified address
+ \param old_data If not 0, a pointer to the data will be stored at the specified address
+*/
+void hash_remove( hash_table_t *h,
+ const void *key,
+ const void **old_key,
+ const void **old_data );
+/**
+ Checks whether the specified key is in the hash table
+*/
+int hash_contains( hash_table_t *h,
+ const void *key );
+
+/**
+ Appends all keys in the table to the specified list
+*/
+void hash_get_keys( hash_table_t *h,
+ array_list_t *arr );
+/**
+ Appends all data elements in the table to the specified list
+*/
+void hash_get_data( hash_table_t *h,
+ array_list_t *arr );
+/** Call the function func for each key/data pair in the table*/
+void hash_foreach( hash_table_t *h,
+ void (*func)( const void *, const void * ) );
+/** Same as hash_foreach, but the function func takes an additional
+ * argument, which is provided by the caller in the variable aux */
+void hash_foreach2( hash_table_t *h, void (*func)( const void *,
+ const void *,
+ void *),
+ void *aux );
+
+/**
+ Hash function suitable for character strings.
+*/
+int hash_str_func( const void *data );
+/**
+ Hash comparison function suitable for character strings
+*/
+int hash_str_cmp( const void *a, const void *b );
+
+/**
+ Hash function suitable for wide character strings.
+*/
+int hash_wcs_func( const void *data );
+
+/**
+ Hash comparison function suitable for wide character strings
+*/
+int hash_wcs_cmp( const void *a, const void *b );
+
+/** Initialize the priority queue
+
+ \param q the queue to initialize
+ \param compare a comparison function that can compare two entries in the queue
+*/
+void pq_init( priority_queue_t *q,
+ int (*compare)(void *e1, void *e2) );
+/**
+ Add element to the queue
+
+ \param q the queue
+ \param e the new element
+
+*/
+int pq_put( priority_queue_t *q,
+ void *e );
+/**
+ Removes and returns the last entry in the priority queue
+*/
+void *pq_get( priority_queue_t *q );
+/**
+ Returns the last entry in the priority queue witout removing it.
+*/
+void *pq_peek( priority_queue_t *q );
+
+/**
+ Returns 1 if the priority queue is empty, 0 otherwise.
+*/
+int pq_empty( priority_queue_t *q );
+
+/**
+ Returns the number of elements in the priority queue.
+*/
+int pq_get_count( priority_queue_t *q );
+
+/**
+ Destroy the priority queue and free memory used by it.
+*/
+void pq_destroy( priority_queue_t *q );
+
+/** Initialize the list. */
+void al_init( array_list_t *l );
+/** Destroy the list and free memory used by it.*/
+void al_destroy( array_list_t *l );
+/**
+ Append element to list
+
+ \param l The list
+ \param o The element
+ \return
+ \return 1 if succesfull, 0 otherwise
+*/
+int al_push( array_list_t *l, const void *o );
+/**
+ Append all elements of a list to another
+
+ \param a The destination list
+ \param b The source list
+ \return 1 if succesfull, 0 otherwise
+*/
+int al_push_all( array_list_t *a, array_list_t *b );
+/**
+ Sets the element at the specified index
+
+ \param l The array_list_t
+ \param pos The index
+ \param o The element
+*/
+int al_set( array_list_t *l, int pos, const void *o );
+/**
+ Returns the element at the specified index
+
+ \param l The array_list_t
+ \param pos The index
+ \return The element
+*/
+const void *al_get( array_list_t *l, int pos );
+/**
+ Truncates the list to new_sz items.
+*/
+void al_truncate( array_list_t *l, int new_sz );
+/**
+ Removes and returns the last entry in the list
+*/
+const void *al_pop( array_list_t *l );
+/**
+ Returns the number of elements in the list
+*/
+int al_get_count( array_list_t *l );
+/**
+ Returns the last entry in the list witout removing it.
+*/
+const void *al_peek( array_list_t *l );
+/** Returns 1 if the list is empty, 0 otherwise*/
+int al_empty( array_list_t *l);
+/** Call the function func for each entry in the list*/
+void al_foreach( array_list_t *l, void (*func)(const void * ));
+/**
+ Same as al_foreach, but the function func takes an additional
+ argument, which is provided by the caller in the variable aux
+*/
+void al_foreach2( array_list_t *l, void (*func)(const void *, void *), void *aux);
+
+/**
+ Compares two wide character strings without case but with
+ a logical ordering for numbers.
+
+ This function tries to order strings in a way which is intuitive to
+ humans with regards to sorting strings containing numbers.
+
+ Most sorting functions would sort the strings 'file1.txt'
+ 'file5.txt' and 'file12.txt' as:
+
+ file1.txt
+ file12.txt
+ file5.txt
+
+ This function regards any sequence of digits as a single entity
+ when performing comparisons, so the output is instead:
+
+ file1.txt
+ file5.txt
+ file12.txt
+
+ Which most people would find more intuitive.
+
+ The system breaks down if the user is using numbers of a base larger than 10.
+*/
+int wcsfilecmp( const wchar_t *a, const wchar_t *b );
+
+
+/**
+ Initialize the specified string_buffer
+*/
+void sb_init( string_buffer_t * );
+
+/**
+ Append a string to the buffer
+*/
+void sb_append( string_buffer_t *, const wchar_t * );
+
+/**
+ Append a part of a string to the buffer
+*/
+void sb_append_substring( string_buffer_t *, const wchar_t *, size_t );
+
+/**
+ Append a character to the buffer
+*/
+void sb_append_char( string_buffer_t *, wchar_t );
+
+
+/**
+ Append a null terminated list of strings to the buffer.
+ Example:
+
+ sb_append2( my_buff, L"foo", L"bar", 0 );
+*/
+void sb_append2( string_buffer_t *, ... );
+
+/**
+ Append formated string data to the buffer. This function internally
+ relies on \c vswprintf, so any filter options supported by that
+ function is also supported by this function
+*/
+int sb_printf( string_buffer_t *buffer, const wchar_t *format, ... );
+
+
+/**
+ Destroy the buffer and free it's memory
+*/
+void sb_destroy( string_buffer_t * );
+
+/**
+ Truncate the buffer.
+*/
+void sb_clear( string_buffer_t * );
+
+
+/**
+ Initialize the specified buffer_t
+ */
+void b_init( buffer_t *b);
+/**
+ Destroy the specified buffer_t
+*/
+
+void b_destroy( buffer_t *b );
+
+/**
+ Add data of the specified length to the specified buffer_t
+*/
+void b_append( buffer_t *b, const void *d, ssize_t len );
+
+/**
+ Get the current time in microseconds since Jan 1, 1970
+*/
+long long get_time();
diff --git a/wgetopt.c b/wgetopt.c
new file mode 100644
index 00000000..8e1d28f4
--- /dev/null
+++ b/wgetopt.c
@@ -0,0 +1,696 @@
+/* Getopt for GNU.
+ NOTE: getopt is now part of the C library, so if you don't know what
+ "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu
+ before changing it!
+
+ Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94
+ Free Software Foundation, Inc.
+
+This file is part of the GNU C Library. Its master source is NOT part of
+the C library, however. The master source lives in /gd/gnu/lib.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB. If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA. */
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+ Ditto for AIX 3.2 and <stdlib.h>. */
+#ifndef _NO_PROTO
+#define _NO_PROTO
+#endif
+
+#include "config.h"
+
+#if !defined (__STDC__) || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+#include <wchar.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+ contain conflicting prototypes for getopt. */
+#include <stdlib.h>
+#endif /* GNU C library. */
+
+/* This version of `getopt' appears to the caller like standard Unix `getopt'
+ but it behaves differently for the user, since it allows the user
+ to intersperse the options with the other arguments.
+
+ As `getopt' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Setting the environment variable POSIXLY_CORRECT disables permutation.
+ Then the behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+#include "wgetopt.h"
+#include "wutil.h"
+
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+wchar_t *woptarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns EOF, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `woptind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* XXX 1003.2 says this must be 1 before any call. */
+int woptind = 0;
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+static wchar_t *nextchar;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int wopterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int woptopt = '?';
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using `+' as the first character
+ of the list of option characters.
+
+ PERMUTE is the default. We permute the contents of ARGV as we scan,
+ so that eventually all the non-options are at the end. This allows options
+ to be given in any order, even with programs that were not written to
+ expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were written
+ to expect options and other ARGV-elements in any order and that care about
+ the ordering of the two. We describe each non-option ARGV-element
+ as if it were the argument of an option with character code 1.
+ Using `-' as the first character of the list of option characters
+ selects this mode of operation.
+
+ The special argument `--' forces an end of option-scanning regardless
+ of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+ `--' can cause `getopt' to return EOF with `woptind' != ARGC. */
+
+static enum
+{
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} ordering;
+
+/* Value of POSIXLY_CORRECT environment variable. */
+static char *posixly_correct;
+
+#ifdef __GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+ because there are many ways it can cause trouble.
+ On some systems, it contains special magic macros that don't work
+ in GCC. */
+#include <string.h>
+#define my_index wcschr
+#else
+
+/* Avoid depending on library functions or files
+ whose names are inconsistent. */
+
+char *getenv ();
+
+static wchar_t *
+my_index (str, chr)
+ const wchar_t *str;
+ int chr;
+{
+ while (*str)
+ {
+ if (*str == chr)
+ return (wchar_t *) str;
+ str++;
+ }
+ return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+ If not using GCC, it is ok not to declare it. */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+ That was relevant to code that was here before. */
+#if !defined (__STDC__) || !__STDC__
+/* gcc with -traditional declares the built-in strlen to return int,
+ and has done so at least since version 2.4.5. -- rms. */
+extern int wcslen (const wchar_t *);
+#endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+
+/* Handle permutation of arguments. */
+
+/* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first of them;
+ `last_nonopt' is the index after the last of them. */
+
+static int first_nonopt;
+static int last_nonopt;
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,woptind), which contains all
+ the options processed since those non-options were skipped.
+
+ `first_nonopt' and `last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+static void
+exchange (argv)
+ wchar_t **argv;
+{
+ int bottom = first_nonopt;
+ int middle = last_nonopt;
+ int top = woptind;
+ wchar_t *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+ while (top > middle && middle > bottom)
+ {
+ if (top - middle > middle - bottom)
+ {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else
+ {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ first_nonopt += (woptind - last_nonopt);
+ last_nonopt = woptind;
+}
+
+/* Initialize the internal data when the first call is made. */
+
+static const wchar_t *
+_wgetopt_initialize (optstring)
+ const wchar_t *optstring;
+{
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ first_nonopt = last_nonopt = woptind = 1;
+
+ nextchar = NULL;
+
+ posixly_correct = getenv ("POSIXLY_CORRECT");
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (optstring[0] == '-')
+ {
+ ordering = RETURN_IN_ORDER;
+ ++optstring;
+ }
+ else if (optstring[0] == '+')
+ {
+ ordering = REQUIRE_ORDER;
+ ++optstring;
+ }
+ else if (posixly_correct != NULL)
+ ordering = REQUIRE_ORDER;
+ else
+ ordering = PERMUTE;
+
+ return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `getopt' finds another option character, it returns that character,
+ updating `woptind' and `nextchar' so that the next call to `getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `getopt' returns `EOF'.
+ Then `woptind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `wopterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in `woptarg', otherwise `woptarg' is set to zero.
+
+ If OPTSTRING starts with `-' or `+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with `--' instead of `-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a `=', or else the in next ARGV-element.
+ When `getopt' finds a long-named option, it returns 0 if that option's
+ `flag' field is nonzero, the value of the option's `val' field
+ if the `flag' field is zero.
+
+ The elements of ARGV aren't really const, because we permute them.
+ But we pretend they're const in the prototype to be compatible
+ with other systems.
+
+ LONGOPTS is a vector of `struct option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options. */
+
+int
+_wgetopt_internal (argc, argv, optstring, longopts, longind, long_only)
+ int argc;
+ wchar_t *const *argv;
+ const wchar_t *optstring;
+ const struct woption *longopts;
+ int *longind;
+ int long_only;
+{
+ woptarg = NULL;
+
+ if (woptind == 0)
+ optstring = _wgetopt_initialize (optstring);
+
+ if (nextchar == NULL || *nextchar == '\0')
+ {
+ /* Advance to the next ARGV-element. */
+
+ if (ordering == PERMUTE)
+ {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (first_nonopt != last_nonopt && last_nonopt != woptind)
+ exchange ((wchar_t **) argv);
+ else if (last_nonopt != woptind)
+ first_nonopt = woptind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (woptind < argc
+ && (argv[woptind][0] != '-' || argv[woptind][1] == '\0'))
+ woptind++;
+ last_nonopt = woptind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (woptind != argc && !wcscmp (argv[woptind], L"--"))
+ {
+ woptind++;
+
+ if (first_nonopt != last_nonopt && last_nonopt != woptind)
+ exchange ((wchar_t **) argv);
+ else if (first_nonopt == last_nonopt)
+ first_nonopt = woptind;
+ last_nonopt = argc;
+
+ woptind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (woptind == argc)
+ {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (first_nonopt != last_nonopt)
+ woptind = first_nonopt;
+ return EOF;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if ((argv[woptind][0] != '-' || argv[woptind][1] == '\0'))
+ {
+ if (ordering == REQUIRE_ORDER)
+ return EOF;
+ woptarg = argv[woptind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ nextchar = (argv[woptind] + 1
+ + (longopts != NULL && argv[woptind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (argv[woptind][1] == '-'
+ || (long_only && (argv[woptind][2] || !my_index (optstring, argv[woptind][1])))))
+ {
+ wchar_t *nameend;
+ const struct woption *p;
+ const struct woption *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0; /* set to zero by Anton */
+ int option_index;
+
+ for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!wcsncmp(p->name, nextchar, nameend - nextchar))
+ {
+ if ((unsigned int)(nameend - nextchar) == (unsigned int)wcslen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact)
+ {
+ if (wopterr)
+ fwprintf (stderr, L"%ls: option `%ls' is ambiguous\n",
+ argv[0], argv[woptind]);
+ nextchar += wcslen (nextchar);
+ woptind++;
+ return '?';
+ }
+
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ woptind++;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ woptarg = nameend + 1;
+ else
+ {
+ if (wopterr)
+ {
+ if (argv[woptind - 1][1] == '-')
+ /* --option */
+ fwprintf (stderr,
+ L"%ls: option `--%ls' doesn't allow an argument\n",
+ argv[0], pfound->name);
+ else
+ /* +option or -option */
+ fwprintf (stderr,
+ L"%ls: option `%lc%ls' doesn't allow an argument\n",
+ argv[0], argv[woptind - 1][0], pfound->name);
+ }
+ nextchar += wcslen (nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (woptind < argc)
+ woptarg = argv[woptind++];
+ else
+ {
+ if (wopterr)
+ fwprintf (stderr, L"%ls: option `%ls' requires an argument\n",
+ argv[0], argv[woptind - 1]);
+ nextchar += wcslen (nextchar);
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += wcslen (nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || argv[woptind][1] == '-'
+ || my_index (optstring, *nextchar) == NULL)
+ {
+ if (wopterr)
+ {
+ if (argv[woptind][1] == '-')
+ /* --option */
+ fwprintf (stderr, L"%ls: unrecognized option `--%ls'\n",
+ argv[0], nextchar);
+ else
+ /* +option or -option */
+ fwprintf (stderr, L"%ls: unrecognized option `%lc%ls'\n",
+ argv[0], argv[woptind][0], nextchar);
+ }
+ nextchar = (wchar_t *) L"";
+ woptind++;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ wchar_t c = *nextchar++;
+ wchar_t *temp = my_index (optstring, c);
+
+ /* Increment `woptind' when we start to process its last character. */
+ if (*nextchar == '\0')
+ ++woptind;
+
+ if (temp == NULL || c == ':')
+ {
+ if (wopterr)
+ {
+ if (posixly_correct)
+ /* 1003.2 specifies the format of this message. */
+ fwprintf (stderr, L"%ls: illegal option -- %lc\n", argv[0], c);
+ else
+ fwprintf (stderr, L"%ls: invalid option -- %lc\n", argv[0], c);
+ }
+ woptopt = c;
+ return '?';
+ }
+ if (temp[1] == ':')
+ {
+ if (temp[2] == ':')
+ {
+ /* This is an option that accepts an argument optionally. */
+ if (*nextchar != '\0')
+ {
+ woptarg = nextchar;
+ woptind++;
+ }
+ else
+ woptarg = NULL;
+ nextchar = NULL;
+ }
+ else
+ {
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0')
+ {
+ woptarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ woptind++;
+ }
+ else if (woptind == argc)
+ {
+ if (wopterr)
+ {
+ /* 1003.2 specifies the format of this message. */
+ fwprintf (stderr, L"%ls: option requires an argument -- %lc\n",
+ argv[0], c);
+ }
+ woptopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else
+ /* We already incremented `woptind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ woptarg = argv[woptind++];
+ nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+int
+wgetopt (argc, argv, optstring)
+ int argc;
+ wchar_t *const *argv;
+ const wchar_t *optstring;
+{
+ return _wgetopt_internal (argc, argv, optstring,
+ (const struct woption *) 0,
+ (int *) 0,
+ 0);
+}
+
+int
+wgetopt_long (argc, argv, options, long_options, opt_index)
+ int argc;
+ wchar_t *const *argv;
+ const wchar_t *options;
+ const struct woption *long_options;
+ int *opt_index;
+{
+ return _wgetopt_internal (argc, argv, options, long_options, opt_index, 0);
+}
+
+int
+wgetopt_long_only (argc, argv, options, long_options, opt_index)
+ int argc;
+ wchar_t *const *argv;
+ const wchar_t *options;
+ const struct woption *long_options;
+ int *opt_index;
+{
+ return _wgetopt_internal (argc, argv, options, long_options, opt_index, 1);
+}
diff --git a/wgetopt.h b/wgetopt.h
new file mode 100644
index 00000000..9421c64e
--- /dev/null
+++ b/wgetopt.h
@@ -0,0 +1,145 @@
+/** \file wgetopt.h
+ The getopt librar for wide character strings.
+
+ This is simply the gnu getopt library, but converted for use with wchar_t instead of char. This is not usually useful since the argv array is always defined to be of type char**, but in fish, all internal commands use wide characters and hence this library is usefull.
+*/
+
+
+/* Declarations for getopt.
+ Copyright (C) 1989, 90, 91, 92, 93, 94 Free Software Foundation, Inc.
+
+This file is part of the GNU C Library. Its master source is NOT part of
+the C library, however. The master source lives in /gd/gnu/lib.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB. If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA. */
+
+#ifndef _WGETOPT_H
+#define _WGETOPT_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+extern wchar_t *woptarg;
+
+/** Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns EOF, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+extern int woptind;
+
+/** Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+extern int wopterr;
+
+/** Set to an option character which was unrecognized. */
+
+extern int woptopt;
+
+/** Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of `struct option' terminated by an element containing a name which is
+ zero.
+
+ The field `has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field `flag' is not NULL, it points to a variable that is set
+ to the value given in the field `val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an `int' to
+ a compiled-in constant, such as set a value from `optarg', set the
+ option's `flag' field to zero and its `val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero `flag' field, `getopt'
+ returns the contents of the `val' field. */
+
+struct woption
+{
+#if defined (__STDC__) && __STDC__
+ const wchar_t *name;
+#else
+ wchar_t *name;
+#endif
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'. */
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+#if defined (__STDC__) && __STDC__
+#ifdef __GNU_LIBRARY__
+/* Get options from argument list */
+extern int wgetopt (int argc, wchar_t *const *argv, const wchar_t *shortopts);
+#else /* not __GNU_LIBRARY__ */
+/* Get options from argument list */
+extern int wgetopt ();
+#endif /* __GNU_LIBRARY__ */
+/* Get options from argument list */
+extern int wgetopt_long (int argc, wchar_t *const *argv, const wchar_t *shortopts,
+ const struct woption *longopts, int *longind);
+/* Get options from argument list */
+extern int wgetopt_long_only (int argc, wchar_t *const *argv,
+ const wchar_t *shortopts,
+ const struct woption *longopts, int *longind);
+
+/** Internal only. Users should not call this directly. */
+extern int _wgetopt_internal (int argc, wchar_t *const *argv,
+ const wchar_t *shortopts,
+ const struct woption *longopts, int *longind,
+ int long_only);
+#else /* not __STDC__ */
+/* Get options from argument list */
+extern int wgetopt ();
+/* Get options from argument list */
+extern int wgetopt_long ();
+/* Get options from argument list */
+extern int wgetopt_long_only ();
+
+/* Get options from argument list */
+extern int _wgetopt_internal ();
+#endif /* __STDC__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WGETOPT_H */
diff --git a/wildcard.c b/wildcard.c
new file mode 100644
index 00000000..bc030771
--- /dev/null
+++ b/wildcard.c
@@ -0,0 +1,581 @@
+/** \file wildcard.c
+ My own globbing implementation. Needed to implement this to
+ support tab-expansion of globbed parameters.
+
+*/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <wchar.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include "util.h"
+#include "wutil.h"
+#include "complete.h"
+#include "common.h"
+#include "wildcard.h"
+#include "complete.h"
+#include "reader.h"
+#include "expand.h"
+
+int wildcard_has( const wchar_t *str, int internal )
+{
+ wchar_t prev=0;
+ if( internal )
+ {
+ for( ; *str; str++ )
+ {
+ if( ( *str == ANY_CHAR ) || (*str == ANY_STRING) || (*str == ANY_STRING_RECURSIVE) )
+ return 1;
+ prev = *str;
+ }
+ }
+ else
+ {
+ for( ; *str; str++ )
+ {
+ if( ( (*str == L'*' ) || (*str == L'?' ) ) && (prev != L'\\') )
+ return 1;
+ prev = *str;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ Check whether the string str matches the wildcard string wc.
+
+ \param str String to be matched.
+ \param wc The wildcard.
+ \param is_first Whether files beginning with dots should not be matched against wildcards.
+ \param wc_unescaped Whether the unescaped special character ANY_CHAR abd ANY_STRING should be used instead of '?' and '*' for wildcard matching
+*/
+
+
+static int wildcard_match2( const wchar_t *str,
+ const wchar_t *wc,
+ int is_first )
+{
+
+ if( *str == 0 && *wc==0 )
+ return 1;
+
+ if( *wc == ANY_STRING || *wc == ANY_STRING_RECURSIVE)
+ {
+ /* Ignore hidden file */
+ if( is_first && *str == L'.' )
+ return 0;
+
+ /* Try all submatches */
+ do
+ {
+ if( wildcard_match2( str, wc+1, 0 ) )
+ return 1;
+ }
+ while( *(str++) != 0 );
+ return 0;
+ }
+ if( *wc == ANY_CHAR )
+ return wildcard_match2( str+1, wc+1, 0 );
+
+ if( *wc == *str )
+ return wildcard_match2( str+1, wc+1, 0 );
+
+ return 0;
+}
+
+/**
+ Matches the string against the wildcard, and if the wildcard is a
+ possible completion of the string, the remainder of the string is
+ inserted into the array_list_t.
+*/
+static int wildcard_complete_internal( const wchar_t *orig,
+ const wchar_t *str,
+ const wchar_t *wc,
+ int is_first,
+ const wchar_t *desc,
+ const wchar_t *(*desc_func)(const wchar_t *),
+ array_list_t *out )
+{
+ if( *wc == 0 &&
+ ( ( *str != L'.') || (!is_first)) )
+ {
+ if( !out )
+ return 1;
+
+ wchar_t *new;
+
+ if( wcschr( str, PROG_COMPLETE_SEP ) )
+ {
+ /*
+ This completion has an embedded description, du not use the generic description
+ */
+ wchar_t *sep;
+
+ new = wcsdup( str );
+ sep = wcschr(new, PROG_COMPLETE_SEP );
+ *sep = COMPLETE_SEP;
+ }
+ else if( desc_func )
+ {
+ /*
+ A descripton generating function is specified, use it
+ */
+ new = wcsdupcat2( str, COMPLETE_SEP_STR, desc_func( orig ), 0);
+ }
+ else
+ {
+ /*
+ Append generic description to item, if the description exists
+ */
+ if( desc && wcslen(desc)>1 )
+ new = wcsdupcat( str, desc );
+ else
+ new = wcsdup( str );
+ }
+
+ if( new )
+ {
+ al_push( out, new );
+ }
+ return 1;
+ }
+
+
+ if( *wc == ANY_STRING )
+ {
+ int res=0;
+
+ /* Ignore hidden file */
+ if( is_first && str[0] == L'.' )
+ return 0;
+
+ /* Try all submatches */
+ do
+ {
+ res |= wildcard_complete_internal( orig, str, wc+1, 0, desc, desc_func, out );
+ if( res && !out )
+ break;
+ }
+ while( *str++ != 0 );
+ return res;
+
+ }
+ else if( *wc == ANY_CHAR )
+ {
+ return wildcard_complete_internal( orig, str+1, wc+1, 0, desc, desc_func, out );
+ }
+ else if( *wc == *str )
+ {
+ return wildcard_complete_internal( orig, str+1, wc+1, 0, desc, desc_func, out );
+ }
+ return 0;
+}
+
+int wildcard_complete( const wchar_t *str,
+ const wchar_t *wc,
+ const wchar_t *desc,
+ const wchar_t *(*desc_func)(const wchar_t *),
+ array_list_t *out )
+{
+ return wildcard_complete_internal( str, str, wc, 1, desc, desc_func, out );
+}
+
+
+int wildcard_match( const wchar_t *str, const wchar_t *wc )
+{
+ return wildcard_match2( str, wc, 1 );
+}
+
+/**
+ Creates a path from the specified directory and filename.
+*/
+static wchar_t *make_path( const wchar_t *base_dir, const wchar_t *name )
+{
+
+ wchar_t *long_name;
+ int base_len = wcslen( base_dir );
+ if( !(long_name= malloc( sizeof(wchar_t)*(base_len+wcslen(name)+1) )))
+ {
+
+ return 0;
+ }
+ wcscpy( long_name, base_dir );
+ wcscpy(&long_name[base_len], name );
+ return long_name;
+}
+
+
+void get_desc( wchar_t *fn, string_buffer_t *sb, int is_cmd )
+{
+ const wchar_t *desc;
+
+ struct stat buf;
+ off_t sz;
+ wchar_t *sz_name[]=
+ {
+ L"kB", L"MB", L"GB", L"TB", L"PB", L"EB", L"ZB", L"YB", 0
+ }
+ ;
+
+
+ sb_clear( sb );
+
+ if( wstat( fn, &buf ) )
+ {
+ sz=-1;
+ }
+ else
+ {
+ sz = buf.st_size;
+ }
+
+ desc = complete_get_desc( fn );
+
+ if( sz >= 0 && S_ISDIR(buf.st_mode) )
+ {
+ sb_append2( sb, desc, 0 );
+ }
+ else
+ {
+ sb_append2( sb, desc, L", ", 0 );
+ if( sz < 0 )
+ {
+ sb_append( sb, L"unknown" );
+ }
+ else if( sz < 1 )
+ {
+ sb_append( sb, L"empty" );
+ }
+ else if( sz < 1024 )
+ {
+ sb_printf( sb, L"%dB", sz );
+ }
+ else
+ {
+ int i;
+
+ for( i=0; sz_name[i]; i++ )
+ {
+ if( sz < (1024*1024) || !sz_name[i+1] )
+ {
+ int isz = sz/1024;
+ if( isz > 9 )
+ sb_printf( sb, L"%d%ls", isz, sz_name[i] );
+ else
+ sb_printf( sb, L"%.1f%ls", (double)sz/1024, sz_name[i] );
+
+ break;
+ }
+ sz /= 1024;
+
+ }
+ }
+ }
+}
+
+static int test_flags( wchar_t *filename,
+ int flags )
+{
+ if( !(flags & EXECUTABLES_ONLY) && !(flags & DIRECTORIES_ONLY) )
+ return 1;
+
+ struct stat buf;
+ wstat( filename, &buf );
+
+ if( S_IFDIR & buf.st_mode )
+ return 1;
+
+ if( flags & EXECUTABLES_ONLY )
+ return ( waccess( filename, X_OK ) == 0);
+
+ return 0;
+}
+
+
+int wildcard_expand( const wchar_t *wc,
+ const wchar_t *base_dir,
+ int flags,
+ array_list_t *out )
+{
+// debug( 3, L"WILDCARD_EXPAND %ls in %ls", wc, base_dir );
+
+ if( flags & ACCEPT_INCOMPLETE )
+ {
+ /* Avoid excessive number of returned matches for wc ending with a * */
+ int len = wcslen(wc);
+ if( len && (wc[len-1]==ANY_STRING) )
+ {
+ wchar_t * foo = wcsdup( wc );
+ foo[len-1]=0;
+ int res = wildcard_expand( foo, base_dir, flags, out );
+ free( foo );
+ return res;
+ }
+ }
+
+ struct dirent *next;
+ wchar_t *wc_end = wcschr(wc,L'/');
+ DIR *dir;
+
+ int res = 0;
+ int base_len = wcslen( base_dir );
+
+ wchar_t *wc_recursive = wcschr( wc, ANY_STRING_RECURSIVE );
+ int is_recursive = is_recursive = ( wc_recursive && (!wc_end || wc_recursive < wc_end));
+
+ const wchar_t *dir_string = base_dir[0]==L'\0'?L".":base_dir;
+
+ string_buffer_t sb_desc;
+
+ sb_init( &sb_desc );
+// if( accept_incomplete )
+// wprintf( L"Glob %ls in '%ls'\n", wc, base_dir );//[0]==L'\0'?L".":base_dir );
+
+/*
+ Test for recursive match string in current segment
+*/
+
+ if( !(dir = wopendir( dir_string )))
+ {
+// if( errno != EACCES && errno != ENOENT )
+// wperror( L"opendir" );
+ return 0;
+ }
+
+/*
+ Is this segment of the wildcard the last?
+*/
+ if( wc_end == 0 )
+ {
+ /*
+ Wildcard segment is the last segment
+
+ Insert all matching files/directories
+ */
+ if( wc[0]=='\0' )
+ {
+ /*
+ The last wildcard segment is empty. Insert everything if completing, the directory itself otherwise.
+ */
+ if( flags & ACCEPT_INCOMPLETE )
+ {
+ while( (next=readdir(dir))!=0 )
+ {
+ if( next->d_name[0] != '.' )
+ {
+ wchar_t *name = str2wcs(next->d_name);
+ if( name == 0 )
+ {
+/* closedir( dir );*/
+/* return -1; */
+ continue;
+ }
+ wchar_t *long_name = make_path( base_dir, name );
+
+ if( long_name == 0 )
+ {
+ wperror( L"malloc" );
+ closedir( dir );
+ free(name);
+ return 0;
+ }
+
+ if( test_flags( long_name, flags ) )
+ {
+ get_desc( long_name,
+ &sb_desc,
+ flags & EXECUTABLES_ONLY );
+ al_push( out,
+ wcsdupcat(name, (wchar_t *)sb_desc.buff) );
+ }
+
+ free(name);
+
+ free( long_name );
+ }
+ }
+ }
+ else
+ {
+ res = 1;
+ al_push( out, wcsdup( base_dir ) );
+ }
+ }
+ else
+ {
+ /*
+ This is the last wildcard segment, and it is not empty. Match files/directories.
+ */
+ while( (next=readdir(dir))!=0 )
+ {
+ wchar_t *name = str2wcs(next->d_name);
+ if( name == 0 )
+ {
+ continue;
+ }
+
+/* wprintf( L"Filen heter %s\n\n\n", next->d_name );*/
+/* wprintf( L"Match %ls (%s) against %ls\n\n\n", name, "tjo", wc );*/
+ if( flags & ACCEPT_INCOMPLETE )
+ {
+ /* wprintf( L"match %ls to %ls\n", name, wc );*/
+
+ wchar_t *long_name = make_path( base_dir, name );
+ if( long_name == 0 )
+ {
+ wperror( L"malloc" );
+ closedir( dir );
+ free(name);
+ return 0;
+ }
+ /*
+ Test for matches before stating file, so as to minimize the number of stat calls
+ */
+ if( wildcard_complete( name,
+ wc,
+ L"",
+ 0,
+ 0 ) )
+ {
+
+
+ if( test_flags( long_name, flags ) )
+ {
+ get_desc( long_name,
+ &sb_desc,
+ flags & EXECUTABLES_ONLY );
+
+ wildcard_complete( name,
+ wc,
+ (wchar_t *)sb_desc.buff,
+ 0,
+ out );
+ }
+ }
+
+ free( long_name );
+
+ }
+ else
+ {
+ if( wildcard_match2( name, wc, 1 ) )
+ {
+ wchar_t *long_name = make_path( base_dir, name );
+ if( long_name == 0 )
+ {
+ wperror( L"malloc" );
+ closedir( dir );
+ free(name);
+ return 0;
+ }
+
+ al_push( out, long_name );
+ res = 1;
+ }
+ }
+ free( name );
+ }
+ }
+ }
+ else
+ {
+ /*
+ Wilcard segment is not the last segment.
+ Recursively call wildcard_expand for all matching subdirectories.
+ */
+ wchar_t *wc_str;
+ wchar_t *new_dir;
+ static size_t ln=1024;
+ char * narrow_dir_string = wcs2str( dir_string );
+
+ if( narrow_dir_string )
+ {
+ ln = pathconf( narrow_dir_string, _PC_NAME_MAX ); /* Find out how long the filename can be in a worst case scenario */
+ if( ln < 0 )
+ ln = 1024;
+ free( narrow_dir_string );
+ }
+ new_dir= malloc( sizeof(wchar_t)*(base_len+ln+2) );
+
+ wc_str = wcsndup(wc, wc_end-wc);
+ if( (!new_dir) || (!wc_str) )
+ {
+ if( new_dir )
+ free( new_dir );
+ if( wc_str )
+ free( wc_str );
+ wperror( L"malloc" );
+ closedir( dir );
+ return 0;
+ }
+ wcscpy( new_dir, base_dir );
+
+ while( (next=readdir(dir))!=0 )
+ {
+ wchar_t *name = str2wcs(next->d_name);
+ if( name == 0 )
+ {
+ continue;
+ }
+
+ if( wildcard_match2( name, wc_str, 1 ) )
+ {
+ int new_len;
+ struct stat buf;
+ wcscpy(&new_dir[base_len], name );
+ free(name);
+ char *dir_str = wcs2str( new_dir );
+ int stat_res;
+
+ if( !dir_str )
+ {
+ continue;
+ }
+
+ stat_res= stat( dir_str, &buf );
+ free( dir_str );
+
+ if( stat_res )
+ {
+ continue;
+ }
+
+ if( buf.st_mode & S_IFDIR )
+ {
+ new_len = wcslen( new_dir );
+ new_dir[new_len] = L'/';
+ new_dir[new_len+1] = L'\0';
+ switch( wildcard_expand( wc_end + 1, new_dir, flags, out ) )
+ {
+ case 0:
+ break;
+ case 1:
+ res = 1;
+ break;
+ }
+ }
+ }
+ else
+ {
+ free(name);
+ }
+ }
+ free( wc_str );
+ free( new_dir );
+ }
+ closedir( dir );
+
+ sb_destroy( &sb_desc );
+
+ return res;
+}
+
diff --git a/wildcard.h b/wildcard.h
new file mode 100644
index 00000000..27c74d3f
--- /dev/null
+++ b/wildcard.h
@@ -0,0 +1,80 @@
+
+/** \file wildcard.h
+
+ My own globbing implementation. Needed to implement this instead
+ of using libs globbing to support tab-expantion of globbed
+ paramaters.
+
+*/
+
+/*
+ These constants are outside the 31 bit character space of USC4,
+ thogh they may clash with WEOF. I need to use characters outside of
+ the regular character space to represent wildcards and such,
+ in order to do backslash removal before wildcard matching.
+*/
+
+/** Character representing any character except '/' */
+#define ANY_CHAR 0xfffffffe
+
+/** Character representing any character string not containing '/' (A slash) */
+#define ANY_STRING 0xfffffffd
+
+/** Character representing any character string */
+#define ANY_STRING_RECURSIVE 0xfffffff6
+
+/**
+ Expand the wildcard by matching against the filesystem.
+
+ New strings are allocated using malloc and should be freed by the caller.
+
+ wildcard_expand works by dividing the wildcard into segments at
+ each directory boundary. Each segment is processed separatly. All
+ except the last segment are handled by matching the wildcard
+ segment against all subdirectories of matching directories, and
+ recursively calling wildcard_expand for matches. On the last
+ segment, matching is made to any file, and all matches are
+ inserted to the list.
+
+ If wildcard_expand encounters any errors (such as insufficient
+ priviliges) during matching, no error messages will be printed and
+ wildcard_expand will continue the matching process.
+
+ \param wc The wildcard string
+ \param base_dir The base directory of the filesystem to perform the match against
+ \param status flags for the search. Can be any combination of ACCEPT_INCOMPLETE and EXECUTABLES_ONLY
+ \param out The list in which to put the output
+
+ \return 1 if matches where found, 0 otherwise.
+
+*/
+int wildcard_expand( const wchar_t *wc,
+ const wchar_t *base_dir,
+ int flags,
+ array_list_t *out );
+/**
+ Test whether the given wildcard matches the string
+
+ \param str The string to test
+ \param wc The wildcard to test against
+ \param wc_unescaped if wc_unescaped is true, \c wildcard_match uses the ANY_CHAR and ANY_STRING characters for globbing, otherwise, the '?' and '*' characters are used
+ \return true if the wildcard matched
+*/
+int wildcard_match( const wchar_t *str,
+ const wchar_t *wc );
+
+
+/**
+ Check if the specified string contains wildcards
+*/
+int wildcard_has( const wchar_t *str, int internal );
+
+/**
+ Test wildcard completion
+*/
+int wildcard_complete( const wchar_t *str,
+ const wchar_t *wc,
+ const wchar_t *desc,
+ const wchar_t *(*desc_func)(const wchar_t *),
+ array_list_t *out );
+
diff --git a/wutil.c b/wutil.c
new file mode 100644
index 00000000..6a1a4122
--- /dev/null
+++ b/wutil.c
@@ -0,0 +1,546 @@
+/** \file wutil.c
+ Wide character equivalents of various standard unix functions.
+*/
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <wchar.h>
+#include <string.h>
+#include <dirent.h>
+#include <stdarg.h>
+#include <limits.h>
+
+#include "util.h"
+#include "common.h"
+#include "wutil.h"
+
+static char *tmp=0;
+static size_t tmp_len=0;
+
+int c = 0;
+
+void wutil_destroy()
+{
+ free( tmp );
+ tmp=0;
+ tmp_len=0;
+ debug( 3, L"wutil functions called %d times", c );
+}
+
+static char *wutil_wcs2str( const wchar_t *in )
+{
+ c++;
+
+ size_t new_sz =MAX_UTF8_BYTES*wcslen(in)+1;
+ if( tmp_len < new_sz )
+ {
+ free( tmp );
+ tmp = malloc( new_sz );
+ if( !tmp )
+ {
+ die_mem();
+ }
+ tmp_len = new_sz;
+ }
+
+ wcstombs( tmp, in, tmp_len );
+ return tmp;
+}
+
+wchar_t *wgetcwd( wchar_t *buff, size_t sz )
+{
+ char buffc[sz*MAX_UTF8_BYTES];
+ char *res = getcwd( buffc, sz*MAX_UTF8_BYTES );
+ if( !res )
+ return 0;
+
+ if( (size_t)-1 == mbstowcs( buff, buffc, sizeof( wchar_t ) * sz ) )
+ {
+ return 0;
+ }
+ return buff;
+}
+
+int wchdir( const wchar_t * dir )
+{
+ char *tmp = wutil_wcs2str(dir);
+ return chdir( tmp );
+}
+
+FILE *wfopen(const wchar_t *path, const char *mode)
+{
+
+ char *tmp =wutil_wcs2str(path);
+ FILE *res=0;
+ if( tmp )
+ {
+ res = fopen(tmp, mode);
+
+ }
+ return res;
+}
+
+FILE *wfreopen(const wchar_t *path, const char *mode, FILE *stream)
+{
+ char *tmp =wutil_wcs2str(path);
+ FILE *res=0;
+ if( tmp )
+ {
+ res = freopen(tmp, mode, stream);
+ }
+ return res;
+}
+
+
+
+int wopen(const wchar_t *pathname, int flags, ...)
+{
+ char *tmp =wutil_wcs2str(pathname);
+ int res=-1;
+ va_list argp;
+
+ if( tmp )
+ {
+ va_start( argp, flags );
+
+ if( ! (flags & O_CREAT) )
+ res = open(tmp, flags);
+ else
+ res = open(tmp, flags, va_arg(argp, int) );
+
+ va_end( argp );
+ }
+
+ return res;
+}
+
+int wcreat(const wchar_t *pathname, mode_t mode)
+{
+ char *tmp =wutil_wcs2str(pathname);
+ int res = -1;
+ if( tmp )
+ {
+ res= creat(tmp, mode);
+ }
+
+ return res;
+}
+
+DIR *wopendir(const wchar_t *name)
+{
+ char *tmp =wutil_wcs2str(name);
+ DIR *res = 0;
+ if( tmp )
+ {
+ res = opendir(tmp);
+ }
+
+ return res;
+}
+
+int wstat(const wchar_t *file_name, struct stat *buf)
+{
+ char *tmp =wutil_wcs2str(file_name);
+ int res = -1;
+
+ if( tmp )
+ {
+ res = stat(tmp, buf);
+ }
+
+ return res;
+}
+
+int lwstat(const wchar_t *file_name, struct stat *buf)
+{
+ char *tmp =wutil_wcs2str(file_name);
+ int res = -1;
+
+ if( tmp )
+ {
+ res = lstat(tmp, buf);
+ }
+
+ return res;
+}
+
+
+int waccess(const wchar_t *file_name, int mode)
+{
+ char *tmp =wutil_wcs2str(file_name);
+ int res = -1;
+ if( tmp )
+ {
+ res= access(tmp, mode);
+ }
+ return res;
+}
+
+void wperror(const wchar_t *s)
+{
+ if( s != 0 )
+ {
+ fwprintf( stderr, L"%ls: ", s );
+ }
+ fwprintf( stderr, L"%s\n", strerror( errno ) );
+}
+
+
+#if !HAVE_WPRINTF
+/*
+ Here is my own implementation of *wprintf, included since NetBSD does
+ not provide one of it's own.
+*/
+
+/**
+ This function is defined to help vgwprintf when it wants to call
+ itself recursively
+*/
+static int gwprintf( void (*writer)(wchar_t),
+ const wchar_t *filter,
+ ... );
+
+
+/**
+ Generic formatting function. All other formatting functions are
+ secretly a wrapper around this function.
+*/
+static int vgwprintf( void (*writer)(wchar_t),
+ const wchar_t *filter,
+ va_list va )
+{
+ const wchar_t *filter_org=filter;
+ int count=0;
+
+ for( ;*filter; filter++)
+ {
+ if(*filter == L'%')
+ {
+ int i;
+ int is_long=0;
+ int width = 0;
+ filter++;
+ int loop=1;
+ int precision=INT_MAX;
+
+ while( loop )
+ {
+ switch(*filter)
+ {
+ case L'l':
+ /* Long variable */
+ is_long++;
+ filter++;
+ break;
+ case L'*':
+ /* Set minimum field width */
+ width = va_arg( va, int );
+ filter++;
+ break;
+ case L'.':
+ /*
+ Set precision.
+ Hasn't been tested enough yet, so I don't really trust it.
+ */
+ filter++;
+ if( *filter == L'*' )
+ {
+ precision = va_arg( va, int );
+ }
+ else
+ {
+ while( (*filter >= L'0') && (*filter <= L'9'))
+ {
+ precision=10*precision+(*filter - L'0');
+ }
+ }
+ break;
+
+ default:
+ loop=0;
+ break;
+ }
+ }
+ switch( *filter )
+ {
+ case L'c':
+ {
+ wchar_t c;
+
+ c = is_long?va_arg(va, wchar_t):btowc(va_arg(va, int));
+ if( width )
+ {
+ int i;
+ for( i=1; i<width; i++ )
+ {
+ writer( L' ' );
+ count++;
+ }
+ }
+ if( precision != 0 )
+ writer( c );
+ count++;
+
+ break;
+ }
+ case L's':
+ {
+ wchar_t *ss = is_long?va_arg(va, wchar_t*):str2wcs(va_arg(va, char*));
+
+ if( !ss )
+ return -1;
+
+ if( width )
+ {
+ int i;
+ for( i=wcslen(ss); i<width; i++ )
+ {
+ writer( L' ' );
+ count++;
+ }
+ }
+
+ wchar_t *s=ss;
+ int precount = count;
+
+ while( *s )
+ {
+ if( (precision <= (count-precount) ) )
+ break;
+
+ writer( *(s++) );
+ count++;
+ }
+
+ if( !is_long )
+ free( ss );
+
+ break;
+ }
+
+ case L'd':
+ case L'i':
+ {
+ char str[32];
+
+ switch( is_long )
+ {
+ case 0:
+ {
+ int d = va_arg( va, int );
+ snprintf( str, 32, "%.*d", precision, d );
+ break;
+ }
+
+ case 1:
+ {
+ long d = va_arg( va, long );
+ snprintf( str, 32, "%.*ld", precision, d );
+ break;
+ }
+
+ case 2:
+ {
+ long long d = va_arg( va, long long );
+ snprintf( str, 32, "%.*lld", precision, d );
+ break;
+ }
+
+ default:
+ return -1;
+ }
+
+ if( width )
+ {
+ int i;
+ for( i=strlen(str); i<width; i++ )
+ {
+ writer( L' ' );
+ count++;
+ }
+ }
+
+ int c = gwprintf( writer, L"%s", str );
+ if( c==-1 )
+ return -1;
+ else
+ count += c;
+
+ break;
+ }
+
+ case L'u':
+ {
+ char str[32];
+
+
+ switch( is_long )
+ {
+ case 0:
+ {
+ unsigned d = va_arg( va, unsigned );
+ snprintf( str, 32, "%d", d );
+ break;
+ }
+
+ case 1:
+ {
+ unsigned long d = va_arg( va, unsigned long );
+ snprintf( str, 32, "%ld", d );
+ break;
+ }
+
+ case 2:
+ {
+ unsigned long long d = va_arg( va, unsigned long long );
+ snprintf( str, 32, "%lld", d );
+ break;
+ }
+
+ default:
+ return -1;
+ }
+
+ if( width )
+ {
+ int i;
+ for( i=strlen(str); i<width; i++ )
+ {
+ writer( L' ' );
+ count++;
+ }
+ }
+
+ int c = gwprintf( writer, L"%s", str );
+ if( c==-1 )
+ return -1;
+ else
+ count += c;
+
+ break;
+ }
+
+ case L'n':
+ {
+ int *n = va_arg( va, int *);
+
+ *n = count;
+ break;
+ }
+ default:
+ debug( 0, L"Unknown switch %lc in string %ls\n", *filter, filter_org );
+ exit(1);
+ }
+ }
+ else
+ {
+ writer( *filter );
+ count++;
+ }
+ }
+
+ return count;
+}
+
+static int gwprintf( void (*writer)(wchar_t),
+ const wchar_t *filter,
+ ... )
+{
+ va_list va;
+ va_start( va, filter );
+ int written=vgwprintf( writer,
+ filter,
+ va );
+ va_end( va );
+ return written;
+}
+
+/**
+ Holds data for swprintf writer
+*/
+static struct
+{
+ int count;
+ int max;
+ wchar_t *pos;
+}
+sw_data;
+
+/**
+ Writer for swprintf
+*/
+static void sw_writer( wchar_t c )
+{
+ if( sw_data.count < sw_data.max )
+ *(sw_data.pos++)=c;
+ sw_data.count++;
+}
+
+
+int swprintf( wchar_t *out, size_t n, const wchar_t *filter, ... )
+{
+ va_list va;
+ va_start( va, filter );
+ sw_data.pos=out;
+ sw_data.max=n;
+ sw_data.count=0;
+ int written=vgwprintf( &sw_writer,
+ filter,
+ va );
+ if( written < n )
+ {
+ *sw_data.pos = 0;
+ }
+ else
+ {
+ written=-1;
+ }
+
+ va_end( va );
+ return written;
+}
+
+/**
+ Holds auxiliary data for fwprintf and wprintf writer
+*/
+static FILE *fw_data;
+
+static void fw_writer( wchar_t c )
+{
+ putw( c, fw_data );
+}
+
+/**
+ Writer for fwprintf and wprintf
+*/
+int fwprintf( FILE *f, const wchar_t *filter, ... )
+{
+ va_list va;
+ va_start( va, filter );
+ fw_data = f;
+
+ int written=vgwprintf( &fw_writer, filter, va );
+ va_end( va );
+ return written;
+}
+
+int wprintf( const wchar_t *filter, ... )
+{
+ va_list va;
+ va_start( va, filter );
+ fw_data = stdout;
+
+ int written=vgwprintf( &fw_writer, filter, va );
+ va_end( va );
+ return written;
+}
+
+#endif
+
diff --git a/wutil.h b/wutil.h
new file mode 100644
index 00000000..65b82014
--- /dev/null
+++ b/wutil.h
@@ -0,0 +1,111 @@
+/** \file wutil.h
+
+ Prototypes for wide character equivalents of various standard unix
+ functions.
+
+*/
+#ifndef WUTIL_HH
+#define WUTIL_HH
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+/**
+ Call this function on exit to free internal wutil resources
+*/
+void wutil_destroy();
+
+/**
+ Wide character version of fopen().
+*/
+FILE *wfopen(const wchar_t *path, const char *mode);
+
+/**
+ Wide character version of freopen().
+*/
+FILE *wfreopen(const wchar_t *path, const char *mode, FILE *stream);
+
+/**
+ Wide character version of open().
+*/
+int wopen(const wchar_t *pathname, int flags, ...);
+
+/**
+ Wide character version of creat().
+*/
+int wcreat(const wchar_t *pathname, mode_t mode);
+
+
+/**
+ Wide character version of opendir().
+*/
+DIR *wopendir(const wchar_t *name);
+
+/**
+ Wide character version of stat().
+*/
+int wstat(const wchar_t *file_name, struct stat *buf);
+
+/**
+ Wide character version of lstat().
+*/
+int lwstat(const wchar_t *file_name, struct stat *buf);
+
+/**
+ Wide character version of access().
+*/
+int waccess(const wchar_t *pathname, int mode);
+
+/**
+ Wide character version of perror().
+*/
+void wperror(const wchar_t *s);
+
+/**
+ Wide character version of getcwd().
+*/
+wchar_t *wgetcwd( wchar_t *buff, size_t sz );
+
+/**
+ Wide character version of chdir()
+*/
+int wchdir( const wchar_t * dir );
+
+
+#if !HAVE_WPRINTF
+
+/**
+ Print formated string. Some operating systems (Like NetBSD) do not
+ have wide string formating functions. Therefore we implement our
+ own. Not at all complete. Supports wide and narrow characters,
+ strings and decimal numbers, position (%n), field width and
+ precision.
+*/
+int fwprintf( FILE *f, const wchar_t *format, ... );
+
+
+/**
+ Print formated string. Some operating systems (Like NetBSD) do not
+ have wide string formating functions. Therefore we define our
+ own. Not at all complete. Supports wide and narrow characters,
+ strings and decimal numbers, position (%n), field width and
+ precision.
+*/
+int swprintf( wchar_t *str, size_t l, const wchar_t *format, ... );
+
+/**
+ Print formated string. Some operating systems (Like NetBSD) do not
+ have wide string formating functions. Therefore we define our
+ own. Not at all complete. Supports wide and narrow characters,
+ strings and decimal numbers, position (%n), field width and
+ precision.
+*/
+int wprintf( const wchar_t *format, ... );
+
+
+#endif
+
+#endif
diff --git a/xdgmime.c b/xdgmime.c
new file mode 100644
index 00000000..e4934a97
--- /dev/null
+++ b/xdgmime.c
@@ -0,0 +1,715 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmime.c: XDG Mime Spec mime resolver. Based on version 0.11 of the spec.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2003,2004 Red Hat, Inc.
+ * Copyright (C) 2003,2004 Jonathan Blandford <jrb@alum.mit.edu>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xdgmime.h"
+#include "xdgmimeint.h"
+#include "xdgmimeglob.h"
+#include "xdgmimemagic.h"
+#include "xdgmimealias.h"
+#include "xdgmimeparent.h"
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <assert.h>
+
+typedef struct XdgDirTimeList XdgDirTimeList;
+typedef struct XdgCallbackList XdgCallbackList;
+
+static int need_reread = TRUE;
+static time_t last_stat_time = 0;
+
+static XdgGlobHash *global_hash = NULL;
+static XdgMimeMagic *global_magic = NULL;
+static XdgAliasList *alias_list = NULL;
+static XdgParentList *parent_list = NULL;
+static XdgDirTimeList *dir_time_list = NULL;
+static XdgCallbackList *callback_list = NULL;
+const char *xdg_mime_type_unknown = "application/octet-stream";
+
+
+enum
+ {
+ XDG_CHECKED_UNCHECKED,
+ XDG_CHECKED_VALID,
+ XDG_CHECKED_INVALID
+ };
+
+struct XdgDirTimeList
+{
+ time_t mtime;
+ char *directory_name;
+ int checked;
+ XdgDirTimeList *next;
+};
+
+struct XdgCallbackList
+{
+ XdgCallbackList *next;
+ XdgCallbackList *prev;
+ int callback_id;
+ XdgMimeCallback callback;
+ void *data;
+ XdgMimeDestroy destroy;
+};
+
+/* Function called by xdg_run_command_on_dirs. If it returns TRUE, further
+ * directories aren't looked at */
+typedef int (*XdgDirectoryFunc) (const char *directory,
+ void *user_data);
+
+static XdgDirTimeList *
+xdg_dir_time_list_new (void)
+{
+ XdgDirTimeList *retval;
+
+ retval = calloc (1, sizeof (XdgDirTimeList));
+ retval->checked = XDG_CHECKED_UNCHECKED;
+
+ return retval;
+}
+
+static void
+xdg_dir_time_list_free (XdgDirTimeList *list)
+{
+ XdgDirTimeList *next;
+
+ while (list)
+ {
+ next = list->next;
+ free (list->directory_name);
+ free (list);
+ list = next;
+ }
+}
+
+static int
+xdg_mime_init_from_directory (const char *directory)
+{
+ char *file_name;
+ struct stat st;
+ XdgDirTimeList *list;
+
+ assert (directory != NULL);
+
+ file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1);
+ strcpy (file_name, directory); strcat (file_name, "/mime/globs");
+ if (stat (file_name, &st) == 0)
+ {
+ _xdg_mime_glob_read_from_file (global_hash, file_name);
+
+ list = xdg_dir_time_list_new ();
+ list->directory_name = file_name;
+ list->mtime = st.st_mtime;
+ list->next = dir_time_list;
+ dir_time_list = list;
+ }
+ else
+ {
+ free (file_name);
+ }
+
+ file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1);
+ strcpy (file_name, directory); strcat (file_name, "/mime/magic");
+ if (stat (file_name, &st) == 0)
+ {
+ _xdg_mime_magic_read_from_file (global_magic, file_name);
+
+ list = xdg_dir_time_list_new ();
+ list->directory_name = file_name;
+ list->mtime = st.st_mtime;
+ list->next = dir_time_list;
+ dir_time_list = list;
+ }
+ else
+ {
+ free (file_name);
+ }
+
+ file_name = malloc (strlen (directory) + strlen ("/mime/aliases") + 1);
+ strcpy (file_name, directory); strcat (file_name, "/mime/aliases");
+ _xdg_mime_alias_read_from_file (alias_list, file_name);
+ free (file_name);
+
+ file_name = malloc (strlen (directory) + strlen ("/mime/subclasses") + 1);
+ strcpy (file_name, directory); strcat (file_name, "/mime/subclasses");
+ _xdg_mime_parent_read_from_file (parent_list, file_name);
+ free (file_name);
+
+ return FALSE; /* Keep processing */
+}
+
+/* Runs a command on all the directories in the search path */
+static void
+xdg_run_command_on_dirs (XdgDirectoryFunc func,
+ void *user_data)
+{
+ const char *xdg_data_home;
+ const char *xdg_data_dirs;
+ const char *ptr;
+
+ xdg_data_home = getenv ("XDG_DATA_HOME");
+ if (xdg_data_home)
+ {
+ if ((func) (xdg_data_home, user_data))
+ return;
+ }
+ else
+ {
+ const char *home;
+
+ home = getenv ("HOME");
+ if (home != NULL)
+ {
+ char *guessed_xdg_home;
+ int stop_processing;
+
+ guessed_xdg_home = malloc (strlen (home) + strlen ("/.local/share/") + 1);
+ strcpy (guessed_xdg_home, home);
+ strcat (guessed_xdg_home, "/.local/share/");
+ stop_processing = (func) (guessed_xdg_home, user_data);
+ free (guessed_xdg_home);
+
+ if (stop_processing)
+ return;
+ }
+ }
+
+ xdg_data_dirs = getenv ("XDG_DATA_DIRS");
+ if (xdg_data_dirs == NULL)
+ xdg_data_dirs = "/usr/local/share/:/usr/share/";
+
+ ptr = xdg_data_dirs;
+
+ while (*ptr != '\000')
+ {
+ const char *end_ptr;
+ char *dir;
+ int len;
+ int stop_processing;
+
+ end_ptr = ptr;
+ while (*end_ptr != ':' && *end_ptr != '\000')
+ end_ptr ++;
+
+ if (end_ptr == ptr)
+ {
+ ptr++;
+ continue;
+ }
+
+ if (*end_ptr == ':')
+ len = end_ptr - ptr;
+ else
+ len = end_ptr - ptr + 1;
+ dir = malloc (len + 1);
+ strncpy (dir, ptr, len);
+ dir[len] = '\0';
+ stop_processing = (func) (dir, user_data);
+ free (dir);
+
+ if (stop_processing)
+ return;
+
+ ptr = end_ptr;
+ }
+}
+
+/* Checks file_path to make sure it has the same mtime as last time it was
+ * checked. If it has a different mtime, or if the file doesn't exist, it
+ * returns FALSE.
+ *
+ * FIXME: This doesn't protect against permission changes.
+ */
+static int
+xdg_check_file (const char *file_path)
+{
+ struct stat st;
+
+ /* If the file exists */
+ if (stat (file_path, &st) == 0)
+ {
+ XdgDirTimeList *list;
+
+ for (list = dir_time_list; list; list = list->next)
+ {
+ if (! strcmp (list->directory_name, file_path) &&
+ st.st_mtime == list->mtime)
+ {
+ if (list->checked == XDG_CHECKED_UNCHECKED)
+ list->checked = XDG_CHECKED_VALID;
+ else if (list->checked == XDG_CHECKED_VALID)
+ list->checked = XDG_CHECKED_INVALID;
+
+ return (list->checked != XDG_CHECKED_VALID);
+ }
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int
+xdg_check_dir (const char *directory,
+ int *invalid_dir_list)
+{
+ int invalid;
+ char *file_name;
+
+ assert (directory != NULL);
+
+ /* Check the globs file */
+ file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1);
+ strcpy (file_name, directory); strcat (file_name, "/mime/globs");
+ invalid = xdg_check_file (file_name);
+ free (file_name);
+ if (invalid)
+ {
+ *invalid_dir_list = TRUE;
+ return TRUE;
+ }
+
+ /* Check the magic file */
+ file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1);
+ strcpy (file_name, directory); strcat (file_name, "/mime/magic");
+ invalid = xdg_check_file (file_name);
+ free (file_name);
+ if (invalid)
+ {
+ *invalid_dir_list = TRUE;
+ return TRUE;
+ }
+
+ return FALSE; /* Keep processing */
+}
+
+/* Walks through all the mime files stat()ing them to see if they've changed.
+ * Returns TRUE if they have. */
+static int
+xdg_check_dirs (void)
+{
+ XdgDirTimeList *list;
+ int invalid_dir_list = FALSE;
+
+ for (list = dir_time_list; list; list = list->next)
+ list->checked = XDG_CHECKED_UNCHECKED;
+
+ xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_check_dir,
+ &invalid_dir_list);
+
+ if (invalid_dir_list)
+ return TRUE;
+
+ for (list = dir_time_list; list; list = list->next)
+ {
+ if (list->checked != XDG_CHECKED_VALID)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* We want to avoid stat()ing on every single mime call, so we only look for
+ * newer files every 5 seconds. This will return TRUE if we need to reread the
+ * mime data from disk.
+ */
+static int
+xdg_check_time_and_dirs (void)
+{
+ struct timeval tv;
+ time_t current_time;
+ int retval = FALSE;
+
+ gettimeofday (&tv, NULL);
+ current_time = tv.tv_sec;
+
+ if (current_time >= last_stat_time + 5)
+ {
+ retval = xdg_check_dirs ();
+ last_stat_time = current_time;
+ }
+
+ return retval;
+}
+
+/* Called in every public function. It reloads the hash function if need be.
+ */
+static void
+xdg_mime_init (void)
+{
+ if (xdg_check_time_and_dirs ())
+ {
+ xdg_mime_shutdown ();
+ }
+
+ if (need_reread)
+ {
+ global_hash = _xdg_glob_hash_new ();
+ global_magic = _xdg_mime_magic_new ();
+ alias_list = _xdg_mime_alias_list_new ();
+ parent_list = _xdg_mime_parent_list_new ();
+
+ xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_mime_init_from_directory,
+ NULL);
+
+ need_reread = FALSE;
+ }
+}
+
+const char *
+xdg_mime_get_mime_type_for_data (const void *data,
+ size_t len)
+{
+ const char *mime_type;
+
+ xdg_mime_init ();
+
+ mime_type = _xdg_mime_magic_lookup_data (global_magic, data, len);
+
+ if (mime_type)
+ return mime_type;
+
+ return XDG_MIME_TYPE_UNKNOWN;
+}
+
+const char *
+xdg_mime_get_mime_type_for_file (const char *file_name)
+{
+ const char *mime_type;
+ FILE *file;
+ unsigned char *data;
+ int max_extent;
+ int bytes_read;
+ struct stat statbuf;
+ const char *base_name;
+
+ if (file_name == NULL)
+ return NULL;
+ if (! _xdg_utf8_validate (file_name))
+ return NULL;
+
+ xdg_mime_init ();
+
+ base_name = _xdg_get_base_name (file_name);
+ mime_type = xdg_mime_get_mime_type_from_file_name (base_name);
+
+ if (mime_type != XDG_MIME_TYPE_UNKNOWN)
+ return mime_type;
+
+ if (stat (file_name, &statbuf) != 0)
+ return XDG_MIME_TYPE_UNKNOWN;
+
+ if (!S_ISREG (statbuf.st_mode))
+ return XDG_MIME_TYPE_UNKNOWN;
+
+ /* FIXME: Need to make sure that max_extent isn't totally broken. This could
+ * be large and need getting from a stream instead of just reading it all
+ * in. */
+ max_extent = _xdg_mime_magic_get_buffer_extents (global_magic);
+ data = malloc (max_extent);
+ if (data == NULL)
+ return XDG_MIME_TYPE_UNKNOWN;
+
+ file = fopen (file_name, "r");
+ if (file == NULL)
+ {
+ free (data);
+ return XDG_MIME_TYPE_UNKNOWN;
+ }
+
+ bytes_read = fread (data, 1, max_extent, file);
+ if (ferror (file))
+ {
+ free (data);
+ fclose (file);
+ return XDG_MIME_TYPE_UNKNOWN;
+ }
+
+ mime_type = _xdg_mime_magic_lookup_data (global_magic, data, bytes_read);
+
+ free (data);
+ fclose (file);
+
+ if (mime_type)
+ return mime_type;
+
+ return XDG_MIME_TYPE_UNKNOWN;
+}
+
+const char *
+xdg_mime_get_mime_type_from_file_name (const char *file_name)
+{
+ const char *mime_type;
+
+ xdg_mime_init ();
+
+ mime_type = _xdg_glob_hash_lookup_file_name (global_hash, file_name);
+ if (mime_type)
+ return mime_type;
+ else
+ return XDG_MIME_TYPE_UNKNOWN;
+}
+
+int
+xdg_mime_is_valid_mime_type (const char *mime_type)
+{
+ /* FIXME: We should make this a better test
+ */
+ return _xdg_utf8_validate (mime_type);
+}
+
+void
+xdg_mime_shutdown (void)
+{
+ XdgCallbackList *list;
+
+ /* FIXME: Need to make this (and the whole library) thread safe */
+ if (dir_time_list)
+ {
+ xdg_dir_time_list_free (dir_time_list);
+ dir_time_list = NULL;
+ }
+
+ if (global_hash)
+ {
+ _xdg_glob_hash_free (global_hash);
+ global_hash = NULL;
+ }
+ if (global_magic)
+ {
+ _xdg_mime_magic_free (global_magic);
+ global_magic = NULL;
+ }
+
+ if (alias_list)
+ {
+ _xdg_mime_alias_list_free (alias_list);
+ alias_list = NULL;
+ }
+
+ if( parent_list )
+ {
+ _xdg_mime_parent_list_free ( parent_list);
+ }
+
+
+ for (list = callback_list; list; list = list->next)
+ (list->callback) (list->data);
+
+ need_reread = TRUE;
+}
+
+int
+xdg_mime_get_max_buffer_extents (void)
+{
+ xdg_mime_init ();
+
+ return _xdg_mime_magic_get_buffer_extents (global_magic);
+}
+
+const char *
+xdg_mime_unalias_mime_type (const char *mime_type)
+{
+ const char *lookup;
+
+ xdg_mime_init ();
+
+ if ((lookup = _xdg_mime_alias_list_lookup (alias_list, mime_type)) != NULL)
+ return lookup;
+
+ return mime_type;
+}
+
+int
+xdg_mime_mime_type_equal (const char *mime_a,
+ const char *mime_b)
+{
+ const char *unalias_a, *unalias_b;
+
+ xdg_mime_init ();
+
+ unalias_a = xdg_mime_unalias_mime_type (mime_a);
+ unalias_b = xdg_mime_unalias_mime_type (mime_b);
+
+ if (strcmp (unalias_a, unalias_b) == 0)
+ return 1;
+
+ return 0;
+}
+
+int
+xdg_mime_media_type_equal (const char *mime_a,
+ const char *mime_b)
+{
+ char *sep;
+
+ xdg_mime_init ();
+
+ sep = strchr (mime_a, '/');
+
+ if (sep && strncmp (mime_a, mime_b, sep - mime_a + 1) == 0)
+ return 1;
+
+ return 0;
+}
+
+#if 0
+static int
+xdg_mime_is_super_type (const char *mime)
+{
+ int length;
+ const char *type;
+
+ length = strlen (mime);
+ type = &(mime[length - 2]);
+
+ if (strcmp (type, "/*") == 0)
+ return 1;
+
+ return 0;
+}
+#endif
+
+int
+xdg_mime_mime_type_subclass (const char *mime,
+ const char *base)
+{
+ const char *umime, *ubase;
+ const char **parents;
+
+ xdg_mime_init ();
+
+ umime = xdg_mime_unalias_mime_type (mime);
+ ubase = xdg_mime_unalias_mime_type (base);
+
+ if (strcmp (umime, ubase) == 0)
+ return 1;
+
+#if 0
+ /* Handle supertypes */
+ if (xdg_mime_is_super_type (ubase) &&
+ xdg_mime_media_type_equal (umime, ubase))
+ return 1;
+#endif
+
+ /* Handle special cases text/plain and application/octet-stream */
+ if (strcmp (ubase, "text/plain") == 0 &&
+ strncmp (umime, "text/", 5) == 0)
+ return 1;
+
+ if (strcmp (ubase, "application/octet-stream") == 0)
+ return 1;
+
+ parents = _xdg_mime_parent_list_lookup (parent_list, umime);
+ for (; parents && *parents; parents++)
+ {
+ if (xdg_mime_mime_type_subclass (*parents, ubase))
+ return 1;
+ }
+
+ return 0;
+}
+
+const char **
+xdg_mime_get_mime_parents (const char *mime)
+{
+ const char *umime;
+
+ xdg_mime_init ();
+
+ umime = xdg_mime_unalias_mime_type (mime);
+
+ return _xdg_mime_parent_list_lookup (parent_list, umime);
+}
+
+void
+xdg_mime_dump (void)
+{
+ printf ("*** ALIASES ***\n\n");
+ _xdg_mime_alias_list_dump (alias_list);
+ printf ("\n*** PARENTS ***\n\n");
+ _xdg_mime_parent_list_dump (parent_list);
+}
+
+
+/* Registers a function to be called every time the mime database reloads its files
+ */
+int
+xdg_mime_register_reload_callback (XdgMimeCallback callback,
+ void *data,
+ XdgMimeDestroy destroy)
+{
+ XdgCallbackList *list_el;
+ static int callback_id = 1;
+
+ /* Make a new list element */
+ list_el = calloc (1, sizeof (XdgCallbackList));
+ list_el->callback_id = callback_id;
+ list_el->callback = callback;
+ list_el->data = data;
+ list_el->destroy = destroy;
+ list_el->next = callback_list;
+ if (list_el->next)
+ list_el->next->prev = list_el;
+
+ callback_list = list_el;
+ callback_id ++;
+
+ return callback_id - 1;
+}
+
+void
+xdg_mime_remove_callback (int callback_id)
+{
+ XdgCallbackList *list;
+
+ for (list = callback_list; list; list = list->next)
+ {
+ if (list->callback_id == callback_id)
+ {
+ if (list->next)
+ list->next = list->prev;
+
+ if (list->prev)
+ list->prev->next = list->next;
+ else
+ callback_list = list->next;
+
+ /* invoke the destroy handler */
+ (list->destroy) (list->data);
+ free (list);
+ return;
+ }
+ }
+}
diff --git a/xdgmime.h b/xdgmime.h
new file mode 100644
index 00000000..fd3647b5
--- /dev/null
+++ b/xdgmime.h
@@ -0,0 +1,93 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmime.h: XDG Mime Spec mime resolver. Based on version 0.11 of the spec.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __XDG_MIME_H__
+#define __XDG_MIME_H__
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef XDG_PREFIX
+#define XDG_ENTRY(func) _XDG_ENTRY2(XDG_PREFIX,func)
+#define _XDG_ENTRY2(prefix,func) _XDG_ENTRY3(prefix,func)
+#define _XDG_ENTRY3(prefix,func) prefix##_##func
+#endif
+
+typedef void (*XdgMimeCallback) (void *user_data);
+typedef void (*XdgMimeDestroy) (void *user_data);
+
+
+#ifdef XDG_PREFIX
+#define xdg_mime_get_mime_type_for_data XDG_ENTRY(get_mime_type_for_data)
+#define xdg_mime_get_mime_type_for_file XDG_ENTRY(get_mime_type_for_file)
+#define xdg_mime_get_mime_type_from_file_name XDG_ENTRY(get_mime_type_from_file_name)
+#define xdg_mime_is_valid_mime_type XDG_ENTRY(is_valid_mime_type)
+#define xdg_mime_mime_type_equal XDG_ENTRY(mime_type_equal)
+#define xdg_mime_media_type_equal XDG_ENTRY(media_type_equal)
+#define xdg_mime_mime_type_subclass XDG_ENTRY(mime_type_subclass)
+#define xdg_mime_get_mime_parents XDG_ENTRY(get_mime_parents)
+#define xdg_mime_unalias_mime_type XDG_ENTRY(unalias_mime_type)
+#define xdg_mime_get_max_buffer_extents XDG_ENTRY(get_max_buffer_extents)
+#define xdg_mime_shutdown XDG_ENTRY(shutdown)
+#define xdg_mime_register_reload_callback XDG_ENTRY(register_reload_callback)
+#define xdg_mime_remove_callback XDG_ENTRY(remove_callback)
+#define xdg_mime_type_unknown XDG_ENTRY(type_unknown)
+#endif
+
+extern const char *xdg_mime_type_unknown;
+#define XDG_MIME_TYPE_UNKNOWN xdg_mime_type_unknown
+
+const char *xdg_mime_get_mime_type_for_data (const void *data,
+ size_t len);
+const char *xdg_mime_get_mime_type_for_file (const char *file_name);
+const char *xdg_mime_get_mime_type_from_file_name (const char *file_name);
+int xdg_mime_is_valid_mime_type (const char *mime_type);
+int xdg_mime_mime_type_equal (const char *mime_a,
+ const char *mime_b);
+int xdg_mime_media_type_equal (const char *mime_a,
+ const char *mime_b);
+int xdg_mime_mime_type_subclass (const char *mime_a,
+ const char *mime_b);
+const char **xdg_mime_get_mime_parents (const char *mime);
+const char *xdg_mime_unalias_mime_type (const char *mime);
+int xdg_mime_get_max_buffer_extents (void);
+void xdg_mime_shutdown (void);
+void xdg_mime_dump (void);
+int xdg_mime_register_reload_callback (XdgMimeCallback callback,
+ void *data,
+ XdgMimeDestroy destroy);
+void xdg_mime_remove_callback (int callback_id);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __XDG_MIME_H__ */
diff --git a/xdgmimealias.c b/xdgmimealias.c
new file mode 100644
index 00000000..2dd70f1d
--- /dev/null
+++ b/xdgmimealias.c
@@ -0,0 +1,184 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmimealias.c: Private file. Datastructure for storing the aliases.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ * Copyright (C) 2004 Matthias Clasen <mclasen@redhat.com>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xdgmimealias.h"
+#include "xdgmimeint.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <fnmatch.h>
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+typedef struct XdgAlias XdgAlias;
+
+struct XdgAlias
+{
+ char *alias;
+ char *mime_type;
+};
+
+struct XdgAliasList
+{
+ struct XdgAlias *aliases;
+ int n_aliases;
+};
+
+XdgAliasList *
+_xdg_mime_alias_list_new (void)
+{
+ XdgAliasList *list;
+
+ list = malloc (sizeof (XdgAliasList));
+
+ list->aliases = NULL;
+ list->n_aliases = 0;
+
+ return list;
+}
+
+void
+_xdg_mime_alias_list_free (XdgAliasList *list)
+{
+ int i;
+
+ if (list->aliases)
+ {
+ for (i = 0; i < list->n_aliases; i++)
+ {
+ free (list->aliases[i].alias);
+ free (list->aliases[i].mime_type);
+ }
+ free (list->aliases);
+ }
+ free (list);
+}
+
+static int
+alias_entry_cmp (const void *v1, const void *v2)
+{
+ return strcmp (((XdgAlias *)v1)->alias, ((XdgAlias *)v2)->alias);
+}
+
+const char *
+_xdg_mime_alias_list_lookup (XdgAliasList *list,
+ const char *alias)
+{
+ XdgAlias *entry;
+ XdgAlias key;
+
+ if (list->n_aliases > 0)
+ {
+ key.alias = (char *)alias;
+ key.mime_type = 0;
+
+ entry = bsearch (&key, list->aliases, list->n_aliases,
+ sizeof (XdgAlias), alias_entry_cmp);
+ if (entry)
+ return entry->mime_type;
+ }
+
+ return NULL;
+}
+
+void
+_xdg_mime_alias_read_from_file (XdgAliasList *list,
+ const char *file_name)
+{
+ FILE *file;
+ char line[255];
+ int alloc;
+
+ file = fopen (file_name, "r");
+
+ if (file == NULL)
+ return;
+
+ /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars.
+ * Blah */
+ alloc = list->n_aliases + 16;
+ list->aliases = realloc (list->aliases, alloc * sizeof (XdgAlias));
+ while (fgets (line, 255, file) != NULL)
+ {
+ char *sep;
+ if (line[0] == '#')
+ continue;
+
+ sep = strchr (line, ' ');
+ if (sep == NULL)
+ continue;
+ *(sep++) = '\000';
+ sep[strlen (sep) -1] = '\000';
+ if (list->n_aliases == alloc)
+ {
+ alloc <<= 1;
+ list->aliases = realloc (list->aliases,
+ alloc * sizeof (XdgAlias));
+ }
+ list->aliases[list->n_aliases].alias = strdup (line);
+ list->aliases[list->n_aliases].mime_type = strdup (sep);
+ list->n_aliases++;
+ }
+ list->aliases = realloc (list->aliases,
+ list->n_aliases * sizeof (XdgAlias));
+
+ fclose (file);
+
+ if (list->n_aliases > 1)
+ qsort (list->aliases, list->n_aliases,
+ sizeof (XdgAlias), alias_entry_cmp);
+}
+
+
+void
+_xdg_mime_alias_list_dump (XdgAliasList *list)
+{
+ int i;
+
+ if (list->aliases)
+ {
+ for (i = 0; i < list->n_aliases; i++)
+ {
+ printf ("%s %s\n",
+ list->aliases[i].alias,
+ list->aliases[i].mime_type);
+ }
+ }
+}
+
+
diff --git a/xdgmimealias.h b/xdgmimealias.h
new file mode 100644
index 00000000..3df18d66
--- /dev/null
+++ b/xdgmimealias.h
@@ -0,0 +1,50 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmimealias.h: Private file. Datastructure for storing the aliases.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ * Copyright (C) 200 Matthias Clasen <mclasen@redhat.com>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __XDG_MIME_ALIAS_H__
+#define __XDG_MIME_ALIAS_H__
+
+#include "xdgmime.h"
+
+typedef struct XdgAliasList XdgAliasList;
+
+#ifdef XDG_PREFIX
+#define _xdg_mime_alias_read_from_file XDG_ENTRY(alias_read_from_file)
+#define _xdg_mime_alias_list_new XDG_ENTRY(alias_list_new)
+#define _xdg_mime_alias_list_free XDG_ENTRY(alias_list_free)
+#define _xdg_mime_alias_list_lookup XDG_ENTRY(alias_list_lookup)
+#endif
+
+void _xdg_mime_alias_read_from_file (XdgAliasList *list,
+ const char *file_name);
+XdgAliasList *_xdg_mime_alias_list_new (void);
+void _xdg_mime_alias_list_free (XdgAliasList *list);
+const char *_xdg_mime_alias_list_lookup (XdgAliasList *list,
+ const char *alias);
+void _xdg_mime_alias_list_dump (XdgAliasList *list);
+
+#endif /* __XDG_MIME_ALIAS_H__ */
diff --git a/xdgmimeglob.c b/xdgmimeglob.c
new file mode 100644
index 00000000..ff3e2774
--- /dev/null
+++ b/xdgmimeglob.c
@@ -0,0 +1,472 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmimeglob.c: Private file. Datastructure for storing the globs.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xdgmimeglob.h"
+#include "xdgmimeint.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <fnmatch.h>
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+typedef struct XdgGlobHashNode XdgGlobHashNode;
+typedef struct XdgGlobList XdgGlobList;
+
+struct XdgGlobHashNode
+{
+ xdg_unichar_t character;
+ const char *mime_type;
+ XdgGlobHashNode *next;
+ XdgGlobHashNode *child;
+};
+struct XdgGlobList
+{
+ const char *data;
+ const char *mime_type;
+ XdgGlobList *next;
+};
+
+struct XdgGlobHash
+{
+ XdgGlobList *literal_list;
+ XdgGlobHashNode *simple_node;
+ XdgGlobList *full_list;
+};
+
+
+/* XdgGlobList
+ */
+static XdgGlobList *
+_xdg_glob_list_new (void)
+{
+ XdgGlobList *new_element;
+
+ new_element = calloc (1, sizeof (XdgGlobList));
+
+ return new_element;
+}
+
+/* Frees glob_list and all of it's children */
+static void
+_xdg_glob_list_free (XdgGlobList *glob_list)
+{
+ XdgGlobList *ptr, *next;
+
+ ptr = glob_list;
+
+ while (ptr != NULL)
+ {
+ next = ptr->next;
+
+ if (ptr->data)
+ free ((void *) ptr->data);
+ if (ptr->mime_type)
+ free ((void *) ptr->mime_type);
+ free (ptr);
+
+ ptr = next;
+ }
+}
+
+static XdgGlobList *
+_xdg_glob_list_append (XdgGlobList *glob_list,
+ void *data,
+ const char *mime_type)
+{
+ XdgGlobList *new_element;
+ XdgGlobList *tmp_element;
+
+ new_element = _xdg_glob_list_new ();
+ new_element->data = data;
+ new_element->mime_type = mime_type;
+ if (glob_list == NULL)
+ return new_element;
+
+ tmp_element = glob_list;
+ while (tmp_element->next != NULL)
+ tmp_element = tmp_element->next;
+
+ tmp_element->next = new_element;
+
+ return glob_list;
+}
+
+#if 0
+static XdgGlobList *
+_xdg_glob_list_prepend (XdgGlobList *glob_list,
+ void *data,
+ const char *mime_type)
+{
+ XdgGlobList *new_element;
+
+ new_element = _xdg_glob_list_new ();
+ new_element->data = data;
+ new_element->next = glob_list;
+ new_element->mime_type = mime_type;
+
+ return new_element;
+}
+#endif
+
+/* XdgGlobHashNode
+ */
+
+static XdgGlobHashNode *
+_xdg_glob_hash_node_new (void)
+{
+ XdgGlobHashNode *glob_hash_node;
+
+ glob_hash_node = calloc (1, sizeof (XdgGlobHashNode));
+
+ return glob_hash_node;
+}
+
+static void
+_xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node,
+ int depth)
+{
+ int i;
+ for (i = 0; i < depth; i++)
+ printf (" ");
+
+ printf ("%c", (char)glob_hash_node->character);
+ if (glob_hash_node->mime_type)
+ printf (" - %s\n", glob_hash_node->mime_type);
+ else
+ printf ("\n");
+ if (glob_hash_node->child)
+ _xdg_glob_hash_node_dump (glob_hash_node->child, depth + 1);
+ if (glob_hash_node->next)
+ _xdg_glob_hash_node_dump (glob_hash_node->next, depth);
+}
+
+static XdgGlobHashNode *
+_xdg_glob_hash_insert_text (XdgGlobHashNode *glob_hash_node,
+ const char *text,
+ const char *mime_type)
+{
+ XdgGlobHashNode *node;
+ xdg_unichar_t character;
+
+ character = _xdg_utf8_to_ucs4 (text);
+
+ if ((glob_hash_node == NULL) ||
+ (character < glob_hash_node->character))
+ {
+ node = _xdg_glob_hash_node_new ();
+ node->character = character;
+ node->next = glob_hash_node;
+ glob_hash_node = node;
+ }
+ else if (character == glob_hash_node->character)
+ {
+ node = glob_hash_node;
+ }
+ else
+ {
+ XdgGlobHashNode *prev_node;
+ int found_node = FALSE;
+
+ /* Look for the first character of text in glob_hash_node, and insert it if we
+ * have to.*/
+ prev_node = glob_hash_node;
+ node = prev_node->next;
+
+ while (node != NULL)
+ {
+ if (character < node->character)
+ {
+ node = _xdg_glob_hash_node_new ();
+ node->character = character;
+ node->next = prev_node->next;
+ prev_node->next = node;
+
+ found_node = TRUE;
+ break;
+ }
+ else if (character == node->character)
+ {
+ found_node = TRUE;
+ break;
+ }
+ prev_node = node;
+ node = node->next;
+ }
+
+ if (! found_node)
+ {
+ node = _xdg_glob_hash_node_new ();
+ node->character = character;
+ node->next = prev_node->next;
+ prev_node->next = node;
+ }
+ }
+
+ text = _xdg_utf8_next_char (text);
+ if (*text == '\000')
+ {
+ node->mime_type = mime_type;
+ }
+ else
+ {
+ node->child = _xdg_glob_hash_insert_text (node->child, text, mime_type);
+ }
+ return glob_hash_node;
+}
+
+static const char *
+_xdg_glob_hash_node_lookup_file_name (XdgGlobHashNode *glob_hash_node,
+ const char *file_name,
+ int ignore_case)
+{
+ XdgGlobHashNode *node;
+ xdg_unichar_t character;
+
+ if (glob_hash_node == NULL)
+ return NULL;
+
+ character = _xdg_utf8_to_ucs4 (file_name);
+ if (ignore_case)
+ character = _xdg_ucs4_to_lower(character);
+
+ for (node = glob_hash_node; node && character >= node->character; node = node->next)
+ {
+ if (character == node->character)
+ {
+ file_name = _xdg_utf8_next_char (file_name);
+ if (*file_name == '\000')
+ return node->mime_type;
+ else
+ return _xdg_glob_hash_node_lookup_file_name (node->child,
+ file_name,
+ ignore_case);
+ }
+ }
+ return NULL;
+}
+
+const char *
+_xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash,
+ const char *file_name)
+{
+ XdgGlobList *list;
+ const char *mime_type;
+ const char *ptr;
+ /* First, check the literals */
+
+ assert (file_name != NULL);
+
+ for (list = glob_hash->literal_list; list; list = list->next)
+ if (strcmp ((const char *)list->data, file_name) == 0)
+ return list->mime_type;
+
+ ptr = strchr (file_name, '.');
+ while (ptr != NULL)
+ {
+ mime_type = (_xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, ptr, FALSE));
+ if (mime_type != NULL)
+ return mime_type;
+
+ mime_type = (_xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, ptr, TRUE));
+ if (mime_type != NULL)
+ return mime_type;
+
+ ptr = strchr (ptr+1, '.');
+ }
+
+ /* FIXME: Not UTF-8 safe */
+ for (list = glob_hash->full_list; list; list = list->next)
+ if (fnmatch ((const char *)list->data, file_name, 0) == 0)
+ return list->mime_type;
+
+ return NULL;
+}
+
+
+
+/* XdgGlobHash
+ */
+
+XdgGlobHash *
+_xdg_glob_hash_new (void)
+{
+ XdgGlobHash *glob_hash;
+
+ glob_hash = calloc (1, sizeof (XdgGlobHash));
+
+ return glob_hash;
+}
+
+
+static void
+_xdg_glob_hash_free_nodes (XdgGlobHashNode *node)
+{
+ if (node)
+ {
+ if (node->child)
+ _xdg_glob_hash_free_nodes (node->child);
+ if (node->next)
+ _xdg_glob_hash_free_nodes (node->next);
+ if (node->mime_type)
+ free ((void *) node->mime_type);
+ free (node);
+ }
+}
+
+void
+_xdg_glob_hash_free (XdgGlobHash *glob_hash)
+{
+ _xdg_glob_list_free (glob_hash->literal_list);
+ _xdg_glob_list_free (glob_hash->full_list);
+ _xdg_glob_hash_free_nodes (glob_hash->simple_node);
+ free (glob_hash);
+}
+
+XdgGlobType
+_xdg_glob_determine_type (const char *glob)
+{
+ const char *ptr;
+ int maybe_in_simple_glob = FALSE;
+ int first_char = TRUE;
+
+ ptr = glob;
+
+ while (*ptr != '\000')
+ {
+ if (*ptr == '*' && first_char)
+ maybe_in_simple_glob = TRUE;
+ else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*')
+ return XDG_GLOB_FULL;
+
+ first_char = FALSE;
+ ptr = _xdg_utf8_next_char (ptr);
+ }
+ if (maybe_in_simple_glob)
+ return XDG_GLOB_SIMPLE;
+ else
+ return XDG_GLOB_LITERAL;
+}
+
+/* glob must be valid UTF-8 */
+void
+_xdg_glob_hash_append_glob (XdgGlobHash *glob_hash,
+ const char *glob,
+ const char *mime_type)
+{
+ XdgGlobType type;
+
+ assert (glob_hash != NULL);
+ assert (glob != NULL);
+
+ type = _xdg_glob_determine_type (glob);
+
+ switch (type)
+ {
+ case XDG_GLOB_LITERAL:
+ glob_hash->literal_list = _xdg_glob_list_append (glob_hash->literal_list, strdup (glob), strdup (mime_type));
+ break;
+ case XDG_GLOB_SIMPLE:
+ glob_hash->simple_node = _xdg_glob_hash_insert_text (glob_hash->simple_node, glob + 1, strdup (mime_type));
+ break;
+ case XDG_GLOB_FULL:
+ glob_hash->full_list = _xdg_glob_list_append (glob_hash->full_list, strdup (glob), strdup (mime_type));
+ break;
+ }
+}
+
+void
+_xdg_glob_hash_dump (XdgGlobHash *glob_hash)
+{
+ XdgGlobList *list;
+ printf ("LITERAL STRINGS\n");
+ if (glob_hash->literal_list == NULL)
+ {
+ printf (" None\n");
+ }
+ else
+ {
+ for (list = glob_hash->literal_list; list; list = list->next)
+ printf (" %s - %s\n", (char *)list->data, list->mime_type);
+ }
+ printf ("\nSIMPLE GLOBS\n");
+ _xdg_glob_hash_node_dump (glob_hash->simple_node, 4);
+
+ printf ("\nFULL GLOBS\n");
+ if (glob_hash->full_list == NULL)
+ {
+ printf (" None\n");
+ }
+ else
+ {
+ for (list = glob_hash->full_list; list; list = list->next)
+ printf (" %s - %s\n", (char *)list->data, list->mime_type);
+ }
+}
+
+
+void
+_xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash,
+ const char *file_name)
+{
+ FILE *glob_file;
+ char line[255];
+
+ glob_file = fopen (file_name, "r");
+
+ if (glob_file == NULL)
+ return;
+
+ /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars.
+ * Blah */
+ while (fgets (line, 255, glob_file) != NULL)
+ {
+ char *colon;
+ if (line[0] == '#')
+ continue;
+
+ colon = strchr (line, ':');
+ if (colon == NULL)
+ continue;
+ *(colon++) = '\000';
+ colon[strlen (colon) -1] = '\000';
+ _xdg_glob_hash_append_glob (glob_hash, colon, line);
+ }
+
+ fclose (glob_file);
+}
diff --git a/xdgmimeglob.h b/xdgmimeglob.h
new file mode 100644
index 00000000..771c4527
--- /dev/null
+++ b/xdgmimeglob.h
@@ -0,0 +1,65 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmimeglob.h: Private file. Datastructure for storing the globs.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __XDG_MIME_GLOB_H__
+#define __XDG_MIME_GLOB_H__
+
+#include "xdgmime.h"
+
+typedef struct XdgGlobHash XdgGlobHash;
+
+typedef enum
+{
+ XDG_GLOB_LITERAL, /* Makefile */
+ XDG_GLOB_SIMPLE, /* *.gif */
+ XDG_GLOB_FULL /* x*.[ch] */
+} XdgGlobType;
+
+
+#ifdef XDG_PREFIX
+#define _xdg_mime_glob_read_from_file XDG_ENTRY(glob_read_from_file)
+#define _xdg_glob_hash_new XDG_ENTRY(hash_new)
+#define _xdg_glob_hash_free XDG_ENTRY(hash_free)
+#define _xdg_glob_hash_lookup_file_name XDG_ENTRY(hash_lookup_file_name)
+#define _xdg_glob_hash_append_glob XDG_ENTRY(hash_append_glob)
+#define _xdg_glob_determine_type XDG_ENTRY(determine_type)
+#define _xdg_glob_hash_dump XDG_ENTRY(hash_dump)
+#endif
+
+void _xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash,
+ const char *file_name);
+XdgGlobHash *_xdg_glob_hash_new (void);
+void _xdg_glob_hash_free (XdgGlobHash *glob_hash);
+const char *_xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash,
+ const char *text);
+void _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash,
+ const char *glob,
+ const char *mime_type);
+XdgGlobType _xdg_glob_determine_type (const char *glob);
+void _xdg_glob_hash_dump (XdgGlobHash *glob_hash);
+
+#endif /* __XDG_MIME_GLOB_H__ */
diff --git a/xdgmimeint.c b/xdgmimeint.c
new file mode 100644
index 00000000..c24f8194
--- /dev/null
+++ b/xdgmimeint.c
@@ -0,0 +1,154 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmimeint.c: Internal defines and functions.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xdgmimeint.h"
+#include <ctype.h>
+#include <string.h>
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+static const unsigned char _xdg_utf8_skip_data[256] = {
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
+};
+
+const char * const _xdg_utf8_skip = _xdg_utf8_skip_data;
+
+
+
+/* Returns the number of unprocessed characters. */
+xdg_unichar_t
+_xdg_utf8_to_ucs4(const char *source)
+{
+ xdg_unichar_t ucs32;
+ if( ! ( *source & 0x80 ) )
+ {
+ ucs32 = *source;
+ }
+ else
+ {
+ int bytelength = 0;
+ xdg_unichar_t result;
+ if ( ! (*source & 0x40) )
+ {
+ ucs32 = *source;
+ }
+ else
+ {
+ if ( ! (*source & 0x20) )
+ {
+ result = *source++ & 0x1F;
+ bytelength = 2;
+ }
+ else if ( ! (*source & 0x10) )
+ {
+ result = *source++ & 0x0F;
+ bytelength = 3;
+ }
+ else if ( ! (*source & 0x08) )
+ {
+ result = *source++ & 0x07;
+ bytelength = 4;
+ }
+ else if ( ! (*source & 0x04) )
+ {
+ result = *source++ & 0x03;
+ bytelength = 5;
+ }
+ else if ( ! (*source & 0x02) )
+ {
+ result = *source++ & 0x01;
+ bytelength = 6;
+ }
+ else
+ {
+ result = *source++;
+ bytelength = 1;
+ }
+
+ for ( bytelength --; bytelength > 0; bytelength -- )
+ {
+ result <<= 6;
+ result |= *source++ & 0x3F;
+ }
+ ucs32 = result;
+ }
+ }
+ return ucs32;
+}
+
+
+/* hullo. this is great code. don't rewrite it */
+
+xdg_unichar_t
+_xdg_ucs4_to_lower (xdg_unichar_t source)
+{
+ /* FIXME: Do a real to_upper sometime */
+ /* CaseFolding-3.2.0.txt has a table of rules. */
+ if ((source & 0xFF) == source)
+ return (xdg_unichar_t) tolower ((unsigned char) source);
+ return source;
+}
+
+int
+_xdg_utf8_validate (const char *source)
+{
+ /* FIXME: actually write */
+ return TRUE;
+}
+
+const char *
+_xdg_get_base_name (const char *file_name)
+{
+ const char *base_name;
+
+ if (file_name == NULL)
+ return NULL;
+
+ base_name = strrchr (file_name, '/');
+
+ if (base_name == NULL)
+ return file_name;
+ else
+ return base_name + 1;
+}
diff --git a/xdgmimeint.h b/xdgmimeint.h
new file mode 100644
index 00000000..28814871
--- /dev/null
+++ b/xdgmimeint.h
@@ -0,0 +1,73 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmimeint.h: Internal defines and functions.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __XDG_MIME_INT_H__
+#define __XDG_MIME_INT_H__
+
+#include "xdgmime.h"
+
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+/* FIXME: Needs to be configure check */
+typedef unsigned int xdg_unichar_t;
+typedef unsigned char xdg_uchar8_t;
+typedef unsigned short xdg_uint16_t;
+typedef unsigned int xdg_uint32_t;
+
+#ifdef XDG_PREFIX
+#define _xdg_utf8_skip XDG_ENTRY(utf8_skip)
+#define _xdg_utf8_to_ucs4 XDG_ENTRY(utf8_to_ucs4)
+#define _xdg_ucs4_to_lower XDG_ENTRY(ucs4_to_lower)
+#define _xdg_utf8_validate XDG_ENTRY(utf8_validate)
+#define _xdg_get_base_name XDG_ENTRY(get_ase_name)
+#endif
+
+#define SWAP_BE16_TO_LE16(val) (xdg_uint16_t)(((xdg_uint16_t)(val) << 8)|((xdg_uint16_t)(val) >> 8))
+
+#define SWAP_BE32_TO_LE32(val) (xdg_uint32_t)((((xdg_uint32_t)(val) & 0xFF000000U) >> 24) | \
+ (((xdg_uint32_t)(val) & 0x00FF0000U) >> 8) | \
+ (((xdg_uint32_t)(val) & 0x0000FF00U) << 8) | \
+ (((xdg_uint32_t)(val) & 0x000000FFU) << 24))
+/* UTF-8 utils
+ */
+extern const char *const _xdg_utf8_skip;
+#define _xdg_utf8_next_char(p) (char *)((p) + _xdg_utf8_skip[*(unsigned char *)(p)])
+#define _xdg_utf8_char_size(p) (int) (_xdg_utf8_skip[*(unsigned char *)(p)])
+
+xdg_unichar_t _xdg_utf8_to_ucs4 (const char *source);
+xdg_unichar_t _xdg_ucs4_to_lower (xdg_unichar_t source);
+int _xdg_utf8_validate (const char *source);
+const char *_xdg_get_base_name (const char *file_name);
+
+#endif /* __XDG_MIME_INT_H__ */
diff --git a/xdgmimemagic.c b/xdgmimemagic.c
new file mode 100644
index 00000000..b98c1e73
--- /dev/null
+++ b/xdgmimemagic.c
@@ -0,0 +1,781 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmimemagic.: Private file. Datastructure for storing magic files.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include "xdgmimemagic.h"
+#include "xdgmimeint.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+extern int errno;
+
+typedef struct XdgMimeMagicMatch XdgMimeMagicMatch;
+typedef struct XdgMimeMagicMatchlet XdgMimeMagicMatchlet;
+
+typedef enum
+{
+ XDG_MIME_MAGIC_SECTION,
+ XDG_MIME_MAGIC_MAGIC,
+ XDG_MIME_MAGIC_ERROR,
+ XDG_MIME_MAGIC_EOF
+} XdgMimeMagicState;
+
+struct XdgMimeMagicMatch
+{
+ const char *mime_type;
+ int priority;
+ XdgMimeMagicMatchlet *matchlet;
+ XdgMimeMagicMatch *next;
+};
+
+
+struct XdgMimeMagicMatchlet
+{
+ int indent;
+ int offset;
+ unsigned int value_length;
+ unsigned char *value;
+ unsigned char *mask;
+ unsigned int range_length;
+ unsigned int word_size;
+ XdgMimeMagicMatchlet *next;
+};
+
+
+struct XdgMimeMagic
+{
+ XdgMimeMagicMatch *match_list;
+ int max_extent;
+};
+
+static XdgMimeMagicMatch *
+_xdg_mime_magic_match_new (void)
+{
+ return calloc (1, sizeof (XdgMimeMagicMatch));
+}
+
+
+static XdgMimeMagicMatchlet *
+_xdg_mime_magic_matchlet_new (void)
+{
+ XdgMimeMagicMatchlet *matchlet;
+
+ matchlet = malloc (sizeof (XdgMimeMagicMatchlet));
+
+ matchlet->indent = 0;
+ matchlet->offset = 0;
+ matchlet->value_length = 0;
+ matchlet->value = NULL;
+ matchlet->mask = NULL;
+ matchlet->range_length = 1;
+ matchlet->word_size = 1;
+ matchlet->next = NULL;
+
+ return matchlet;
+}
+
+
+static void
+_xdg_mime_magic_matchlet_free (XdgMimeMagicMatchlet *mime_magic_matchlet)
+{
+ if (mime_magic_matchlet)
+ {
+ if (mime_magic_matchlet->next)
+ _xdg_mime_magic_matchlet_free (mime_magic_matchlet->next);
+ if (mime_magic_matchlet->value)
+ free (mime_magic_matchlet->value);
+ if (mime_magic_matchlet->mask)
+ free (mime_magic_matchlet->mask);
+ free (mime_magic_matchlet);
+ }
+}
+
+
+/* Frees mime_magic_match and the remainder of its list
+ */
+static void
+_xdg_mime_magic_match_free (XdgMimeMagicMatch *mime_magic_match)
+{
+ XdgMimeMagicMatch *ptr, *next;
+
+ ptr = mime_magic_match;
+ while (ptr)
+ {
+ next = ptr->next;
+
+ if (ptr->mime_type)
+ free ((void *) ptr->mime_type);
+ if (ptr->matchlet)
+ _xdg_mime_magic_matchlet_free (ptr->matchlet);
+ free (ptr);
+
+ ptr = next;
+ }
+}
+
+/* Reads in a hunk of data until a newline character or a '\000' is hit. The
+ * returned string is null terminated, and doesn't include the newline.
+ */
+static unsigned char *
+_xdg_mime_magic_read_to_newline (FILE *magic_file,
+ int *end_of_file)
+{
+ unsigned char *retval;
+ int c;
+ int len, pos;
+
+ len = 128;
+ pos = 0;
+ retval = malloc (len);
+ *end_of_file = FALSE;
+
+ while (TRUE)
+ {
+ c = getc_unlocked (magic_file);
+ if (c == EOF)
+ {
+ *end_of_file = TRUE;
+ break;
+ }
+ if (c == '\n' || c == '\000')
+ break;
+ retval[pos++] = (unsigned char) c;
+ if (pos % 128 == 127)
+ {
+ len = len + 128;
+ retval = realloc (retval, len);
+ }
+ }
+
+ retval[pos] = '\000';
+ return retval;
+}
+
+/* Returns the number read from the file, or -1 if no number could be read.
+ */
+static int
+_xdg_mime_magic_read_a_number (FILE *magic_file,
+ int *end_of_file)
+{
+ /* LONG_MAX is about 20 characters on my system */
+#define MAX_NUMBER_SIZE 30
+ char number_string[MAX_NUMBER_SIZE + 1];
+ int pos = 0;
+ int c;
+ long retval = -1;
+
+ while (TRUE)
+ {
+ c = getc_unlocked (magic_file);
+
+ if (c == EOF)
+ {
+ *end_of_file = TRUE;
+ break;
+ }
+ if (! isdigit (c))
+ {
+ ungetc (c, magic_file);
+ break;
+ }
+ number_string[pos] = (char) c;
+ pos++;
+ if (pos == MAX_NUMBER_SIZE)
+ break;
+ }
+ if (pos > 0)
+ {
+ number_string[pos] = '\000';
+ errno = 0;
+ retval = strtol (number_string, NULL, 10);
+
+ if ((retval < INT_MIN) || (retval > INT_MAX) || (errno != 0))
+ return -1;
+ }
+
+ return retval;
+}
+
+/* Headers are of the format:
+ * [<priority>:<mime-type>]
+ */
+static XdgMimeMagicState
+_xdg_mime_magic_parse_header (FILE *magic_file, XdgMimeMagicMatch *match)
+{
+ int c;
+ char *buffer;
+ char *end_ptr;
+ int end_of_file = 0;
+
+ assert (magic_file != NULL);
+ assert (match != NULL);
+
+ c = getc_unlocked (magic_file);
+ if (c == EOF)
+ return XDG_MIME_MAGIC_EOF;
+ if (c != '[')
+ return XDG_MIME_MAGIC_ERROR;
+
+ match->priority = _xdg_mime_magic_read_a_number (magic_file, &end_of_file);
+ if (end_of_file)
+ return XDG_MIME_MAGIC_EOF;
+ if (match->priority == -1)
+ return XDG_MIME_MAGIC_ERROR;
+
+ c = getc_unlocked (magic_file);
+ if (c == EOF)
+ return XDG_MIME_MAGIC_EOF;
+ if (c != ':')
+ return XDG_MIME_MAGIC_ERROR;
+
+ buffer = _xdg_mime_magic_read_to_newline (magic_file, &end_of_file);
+ if (end_of_file)
+ return XDG_MIME_MAGIC_EOF;
+
+ end_ptr = buffer;
+ while (*end_ptr != ']' && *end_ptr != '\000' && *end_ptr != '\n')
+ end_ptr++;
+ if (*end_ptr != ']')
+ {
+ free (buffer);
+ return XDG_MIME_MAGIC_ERROR;
+ }
+ *end_ptr = '\000';
+
+ match->mime_type = strdup (buffer);
+ free (buffer);
+
+ return XDG_MIME_MAGIC_MAGIC;
+}
+
+static XdgMimeMagicState
+_xdg_mime_magic_parse_error (FILE *magic_file)
+{
+ int c;
+
+ while (1)
+ {
+ c = getc_unlocked (magic_file);
+ if (c == EOF)
+ return XDG_MIME_MAGIC_EOF;
+ if (c == '\n')
+ return XDG_MIME_MAGIC_SECTION;
+ }
+}
+
+/* Headers are of the format:
+ * [ indent ] ">" start-offset "=" value
+ * [ "&" mask ] [ "~" word-size ] [ "+" range-length ] "\n"
+ */
+static XdgMimeMagicState
+_xdg_mime_magic_parse_magic_line (FILE *magic_file,
+ XdgMimeMagicMatch *match)
+{
+ XdgMimeMagicMatchlet *matchlet;
+ int c;
+ int end_of_file;
+ int indent = 0;
+ int bytes_read;
+
+ assert (magic_file != NULL);
+
+ /* Sniff the buffer to make sure it's a valid line */
+ c = getc_unlocked (magic_file);
+ if (c == EOF)
+ return XDG_MIME_MAGIC_EOF;
+ else if (c == '[')
+ {
+ ungetc (c, magic_file);
+ return XDG_MIME_MAGIC_SECTION;
+ }
+ else if (c == '\n')
+ return XDG_MIME_MAGIC_MAGIC;
+
+ /* At this point, it must be a digit or a '>' */
+ end_of_file = FALSE;
+ if (isdigit (c))
+ {
+ ungetc (c, magic_file);
+ indent = _xdg_mime_magic_read_a_number (magic_file, &end_of_file);
+ if (end_of_file)
+ return XDG_MIME_MAGIC_EOF;
+ if (indent == -1)
+ return XDG_MIME_MAGIC_ERROR;
+ c = getc_unlocked (magic_file);
+ if (c == EOF)
+ return XDG_MIME_MAGIC_EOF;
+ }
+
+ if (c != '>')
+ return XDG_MIME_MAGIC_ERROR;
+
+ matchlet = _xdg_mime_magic_matchlet_new ();
+ matchlet->indent = indent;
+ matchlet->offset = _xdg_mime_magic_read_a_number (magic_file, &end_of_file);
+ if (end_of_file)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_EOF;
+ }
+ if (matchlet->offset == -1)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_ERROR;
+ }
+ c = getc_unlocked (magic_file);
+ if (c == EOF)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_EOF;
+ }
+ else if (c != '=')
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_ERROR;
+ }
+
+ /* Next two bytes determine how long the value is */
+ matchlet->value_length = 0;
+ c = getc_unlocked (magic_file);
+ if (c == EOF)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_EOF;
+ }
+ matchlet->value_length = c & 0xFF;
+ matchlet->value_length = matchlet->value_length << 8;
+
+ c = getc_unlocked (magic_file);
+ if (c == EOF)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_EOF;
+ }
+ matchlet->value_length = matchlet->value_length + (c & 0xFF);
+
+ matchlet->value = malloc (matchlet->value_length);
+
+ /* OOM */
+ if (matchlet->value == NULL)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_ERROR;
+ }
+ bytes_read = fread (matchlet->value, 1, matchlet->value_length, magic_file);
+ if (bytes_read != matchlet->value_length)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ if (feof (magic_file))
+ return XDG_MIME_MAGIC_EOF;
+ else
+ return XDG_MIME_MAGIC_ERROR;
+ }
+
+ c = getc_unlocked (magic_file);
+ if (c == '&')
+ {
+ matchlet->mask = malloc (matchlet->value_length);
+ /* OOM */
+ if (matchlet->mask == NULL)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_ERROR;
+ }
+ bytes_read = fread (matchlet->mask, 1, matchlet->value_length, magic_file);
+ if (bytes_read != matchlet->value_length)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ if (feof (magic_file))
+ return XDG_MIME_MAGIC_EOF;
+ else
+ return XDG_MIME_MAGIC_ERROR;
+ }
+ c = getc_unlocked (magic_file);
+ }
+
+ if (c == '~')
+ {
+ matchlet->word_size = _xdg_mime_magic_read_a_number (magic_file, &end_of_file);
+ if (end_of_file)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_EOF;
+ }
+ if (matchlet->word_size != 0 &&
+ matchlet->word_size != 1 &&
+ matchlet->word_size != 2 &&
+ matchlet->word_size != 4)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_ERROR;
+ }
+ c = getc_unlocked (magic_file);
+ }
+
+ if (c == '+')
+ {
+ matchlet->range_length = _xdg_mime_magic_read_a_number (magic_file, &end_of_file);
+ if (end_of_file)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_EOF;
+ }
+ if (matchlet->range_length == -1)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_ERROR;
+ }
+ c = getc_unlocked (magic_file);
+ }
+
+
+ if (c == '\n')
+ {
+ /* We clean up the matchlet, byte swapping if needed */
+ if (matchlet->word_size > 1)
+ {
+ int i;
+ if (matchlet->value_length % matchlet->word_size != 0)
+ {
+ _xdg_mime_magic_matchlet_free (matchlet);
+ return XDG_MIME_MAGIC_ERROR;
+ }
+ /* FIXME: need to get this defined in a <config.h> style file */
+#if LITTLE_ENDIAN
+ for (i = 0; i < matchlet->value_length; i = i + matchlet->word_size)
+ {
+ if (matchlet->word_size == 2)
+ *((xdg_uint16_t *) matchlet->value + i) = SWAP_BE16_TO_LE16 (*((xdg_uint16_t *) (matchlet->value + i)));
+ else if (matchlet->word_size == 4)
+ *((xdg_uint32_t *) matchlet->value + i) = SWAP_BE32_TO_LE32 (*((xdg_uint32_t *) (matchlet->value + i)));
+ if (matchlet->mask)
+ {
+ if (matchlet->word_size == 2)
+ *((xdg_uint16_t *) matchlet->mask + i) = SWAP_BE16_TO_LE16 (*((xdg_uint16_t *) (matchlet->mask + i)));
+ else if (matchlet->word_size == 4)
+ *((xdg_uint32_t *) matchlet->mask + i) = SWAP_BE32_TO_LE32 (*((xdg_uint32_t *) (matchlet->mask + i)));
+
+ }
+ }
+#endif
+ }
+
+ matchlet->next = match->matchlet;
+ match->matchlet = matchlet;
+
+
+ return XDG_MIME_MAGIC_MAGIC;
+ }
+
+ _xdg_mime_magic_matchlet_free (matchlet);
+ if (c == EOF)
+ return XDG_MIME_MAGIC_EOF;
+
+ return XDG_MIME_MAGIC_ERROR;
+}
+
+static int
+_xdg_mime_magic_matchlet_compare_to_data (XdgMimeMagicMatchlet *matchlet,
+ const void *data,
+ size_t len)
+{
+ int i, j;
+
+ for (i = matchlet->offset; i <= matchlet->offset + matchlet->range_length; i++)
+ {
+ int valid_matchlet = TRUE;
+
+ if (i + matchlet->value_length > len)
+ return FALSE;
+
+ if (matchlet->mask)
+ {
+ for (j = 0; j < matchlet->value_length; j++)
+ {
+ if ((matchlet->value[j] & matchlet->mask[j]) !=
+ ((((unsigned char *) data)[j + i]) & matchlet->mask[j]))
+ {
+ valid_matchlet = FALSE;
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (j = 0; j < matchlet->value_length; j++)
+ {
+ if (matchlet->value[j] != ((unsigned char *) data)[j + i])
+ {
+ valid_matchlet = FALSE;
+ break;
+ }
+ }
+ }
+ if (valid_matchlet)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int
+_xdg_mime_magic_matchlet_compare_level (XdgMimeMagicMatchlet *matchlet,
+ const void *data,
+ size_t len,
+ int indent)
+{
+ while ((matchlet != NULL) && (matchlet->indent == indent))
+ {
+ if (_xdg_mime_magic_matchlet_compare_to_data (matchlet, data, len))
+ {
+ if ((matchlet->next == NULL) ||
+ (matchlet->next->indent <= indent))
+ return TRUE;
+
+ if (_xdg_mime_magic_matchlet_compare_level (matchlet->next,
+ data,
+ len,
+ indent + 1))
+ return TRUE;
+ }
+
+ do
+ {
+ matchlet = matchlet->next;
+ }
+ while (matchlet && matchlet->indent > indent);
+ }
+
+ return FALSE;
+}
+
+static int
+_xdg_mime_magic_match_compare_to_data (XdgMimeMagicMatch *match,
+ const void *data,
+ size_t len)
+{
+ return _xdg_mime_magic_matchlet_compare_level (match->matchlet, data, len, 0);
+}
+
+static void
+_xdg_mime_magic_insert_match (XdgMimeMagic *mime_magic,
+ XdgMimeMagicMatch *match)
+{
+ XdgMimeMagicMatch *list;
+
+ if (mime_magic->match_list == NULL)
+ {
+ mime_magic->match_list = match;
+ return;
+ }
+
+ if (match->priority > mime_magic->match_list->priority)
+ {
+ match->next = mime_magic->match_list;
+ mime_magic->match_list = match;
+ return;
+ }
+
+ list = mime_magic->match_list;
+ while (list->next != NULL)
+ {
+ if (list->next->priority < match->priority)
+ {
+ match->next = list->next;
+ list->next = match;
+ return;
+ }
+ list = list->next;
+ }
+ list->next = match;
+ match->next = NULL;
+}
+
+XdgMimeMagic *
+_xdg_mime_magic_new (void)
+{
+ return calloc (1, sizeof (XdgMimeMagic));
+}
+
+void
+_xdg_mime_magic_free (XdgMimeMagic *mime_magic)
+{
+ if (mime_magic) {
+ _xdg_mime_magic_match_free (mime_magic->match_list);
+ free (mime_magic);
+ }
+}
+
+int
+_xdg_mime_magic_get_buffer_extents (XdgMimeMagic *mime_magic)
+{
+ return mime_magic->max_extent;
+}
+
+const char *
+_xdg_mime_magic_lookup_data (XdgMimeMagic *mime_magic,
+ const void *data,
+ size_t len)
+{
+ XdgMimeMagicMatch *match;
+
+ for (match = mime_magic->match_list; match; match = match->next)
+ {
+ if (_xdg_mime_magic_match_compare_to_data (match, data, len))
+ {
+ return match->mime_type;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+_xdg_mime_update_mime_magic_extents (XdgMimeMagic *mime_magic)
+{
+ XdgMimeMagicMatch *match;
+ int max_extent = 0;
+
+ for (match = mime_magic->match_list; match; match = match->next)
+ {
+ XdgMimeMagicMatchlet *matchlet;
+
+ for (matchlet = match->matchlet; matchlet; matchlet = matchlet->next)
+ {
+ int extent;
+
+ extent = matchlet->value_length + matchlet->offset + matchlet->range_length;
+ if (max_extent < extent)
+ max_extent = extent;
+ }
+ }
+
+ mime_magic->max_extent = max_extent;
+}
+
+static XdgMimeMagicMatchlet *
+_xdg_mime_magic_matchlet_mirror (XdgMimeMagicMatchlet *matchlets)
+{
+ XdgMimeMagicMatchlet *new_list;
+ XdgMimeMagicMatchlet *tmp;
+
+ if ((matchlets == NULL) || (matchlets->next == NULL))
+ return matchlets;
+
+ new_list = NULL;
+ tmp = matchlets;
+ while (tmp != NULL)
+ {
+ XdgMimeMagicMatchlet *matchlet;
+
+ matchlet = tmp;
+ tmp = tmp->next;
+ matchlet->next = new_list;
+ new_list = matchlet;
+ }
+
+ return new_list;
+
+}
+
+static void
+_xdg_mime_magic_read_magic_file (XdgMimeMagic *mime_magic,
+ FILE *magic_file)
+{
+ XdgMimeMagicState state;
+ XdgMimeMagicMatch *match = NULL; /* Quiet compiler */
+
+ state = XDG_MIME_MAGIC_SECTION;
+
+ while (state != XDG_MIME_MAGIC_EOF)
+ {
+ switch (state)
+ {
+ case XDG_MIME_MAGIC_SECTION:
+ match = _xdg_mime_magic_match_new ();
+ state = _xdg_mime_magic_parse_header (magic_file, match);
+ if (state == XDG_MIME_MAGIC_EOF || state == XDG_MIME_MAGIC_ERROR)
+ _xdg_mime_magic_match_free (match);
+ break;
+ case XDG_MIME_MAGIC_MAGIC:
+ state = _xdg_mime_magic_parse_magic_line (magic_file, match);
+ if (state == XDG_MIME_MAGIC_SECTION ||
+ (state == XDG_MIME_MAGIC_EOF && match->mime_type))
+ {
+ match->matchlet = _xdg_mime_magic_matchlet_mirror (match->matchlet);
+ _xdg_mime_magic_insert_match (mime_magic, match);
+ }
+ else if (state == XDG_MIME_MAGIC_EOF || state == XDG_MIME_MAGIC_ERROR)
+ _xdg_mime_magic_match_free (match);
+ break;
+ case XDG_MIME_MAGIC_ERROR:
+ state = _xdg_mime_magic_parse_error (magic_file);
+ break;
+ case XDG_MIME_MAGIC_EOF:
+ default:
+ /* Make the compiler happy */
+ assert (0);
+ }
+ }
+ _xdg_mime_update_mime_magic_extents (mime_magic);
+}
+
+void
+_xdg_mime_magic_read_from_file (XdgMimeMagic *mime_magic,
+ const char *file_name)
+{
+ FILE *magic_file;
+ char header[12];
+
+ magic_file = fopen (file_name, "r");
+
+ if (magic_file == NULL)
+ return;
+
+ if (fread (header, 1, 12, magic_file) == 12)
+ {
+ if (memcmp ("MIME-Magic\0\n", header, 12) == 0)
+ _xdg_mime_magic_read_magic_file (mime_magic, magic_file);
+ }
+
+ fclose (magic_file);
+}
diff --git a/xdgmimemagic.h b/xdgmimemagic.h
new file mode 100644
index 00000000..dea0a3c0
--- /dev/null
+++ b/xdgmimemagic.h
@@ -0,0 +1,54 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmimemagic.h: Private file. Datastructure for storing the magic files.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __XDG_MIME_MAGIC_H__
+#define __XDG_MIME_MAGIC_H__
+
+#include <unistd.h>
+#include "xdgmime.h"
+typedef struct XdgMimeMagic XdgMimeMagic;
+
+#ifdef XDG_PREFIX
+#define _xdg_mime_glob_read_from_file XDG_ENTRY(glob_read_from_file)
+#define _xdg_mime_magic_new XDG_ENTRY(magic_new)
+#define _xdg_mime_magic_read_from_file XDG_ENTRY(magic_read_from_file)
+#define _xdg_mime_magic_free XDG_ENTRY(magic_free)
+#define _xdg_mime_magic_get_buffer_extents XDG_ENTRY(magic_get_buffer_extents)
+#define _xdg_mime_magic_lookup_data XDG_ENTRY(magic_lookup_data)
+#endif
+
+
+XdgMimeMagic *_xdg_mime_magic_new (void);
+void _xdg_mime_magic_read_from_file (XdgMimeMagic *mime_magic,
+ const char *file_name);
+void _xdg_mime_magic_free (XdgMimeMagic *mime_magic);
+int _xdg_mime_magic_get_buffer_extents (XdgMimeMagic *mime_magic);
+const char *_xdg_mime_magic_lookup_data (XdgMimeMagic *mime_magic,
+ const void *data,
+ size_t len);
+
+#endif /* __XDG_MIME_MAGIC_H__ */
diff --git a/xdgmimeparent.c b/xdgmimeparent.c
new file mode 100644
index 00000000..511bbacb
--- /dev/null
+++ b/xdgmimeparent.c
@@ -0,0 +1,219 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmimealias.c: Private file. Datastructure for storing the hierarchy.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ * Copyright (C) 2004 Matthias Clasen <mclasen@redhat.com>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xdgmimeparent.h"
+#include "xdgmimeint.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <fnmatch.h>
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+typedef struct XdgMimeParents XdgMimeParents;
+
+struct XdgMimeParents
+{
+ char *mime;
+ char **parents;
+ int n_parents;
+};
+
+struct XdgParentList
+{
+ struct XdgMimeParents *parents;
+ int n_mimes;
+};
+
+XdgParentList *
+_xdg_mime_parent_list_new (void)
+{
+ XdgParentList *list;
+
+ list = malloc (sizeof (XdgParentList));
+
+ list->parents = NULL;
+ list->n_mimes = 0;
+
+ return list;
+}
+
+void
+_xdg_mime_parent_list_free (XdgParentList *list)
+{
+ int i;
+ char **p;
+
+ if (list->parents)
+ {
+ for (i = 0; i < list->n_mimes; i++)
+ {
+ for (p = list->parents[i].parents; *p; p++)
+ free (*p);
+
+ free (list->parents[i].parents);
+ free (list->parents[i].mime);
+ }
+ free (list->parents);
+ }
+ free (list);
+}
+
+static int
+parent_entry_cmp (const void *v1, const void *v2)
+{
+ return strcmp (((XdgMimeParents *)v1)->mime, ((XdgMimeParents *)v2)->mime);
+}
+
+const char **
+_xdg_mime_parent_list_lookup (XdgParentList *list,
+ const char *mime)
+{
+ XdgMimeParents *entry;
+ XdgMimeParents key;
+
+ if (list->n_mimes > 0)
+ {
+ key.mime = (char *)mime;
+ key.parents = NULL;
+
+ entry = bsearch (&key, list->parents, list->n_mimes,
+ sizeof (XdgMimeParents), &parent_entry_cmp);
+ if (entry)
+ return (const char **)entry->parents;
+ }
+
+ return NULL;
+}
+
+void
+_xdg_mime_parent_read_from_file (XdgParentList *list,
+ const char *file_name)
+{
+ FILE *file;
+ char line[255];
+ int i, alloc;
+ XdgMimeParents *entry;
+
+ file = fopen (file_name, "r");
+
+ if (file == NULL)
+ return;
+
+ /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars.
+ * Blah */
+ alloc = list->n_mimes + 16;
+ list->parents = realloc (list->parents, alloc * sizeof (XdgMimeParents));
+ while (fgets (line, 255, file) != NULL)
+ {
+ char *sep;
+ if (line[0] == '#')
+ continue;
+
+ sep = strchr (line, ' ');
+ if (sep == NULL)
+ continue;
+ *(sep++) = '\000';
+ sep[strlen (sep) -1] = '\000';
+ entry = NULL;
+ for (i = 0; i < list->n_mimes; i++)
+ {
+ if (strcmp (list->parents[i].mime, line) == 0)
+ {
+ entry = &(list->parents[i]);
+ break;
+ }
+ }
+
+ if (!entry)
+ {
+ if (list->n_mimes == alloc)
+ {
+ alloc <<= 1;
+ list->parents = realloc (list->parents,
+ alloc * sizeof (XdgMimeParents));
+ }
+ list->parents[list->n_mimes].mime = strdup (line);
+ list->parents[list->n_mimes].parents = NULL;
+ entry = &(list->parents[list->n_mimes]);
+ list->n_mimes++;
+ }
+
+ if (!entry->parents)
+ {
+ entry->n_parents = 1;
+ entry->parents = malloc ((entry->n_parents + 1) * sizeof (char *));
+ }
+ else
+ {
+ entry->n_parents += 1;
+ entry->parents = realloc (entry->parents,
+ (entry->n_parents + 2) * sizeof (char *));
+ }
+ entry->parents[entry->n_parents - 1] = strdup (sep);
+ entry->parents[entry->n_parents] = NULL;
+ }
+
+ list->parents = realloc (list->parents,
+ list->n_mimes * sizeof (XdgMimeParents));
+
+ fclose (file);
+
+ if (list->n_mimes > 1)
+ qsort (list->parents, list->n_mimes,
+ sizeof (XdgMimeParents), &parent_entry_cmp);
+}
+
+
+void
+_xdg_mime_parent_list_dump (XdgParentList *list)
+{
+ int i;
+ char **p;
+
+ if (list->parents)
+ {
+ for (i = 0; i < list->n_mimes; i++)
+ {
+ for (p = list->parents[i].parents; *p; p++)
+ printf ("%s %s\n", list->parents[i].mime, *p);
+ }
+ }
+}
+
+
diff --git a/xdgmimeparent.h b/xdgmimeparent.h
new file mode 100644
index 00000000..da29452c
--- /dev/null
+++ b/xdgmimeparent.h
@@ -0,0 +1,50 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* xdgmimeparent.h: Private file. Datastructure for storing the hierarchy.
+ *
+ * More info can be found at http://www.freedesktop.org/standards/
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ * Copyright (C) 200 Matthias Clasen <mclasen@redhat.com>
+ *
+ * Licensed under the Academic Free License version 2.0
+ * Or under the following terms:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __XDG_MIME_PARENT_H__
+#define __XDG_MIME_PARENT_H__
+
+#include "xdgmime.h"
+
+typedef struct XdgParentList XdgParentList;
+
+#ifdef XDG_PREFIX
+#define _xdg_mime_parent_read_from_file XDG_ENTRY(parent_read_from_file)
+#define _xdg_mime_parent_list_new XDG_ENTRY(parent_list_new)
+#define _xdg_mime_parent_list_free XDG_ENTRY(parent_list_free)
+#define _xdg_mime_parent_list_lookup XDG_ENTRY(parent_list_lookup)
+#endif
+
+void _xdg_mime_parent_read_from_file (XdgParentList *list,
+ const char *file_name);
+XdgParentList *_xdg_mime_parent_list_new (void);
+void _xdg_mime_parent_list_free (XdgParentList *list);
+const char **_xdg_mime_parent_list_lookup (XdgParentList *list,
+ const char *mime);
+void _xdg_mime_parent_list_dump (XdgParentList *list);
+
+#endif /* __XDG_MIME_PARENT_H__ */
diff --git a/xsel-0.9.6.tar b/xsel-0.9.6.tar
new file mode 100644
index 00000000..47e5ceb2
--- /dev/null
+++ b/xsel-0.9.6.tar
Binary files differ