/// Silly function. static void builtin_complete_add2(const wchar_t *cmd, int cmd_type, const wchar_t *short_opt, const wcstring_list_t &gnu_opt, const wcstring_list_t &old_opt, int result_mode, const wchar_t *condition, const wchar_t *comp, const wchar_t *desc, int flags) { size_t i; const wchar_t *s; for (s = short_opt; *s; s++) { complete_add(cmd, cmd_type, wcstring(1, *s), option_type_short, result_mode, condition, comp, desc, flags); } for (i = 0; i < gnu_opt.size(); i++) { complete_add(cmd, cmd_type, gnu_opt.at(i), option_type_double_long, result_mode, condition, comp, desc, flags); } for (i = 0; i < old_opt.size(); i++) { complete_add(cmd, cmd_type, old_opt.at(i), option_type_single_long, result_mode, condition, comp, desc, flags); } if (old_opt.empty() && gnu_opt.empty() && wcslen(short_opt) == 0) { complete_add(cmd, cmd_type, wcstring(), option_type_args_only, result_mode, condition, comp, desc, flags); } }
static void builtin_complete_remove_cmd(const wcstring &cmd, int cmd_type, const wchar_t *short_opt, const wcstring_list_t &gnu_opt, const wcstring_list_t &old_opt) { bool removed = false; size_t i; for (i=0; short_opt[i] != L'\0'; i++) { complete_remove(cmd, cmd_type, wcstring(1, short_opt[i]), option_type_short); removed = true; } for (i=0; i < old_opt.size(); i++) { complete_remove(cmd, cmd_type, old_opt.at(i), option_type_single_long); removed = true; } for (i=0; i < gnu_opt.size(); i++) { complete_remove(cmd, cmd_type, gnu_opt.at(i), option_type_double_long); removed = true; } if (! removed) { // This means that all loops were empty complete_remove_all(cmd, cmd_type); } }
/** Silly function */ static void builtin_complete_remove(const wcstring_list_t &cmd, const wcstring_list_t &path, const wchar_t *short_opt, const wcstring_list_t &gnu_opt, const wcstring_list_t &old_opt) { for (size_t i=0; i<cmd.size(); i++) { builtin_complete_remove2(cmd.at(i).c_str(), COMMAND, short_opt, gnu_opt, old_opt); } for (size_t i=0; i<path.size(); i++) { builtin_complete_remove2(path.at(i).c_str(), PATH, short_opt, gnu_opt, old_opt); } }
/** Silly function */ static void builtin_complete_add(const wcstring_list_t &cmd, const wcstring_list_t &path, const wchar_t *short_opt, wcstring_list_t &gnu_opt, wcstring_list_t &old_opt, int result_mode, int authoritative, const wchar_t *condition, const wchar_t *comp, const wchar_t *desc, int flags) { for (size_t i=0; i<cmd.size(); i++) { builtin_complete_add2(cmd.at(i).c_str(), COMMAND, short_opt, gnu_opt, old_opt, result_mode, condition, comp, desc, flags); if (authoritative != -1) { complete_set_authoritative(cmd.at(i).c_str(), COMMAND, authoritative); } } for (size_t i=0; i<path.size(); i++) { builtin_complete_add2(path.at(i).c_str(), PATH, short_opt, gnu_opt, old_opt, result_mode, condition, comp, desc, flags); if (authoritative != -1) { complete_set_authoritative(path.at(i).c_str(), PATH, authoritative); } } }
static void update_export_array_if_necessary(bool recalc) { ASSERT_IS_MAIN_THREAD(); if (recalc && !get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } if (has_changed_exported) { std::map<wcstring, wcstring> vals; debug(4, L"env_export_arr() recalc"); get_exported(top, &vals); if (uvars()) { const wcstring_list_t uni = uvars()->get_names(true, false); for (size_t i = 0; i < uni.size(); i++) { const wcstring &key = uni.at(i); const env_var_t val = uvars()->get(key); if (!val.missing() && val != ENV_NULL) { // Note that std::map::insert does NOT overwrite a value already in the map, // which we depend on here. vals.insert(std::pair<wcstring, wcstring>(key, val)); } } } std::vector<std::string> local_export_buffer; export_func(vals, local_export_buffer); export_array.set(local_export_buffer); has_changed_exported = false; } }
void function_prepare_environment(const wcstring &name, const wchar_t *const *argv, const std::map<wcstring, env_var_t> &inherited_vars) { // Three components of the environment: // 1. argv // 2. named arguments // 3. inherited variables env_set_argv(argv); const wcstring_list_t named_arguments = function_get_named_arguments(name); if (!named_arguments.empty()) { const wchar_t *const *arg; size_t i; for (i = 0, arg = argv; i < named_arguments.size(); i++) { env_set(named_arguments.at(i).c_str(), *arg, ENV_LOCAL | ENV_USER); if (*arg) arg++; } } for (std::map<wcstring, env_var_t>::const_iterator it = inherited_vars.begin(), end = inherited_vars.end(); it != end; ++it) { env_set(it->first, it->second.missing() ? NULL : it->second.c_str(), ENV_LOCAL | ENV_USER); } }
static void print_colors(io_streams_t &streams) { const wcstring_list_t result = rgb_color_t::named_color_names(); size_t i; for (i = 0; i < result.size(); i++) { streams.out.append(result.at(i)); streams.out.push_back(L'\n'); } }
/// Print terminfo key binding names to string buffer used for standard output. /// /// \param all if set, all terminfo key binding names will be printed. If not set, only ones that /// are defined for this terminal are printed. void builtin_bind_t::key_names(bool all, io_streams_t &streams) { const wcstring_list_t names = input_terminfo_get_names(!all); for (size_t i = 0; i < names.size(); i++) { const wcstring &name = names.at(i); streams.out.append_format(L"%ls\n", name.c_str()); } }
static void print_colors(void) { const wcstring_list_t result = rgb_color_t::named_color_names(); size_t i; for (i=0; i < result.size(); i++) { stdout_buffer.append(result.at(i)); stdout_buffer.push_back(L'\n'); } }
/** Merge multiple completions with the same description to the same line */ static void join_completions( wcstring_list_t lst ) { std::map<wcstring, long> desc_table; for( size_t i=0; i<lst.size(); i++ ) { const wchar_t *item = lst.at(i).c_str(); const wchar_t *desc = wcschr( item, COMPLETE_SEP ); long prev_idx; if( !desc ) continue; desc++; prev_idx = desc_table[desc] - 1; if( prev_idx == -1 ) { desc_table[desc] = (long)(i+1); } else { const wchar_t *old = lst.at(i).c_str(); const wchar_t *old_end = wcschr( old, COMPLETE_SEP ); if( old_end ) { wcstring foo; foo.append(old, old_end - old); foo.push_back(COMPLETE_ITEM_SEP); foo.append(item); lst.at(prev_idx) = foo; lst.at(i).clear(); } } } /* Remove empty strings */ lst.erase(remove(lst.begin(), lst.end(), wcstring()), lst.end()); }
static bool run_test_test(int expected, wcstring_list_t &lst) { parser_t parser(PARSER_TYPE_GENERAL, true); size_t i, count = lst.size(); wchar_t **argv = new wchar_t *[count+2]; argv[0] = (wchar_t *)L"test"; for (i=0; i < count; i++) { argv[i+1] = (wchar_t *)lst.at(i).c_str(); } argv[i+1] = NULL; int result = builtin_test(parser, argv); delete[] argv; return expected == result; }
/** Silly function */ static void builtin_complete_remove3(const wchar_t *cmd, int cmd_type, wchar_t short_opt, const wcstring_list_t &long_opt) { for (size_t i=0; i<long_opt.size(); i++) { complete_remove(cmd, cmd_type, short_opt, long_opt.at(i).c_str()); } }
void history_tests_t::test_history_races_pound_on_history() { /* Called in child process to modify history */ history_t *hist = new history_t(L"race_test"); hist->chaos_mode = true; const wcstring_list_t lines = generate_history_lines(getpid()); for (size_t idx = 0; idx < lines.size(); idx++) { const wcstring &line = lines.at(idx); hist->add(line); hist->save(); } delete hist; }
/** Replace completion strings with a comp_t structure */ static std::vector<comp_t *> mangle_completions( wcstring_list_t &lst, const wchar_t *prefix ) { std::vector<comp_t *> result; for( size_t i=0; i<lst.size(); i++ ) { wcstring &next = lst.at(i); size_t start, end; comp_t zerod = {}; comp_t *comp = new comp_t(zerod); for( start=end=0; 1; end++ ) { wchar_t c = next.c_str()[end]; if( (c == COMPLETE_ITEM_SEP) || (c==COMPLETE_SEP) || !c) { wcstring start2 = wcstring(next, start, end - start); wcstring str = escape_string(start2, ESCAPE_ALL | ESCAPE_NO_QUOTED); comp->comp_width += my_wcswidth( str.c_str() ); comp->comp.push_back(str); start = end+1; } if( c == COMPLETE_SEP ) { comp->desc = next.c_str() + start; break; } if( !c ) break; } comp->comp_width += (int)(my_wcswidth(prefix)*comp->comp.size() + 2*(comp->comp.size()-1)); comp->desc_width = comp->desc.empty()?0:my_wcswidth( comp->desc.c_str() ); comp->pref_width = comp->comp_width + comp->desc_width + (comp->desc_width?4:0); result.push_back(comp); } recalc_width( result, prefix ); return result; }
void parse_util_set_argv( const wchar_t * const *argv, const wcstring_list_t &named_arguments ) { if( *argv ) { const wchar_t * const *arg; wcstring sb; for( arg=argv; *arg; arg++ ) { if( arg != argv ) { sb.append(ARRAY_SEP_STR); } sb.append(*arg); } env_set( L"argv", sb.c_str(), ENV_LOCAL ); } else { env_set( L"argv", 0, ENV_LOCAL ); } if( named_arguments.size() ) { const wchar_t * const *arg; size_t i; for( i=0, arg=argv; i < named_arguments.size(); i++ ) { env_set( named_arguments.at(i).c_str(), *arg, ENV_LOCAL ); if( *arg ) arg++; } } }
/** Substitute any series of whitespace with a single space character inside completion descriptions. Remove all whitespace from beginning/end of completion descriptions. */ static void mangle_descriptions( wcstring_list_t &lst ) { int skip; for( size_t i=0; i<lst.size(); i++ ) { wcstring &next = lst.at(i); size_t in, out; skip=1; size_t next_idx = 0; while( next_idx < next.size() && next[next_idx] != COMPLETE_SEP ) next_idx++; if( next_idx == next.size() ) continue; in=out=next_idx + 1; while( in < next.size() ) { if( next[in] == L' ' || next[in]==L'\t' || next[in]<32 ) { if( !skip ) next[out++]=L' '; skip=1; } else { next[out++] = next[in]; skip=0; } in++; } next.resize(out); } }
/** Call env_set. If this is a path variable, e.g. PATH, validate the elements. On error, print a description of the problem to stderr. */ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope, io_streams_t &streams) { size_t i; int retcode = 0; const wchar_t *val_str=NULL; if (is_path_variable(key)) { /* Fix for https://github.com/fish-shell/fish-shell/issues/199 . Return success if any path setting succeeds. */ bool any_success = false; /* Don't bother validating (or complaining about) values that are already present. When determining already-present values, use ENV_DEFAULT instead of the passed-in scope because in: set -l PATH stuff $PATH where we are temporarily shadowing a variable, we want to compare against the shadowed value, not the (missing) local value. Also don't bother to complain about relative paths, which don't start with /. */ wcstring_list_t existing_values; const env_var_t existing_variable = env_get_string(key, ENV_DEFAULT); if (! existing_variable.missing_or_empty()) tokenize_variable_array(existing_variable, existing_values); for (i=0; i< val.size() ; i++) { const wcstring &dir = val.at(i); if (!string_prefixes_string(L"/", dir) || list_contains_string(existing_values, dir)) { any_success = true; continue; } bool show_perror = false; int show_hint = 0; bool error = false; struct stat buff; if (wstat(dir, &buff)) { error = true; show_perror = true; } if (!(S_ISDIR(buff.st_mode))) { error = true; } if (!error) { any_success = true; } else { streams.err.append_format(_(BUILTIN_SET_PATH_ERROR), L"set", dir.c_str(), key); const wchar_t *colon = wcschr(dir.c_str(), L':'); if (colon && *(colon+1)) { show_hint = 1; } } if (show_perror) { builtin_wperror(L"set", streams); } if (show_hint) { streams.err.append_format(_(BUILTIN_SET_PATH_HINT), L"set", key, key, wcschr(dir.c_str(), L':')+1); } } /* Fail at setting the path if we tried to set it to something non-empty, but it wound up empty. */ if (! val.empty() && ! any_success) { return 1; } } wcstring sb; if (! val.empty()) { for (i=0; i< val.size() ; i++) { sb.append(val[i]); if (i<val.size() - 1) { sb.append(ARRAY_SEP_STR); } } val_str = sb.c_str(); } switch (env_set(key, val_str, scope | ENV_USER)) { case ENV_PERM: { streams.err.append_format(_(L"%ls: Tried to change the read-only variable '%ls'\n"), L"set", key); retcode=1; break; } case ENV_SCOPE: { streams.err.append_format(_(L"%ls: Tried to set the special variable '%ls' with the wrong scope\n"), L"set", key); retcode=1; break; } case ENV_INVALID: { streams.err.append_format(_(L"%ls: Tried to set the special variable '%ls' to an invalid value\n"), L"set", key); retcode=1; break; } } return retcode; }
/** Call env_set. If this is a path variable, e.g. PATH, validate the elements. On error, print a description of the problem to stderr. */ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope) { size_t i; int retcode = 0; const wchar_t *val_str=NULL; if (is_path_variable(key)) { /* Fix for https://github.com/fish-shell/fish-shell/issues/199 . Return success if any path setting succeeds. */ bool any_success = false; /* Don't bother validating (or complaining about) values that are already present */ wcstring_list_t existing_values; const env_var_t existing_variable = env_get_string(key, scope); if (! existing_variable.missing_or_empty()) tokenize_variable_array(existing_variable, existing_values); for (i=0; i< val.size() ; i++) { const wcstring &dir = val.at(i); if (list_contains_string(existing_values, dir)) { any_success = true; continue; } bool show_perror = false; int show_hint = 0; bool error = false; struct stat buff; if (wstat(dir, &buff)) { error = true; show_perror = true; } if (!(S_ISDIR(buff.st_mode))) { error = true; } if (!error) { any_success = true; } else { append_format(stderr_buffer, _(BUILTIN_SET_PATH_ERROR), L"set", dir.c_str(), key); const wchar_t *colon = wcschr(dir.c_str(), L':'); if (colon && *(colon+1)) { show_hint = 1; } } if (show_perror) { builtin_wperror(L"set"); } if (show_hint) { append_format(stderr_buffer, _(BUILTIN_SET_PATH_HINT), L"set", key, key, wcschr(dir.c_str(), L':')+1); } } /* Fail at setting the path if we tried to set it to something non-empty, but it wound up empty. */ if (! val.empty() && ! any_success) { return 1; } } wcstring sb; if (! val.empty()) { for (i=0; i< val.size() ; i++) { sb.append(val[i]); if (i<val.size() - 1) { sb.append(ARRAY_SEP_STR); } } val_str = sb.c_str(); } switch (env_set(key, val_str, scope | ENV_USER)) { case ENV_PERM: { append_format(stderr_buffer, _(L"%ls: Tried to change the read-only variable '%ls'\n"), L"set", key); retcode=1; break; } case ENV_SCOPE: { append_format(stderr_buffer, _(L"%ls: Tried to set the special variable '%ls' with the wrong scope\n"), L"set", key); retcode=1; break; } case ENV_INVALID: { append_format(stderr_buffer, _(L"%ls: Tried to set the special variable '%ls' to an invalid value\n"), L"set", key); retcode=1; break; } } return retcode; }
/// Evaluate a conditional expression given the arguments. If fromtest is set, the caller is the /// test or [ builtin; with the pointer giving the name of the command. for POSIX conformance this /// supports a more limited range of functionality. /// /// Return status is the final shell status, i.e. 0 for true, 1 for false and 2 for error. int builtin_test(parser_t &parser, io_streams_t &streams, wchar_t **argv) { UNUSED(parser); using namespace test_expressions; // The first argument should be the name of the command ('test'). if (!argv[0]) return BUILTIN_TEST_FAIL; // Whether we are invoked with bracket '[' or not. wchar_t *program_name = argv[0]; const bool is_bracket = !wcscmp(program_name, L"["); size_t argc = 0; while (argv[argc + 1]) argc++; // If we're bracket, the last argument ought to be ]; we ignore it. Note that argc is the number // of arguments after the command name; thus argv[argc] is the last argument. if (is_bracket) { if (!wcscmp(argv[argc], L"]")) { // Ignore the closing bracket from now on. argc--; } else { streams.err.append(L"[: the last argument must be ']'\n"); return BUILTIN_TEST_FAIL; } } // Collect the arguments into a list. const wcstring_list_t args(argv + 1, argv + 1 + argc); if (argc == 0) { return BUILTIN_TEST_FAIL; // Per 1003.1, exit false. } else if (argc == 1) { // Per 1003.1, exit true if the arg is non-empty. return args.at(0).empty() ? BUILTIN_TEST_FAIL : BUILTIN_TEST_SUCCESS; } // Try parsing. If expr is not nil, we are responsible for deleting it. wcstring err; expression *expr = test_parser::parse_args(args, err, program_name); if (!expr) { #if 0 printf("Oops! test was given args:\n"); for (size_t i=0; i < argc; i++) { printf("\t%ls\n", args.at(i).c_str()); } printf("and returned parse error: %ls\n", err.c_str()); #endif streams.err.append(err); return BUILTIN_TEST_FAIL; } wcstring_list_t eval_errors; bool result = expr->evaluate(eval_errors); if (!eval_errors.empty() && !should_suppress_stderr_for_tests()) { printf("test returned eval errors:\n"); for (size_t i = 0; i < eval_errors.size(); i++) { printf("\t%ls\n", eval_errors.at(i).c_str()); } } delete expr; return result ? BUILTIN_TEST_SUCCESS : BUILTIN_TEST_FAIL; }
const wcstring &arg(unsigned int idx) { return strings.at(idx); }
/** This internal helper function does all the real work. By using two functions, the internal function can return on various places in the code, and the caller can take care of various cleanup work. cmd: the command name ('grep') really_load: whether to actually parse it as a function, or just check it it exists reload: whether to reload it if it's already loaded path_list: the set of paths to check Result: if really_load is true, returns whether the function was loaded. Otherwise returns whether the function existed. */ bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_load, bool reload, const wcstring_list_t &path_list) { /* Note that we are NOT locked in this function! */ bool reloaded = 0; /* Try using a cached function. If we really want the function to be loaded, require that it be really loaded. If we're not reloading, allow stale functions. */ { bool allow_stale_functions = ! reload; /* Take a lock */ scoped_lock locker(lock); /* Get the function */ autoload_function_t * func = this->get_node(cmd); /* Determine if we can use this cached function */ bool use_cached; if (! func) { /* Can't use a function that doesn't exist */ use_cached = false; } else if (really_load && ! func->is_placeholder && ! func->is_loaded) { /* Can't use an unloaded function */ use_cached = false; } else if (! allow_stale_functions && is_stale(func)) { /* Can't use a stale function */ use_cached = false; } else { /* I guess we can use it */ use_cached = true; } /* If we can use this function, return whether we were able to access it */ if (use_cached) { return func->is_internalized || func->access.accessible; } } /* The source of the script will end up here */ wcstring script_source; bool has_script_source = false; /* Whether we found an accessible file */ bool found_file = false; /* Look for built-in scripts via a binary search */ const builtin_script_t *matching_builtin_script = NULL; if (builtin_script_count > 0) { const builtin_script_t test_script = {cmd.c_str(), NULL}; const builtin_script_t *array_end = builtin_scripts + builtin_script_count; const builtin_script_t *found = std::lower_bound(builtin_scripts, array_end, test_script, script_name_precedes_script_name); if (found != array_end && ! wcscmp(found->name, test_script.name)) { /* We found it */ matching_builtin_script = found; } } if (matching_builtin_script) { has_script_source = true; script_source = str2wcstring(matching_builtin_script->def); /* Make a node representing this function */ scoped_lock locker(lock); autoload_function_t *func = this->get_autoloaded_function_with_creation(cmd, really_load); /* This function is internalized */ func->is_internalized = true; /* It's a fiction to say the script is loaded at this point, but we're definitely going to load it down below. */ if (really_load) func->is_loaded = true; } if (! has_script_source) { /* Iterate over path searching for suitable completion files */ for (size_t i=0; i<path_list.size(); i++) { wcstring next = path_list.at(i); wcstring path = next + L"/" + cmd + L".fish"; const file_access_attempt_t access = access_file(path, R_OK); if (access.accessible) { /* Found it! */ found_file = true; /* Now we're actually going to take the lock. */ scoped_lock locker(lock); autoload_function_t *func = this->get_node(cmd); /* Generate the source if we need to load it */ bool need_to_load_function = really_load && (func == NULL || func->access.mod_time != access.mod_time || ! func->is_loaded); if (need_to_load_function) { /* Generate the script source */ wcstring esc = escape_string(path, 1); script_source = L". " + esc; has_script_source = true; /* Remove any loaded command because we are going to reload it. Note that this will deadlock if command_removed calls back into us. */ if (func && func->is_loaded) { command_removed(cmd); func->is_placeholder = false; } /* Mark that we're reloading it */ reloaded = true; } /* Create the function if we haven't yet. This does not load it. Do not trigger eviction unless we are actually loading, because we don't want to evict off of the main thread. */ if (! func) { func = get_autoloaded_function_with_creation(cmd, really_load); } /* It's a fiction to say the script is loaded at this point, but we're definitely going to load it down below. */ if (need_to_load_function) func->is_loaded = true; /* Unconditionally record our access time */ func->access = access; break; } } /* If no file or builtin script was found we insert a placeholder function. Later we only research if the current time is at least five seconds later. This way, the files won't be searched over and over again. */ if (! found_file && ! has_script_source) { scoped_lock locker(lock); /* Generate a placeholder */ autoload_function_t *func = this->get_node(cmd); if (! func) { func = new autoload_function_t(cmd); func->is_placeholder = true; if (really_load) { this->add_node(func); } else { this->add_node_without_eviction(func); } } func->access.last_checked = time(NULL); } } /* If we have a script, either built-in or a file source, then run it */ if (really_load && has_script_source) { if (exec_subshell(script_source, false /* do not apply exit status */) == -1) { /* Do nothing on failure */ } } if (really_load) { return reloaded; } else { return found_file || has_script_source; } }
/* Tests whether the specified string cpath is the prefix of anything we could cd to. directories is a list of possible parent directories (typically either the working directory, or the cdpath). This does I/O! We expect the path to already be unescaped. */ bool is_potential_path(const wcstring &const_path, const wcstring_list_t &directories, path_flags_t flags, wcstring *out_path) { ASSERT_IS_BACKGROUND_THREAD(); const bool require_dir = !! (flags & PATH_REQUIRE_DIR); wcstring clean_path; int has_magic = 0; bool result = false; wcstring path(const_path); if (flags & PATH_EXPAND_TILDE) expand_tilde(path); // debug( 1, L"%ls -> %ls ->%ls", path, tilde, unescaped ); for( size_t i=0; i < path.size(); i++) { wchar_t c = path.at(i); switch( c ) { case PROCESS_EXPAND: case VARIABLE_EXPAND: case VARIABLE_EXPAND_SINGLE: case BRACKET_BEGIN: case BRACKET_END: case BRACKET_SEP: case ANY_CHAR: case ANY_STRING: case ANY_STRING_RECURSIVE: { has_magic = 1; break; } case INTERNAL_SEPARATOR: { break; } default: { clean_path.push_back(c); break; } } } if( ! has_magic && ! clean_path.empty() ) { /* Don't test the same path multiple times, which can happen if the path is absolute and the CDPATH contains multiple entries */ std::set<wcstring> checked_paths; /* Keep a cache of which paths / filesystems are case sensitive */ case_sensitivity_cache_t case_sensitivity_cache; for (size_t wd_idx = 0; wd_idx < directories.size() && ! result; wd_idx++) { const wcstring &wd = directories.at(wd_idx); const wcstring abs_path = apply_working_directory(clean_path, wd); /* Skip this if it's empty or we've already checked it */ if (abs_path.empty() || checked_paths.count(abs_path)) continue; checked_paths.insert(abs_path); /* If we end with a slash, then it must be a directory */ bool must_be_full_dir = abs_path.at(abs_path.size()-1) == L'/'; if (must_be_full_dir) { struct stat buf; if (0 == wstat(abs_path, &buf) && S_ISDIR(buf.st_mode)) { result = true; /* Return the path suffix, not the whole absolute path */ if (out_path) *out_path = clean_path; } } else { DIR *dir = NULL; /* We do not end with a slash; it does not have to be a directory */ const wcstring dir_name = wdirname(abs_path); const wcstring base_name = wbasename(abs_path); if (dir_name == L"/" && base_name == L"/") { result = true; if (out_path) *out_path = clean_path; } else if ((dir = wopendir(dir_name))) { // We opened the dir_name; look for a string where the base name prefixes it wcstring ent; // Check if we're case insensitive bool case_insensitive = fs_is_case_insensitive(dir_name, dirfd(dir), case_sensitivity_cache); // Don't ask for the is_dir value unless we care, because it can cause extra filesystem acces */ bool is_dir = false; while (wreaddir_resolving(dir, dir_name, ent, require_dir ? &is_dir : NULL)) { /* Determine which function to call to check for prefixes */ bool (*prefix_func)(const wcstring &, const wcstring &); if (case_insensitive) { prefix_func = string_prefixes_string_case_insensitive; } else { prefix_func = string_prefixes_string; } if (prefix_func(base_name, ent) && (! require_dir || is_dir)) { result = true; if (out_path) { /* We want to return the path in the same "form" as it was given. Take the given path, get its basename. Append that to the output if the basename actually prefixes the path (which it won't if the given path contains no slashes), and isn't a slash (so we don't duplicate slashes). Then append the directory entry. */ out_path->clear(); const wcstring path_base = wdirname(const_path); if (prefix_func(path_base, const_path)) { out_path->append(path_base); if (! string_suffixes_string(L"/", *out_path)) out_path->push_back(L'/'); } out_path->append(ent); /* We actually do want a trailing / for directories, since it makes autosuggestion a bit nicer */ if (is_dir) out_path->push_back(L'/'); } break; } } closedir(dir); } } } } return result; }
/** Silly function */ static void builtin_complete_add2(const wchar_t *cmd, int cmd_type, const wchar_t *short_opt, const wcstring_list_t &gnu_opt, const wcstring_list_t &old_opt, int result_mode, const wchar_t *condition, const wchar_t *comp, const wchar_t *desc, int flags) { size_t i; const wchar_t *s; for (s=short_opt; *s; s++) { complete_add(cmd, cmd_type, *s, 0, 0, result_mode, condition, comp, desc, flags); } for (i=0; i<gnu_opt.size(); i++) { complete_add(cmd, cmd_type, 0, gnu_opt.at(i).c_str(), 0, result_mode, condition, comp, desc, flags); } for (i=0; i<old_opt.size(); i++) { complete_add(cmd, cmd_type, 0, old_opt.at(i).c_str(), 1, result_mode, condition, comp, desc, flags); } if (old_opt.empty() && gnu_opt.empty() && wcslen(short_opt) == 0) { complete_add(cmd, cmd_type, 0, 0, 0, result_mode, condition, comp, desc, flags); } }
/* * Evaluate a conditional expression given the arguments. * If fromtest is set, the caller is the test or [ builtin; * with the pointer giving the name of the command. * for POSIX conformance this supports a more limited range * of functionality. * * Return status is the final shell status, i.e. 0 for true, * 1 for false and 2 for error. */ int builtin_test(parser_t &parser, wchar_t **argv) { using namespace test_expressions; /* The first argument should be the name of the command ('test') */ if (! argv[0]) return BUILTIN_TEST_FAIL; /* Whether we are invoked with bracket '[' or not */ const bool is_bracket = ! wcscmp(argv[0], L"["); size_t argc = 0; while (argv[argc + 1]) argc++; /* If we're bracket, the last argument ought to be ]; we ignore it. Note that argc is the number of arguments after the command name; thus argv[argc] is the last argument. */ if (is_bracket) { if (! wcscmp(argv[argc], L"]")) { /* Ignore the closing bracketp */ argc--; } else { builtin_show_error(L"[: the last argument must be ']'\n"); return BUILTIN_TEST_FAIL; } } /* Collect the arguments into a list */ const wcstring_list_t args(argv + 1, argv + 1 + argc); switch (argc) { case 0: { // Per 1003.1, exit false return BUILTIN_TEST_FAIL; } case 1: { // Per 1003.1, exit true if the arg is non-empty return args.at(0).empty() ? BUILTIN_TEST_FAIL : BUILTIN_TEST_SUCCESS; } default: { // Try parsing. If expr is not nil, we are responsible for deleting it. wcstring err; expression *expr = test_parser::parse_args(args, err); if (! expr) { #if 0 printf("Oops! test was given args:\n"); for (size_t i=0; i < argc; i++) { printf("\t%ls\n", args.at(i).c_str()); } printf("and returned parse error: %ls\n", err.c_str()); #endif builtin_show_error(err); return BUILTIN_TEST_FAIL; } else { wcstring_list_t eval_errors; bool result = expr->evaluate(eval_errors); if (! eval_errors.empty()) { printf("test returned eval errors:\n"); for (size_t i=0; i < eval_errors.size(); i++) { printf("\t%ls\n", eval_errors.at(i).c_str()); } } delete expr; return result ? BUILTIN_TEST_SUCCESS : BUILTIN_TEST_FAIL; } } } return 1; }