fish-shell/builtin_set.cpp

828 lines
20 KiB
C++
Raw Normal View History

/** \file builtin_set.c Functions defining the set builtin
Functions used for implementing the set builtin.
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <wctype.h>
#include <sys/types.h>
#include <termios.h>
#include <signal.h>
#include <vector>
#include <algorithm>
#include "fallback.h"
#include "util.h"
#include "wutil.h"
#include "builtin.h"
#include "env.h"
#include "expand.h"
#include "common.h"
#include "wgetopt.h"
#include "proc.h"
#include "parser.h"
/* We know about these buffers */
extern wcstring stdout_buffer, stderr_buffer;
/**
Error message for invalid path operations
*/
#define BUILTIN_SET_PATH_ERROR L"%ls: Warning: path component %ls may not be valid in %ls.\n"
/**
Hint for invalid path operation with a colon
*/
#define BUILTIN_SET_PATH_HINT L"%ls: Did you mean 'set %ls $%ls %ls'?\n"
/**
Error for mismatch between index count and elements
*/
#define BUILTIN_SET_ARG_COUNT L"%ls: The number of variable indexes does not match the number of values\n"
/**
Test if the specified variable should be subject to path validation
*/
static int is_path_variable(const wchar_t *env)
{
return contains(env, L"PATH", L"CDPATH");
}
/**
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++)
{
2012-02-23 04:00:02 +08:00
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))
{
2012-11-19 16:31:03 +08:00
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;
}
2012-11-19 16:31:03 +08:00
case ENV_INVALID:
{
append_format(stderr_buffer, _(L"%ls: Tried to set the special variable '%ls' to an invalid value\n"), L"set", key);
2012-11-19 16:31:03 +08:00
retcode=1;
break;
}
}
return retcode;
}
/**
Extract indexes from a destination argument of the form name[index1 index2...]
\param indexes the list to insert the new indexes into
\param src the source string to parse
\param name the name of the element. Return null if the name in \c src does not match this name
\param var_count the number of elements in the array to parse.
\return the total number of indexes parsed, or -1 on error
*/
static int parse_index(std::vector<long> &indexes,
const wchar_t *src,
const wchar_t *name,
size_t var_count)
{
size_t len;
int count = 0;
const wchar_t *src_orig = src;
if (src == 0)
{
return 0;
}
while (*src != L'\0' && (iswalnum(*src) || *src == L'_'))
{
src++;
}
if (*src != L'[')
{
append_format(stderr_buffer, _(BUILTIN_SET_ARG_COUNT), L"set");
return 0;
}
len = src-src_orig;
if ((wcsncmp(src_orig, name, len)!=0) || (wcslen(name) != (len)))
{
append_format(stderr_buffer,
_(L"%ls: Multiple variable names specified in single call (%ls and %.*ls)\n"),
L"set",
name,
len,
src_orig);
return 0;
}
src++;
while (iswspace(*src))
{
src++;
}
while (*src != L']')
{
wchar_t *end;
long l_ind;
errno = 0;
l_ind = wcstol(src, &end, 10);
if (end==src || errno)
{
append_format(stderr_buffer, _(L"%ls: Invalid index starting at '%ls'\n"), L"set", src);
return 0;
}
if (l_ind < 0)
{
l_ind = var_count+l_ind+1;
}
src = end;
if (*src==L'.' && *(src+1)==L'.')
{
src+=2;
long l_ind2 = wcstol(src, &end, 10);
if (end==src || errno)
{
return 1;
}
src = end;
if (l_ind2 < 0)
{
l_ind2 = var_count+l_ind2+1;
}
int direction = l_ind2<l_ind ? -1 : 1 ;
for (long jjj = l_ind; jjj*direction <= l_ind2*direction; jjj+=direction)
{
// debug(0, L"Expand range [set]: %i\n", jjj);
indexes.push_back(jjj);
count++;
}
}
else
{
indexes.push_back(l_ind);
count++;
}
while (iswspace(*src)) src++;
}
return count;
}
static int update_values(wcstring_list_t &list,
std::vector<long> &indexes,
wcstring_list_t &values)
{
size_t i;
/* Replace values where needed */
for (i = 0; i < indexes.size(); i++)
{
/*
The '- 1' below is because the indices in fish are
one-based, but the vector uses zero-based indices
*/
long ind = indexes[i] - 1;
const wcstring newv = values[ i ];
if (ind < 0)
{
return 1;
}
if ((size_t)ind >= list.size())
Squashed commit of the following: commit 5b7659ec3d5e67b8dad8d3543d87a0169dc9a9e9 Merge: 57f3df3 22a4cd6 Author: ridiculousfish <corydoras@ridiculousfish.com> Date: Thu Jun 21 10:15:41 2012 -0700 Merge branch 'master' of https://github.com/maxfl/fish-shell into maxfl-master-base commit 22a4cd686fbe4a6730859aa1a84b21bc9c832203 Author: maxfl <gmaxfl@gmail.com> Date: Tue Jun 19 15:51:43 2012 +0400 set now expands the variable size, if index is outside it commit 9b0ffa83157ce0cfa36d246fa2e3179d6b790dea Author: maxfl <gmaxfl@gmail.com> Date: Mon Jun 18 21:30:44 2012 +0400 fixes #78 commit 78387fb3915b01342a981059780a164eedc0f8eb Merge: c0e6096 93dc7d4 Author: maxfl <gmaxfl@gmail.com> Date: Mon Jun 18 21:27:47 2012 +0400 Merge remote-tracking branch 'fishfish/master' commit c0e60963c179f80cb1d5d6a18b44510e55c95e10 Merge: 32a98e7 1bead8a Author: maxfl <gmaxfl@gmail.com> Date: Mon Jun 18 10:29:42 2012 +0400 Merge remote-tracking branch 'fishfish/master' commit 32a98e799eb2f016f8bad5287851f6353b835014 Merge: 6e71021 f2b5292 Author: maxfl <gmaxfl@gmail.com> Date: Sat Jun 16 18:42:07 2012 +0400 Merge remote-tracking branch 'fishfish/master' commit 6e710211bca0bca73d738d71e22d20e700db2a63 Author: maxfl <gmaxfl@gmail.com> Date: Thu Jun 14 11:01:13 2012 +0400 revert fish_pager commit 731a29f35bdbaa4dfaad78c7428ab2e5edb45a6c Author: maxfl <gmaxfl@gmail.com> Date: Thu Jun 14 10:57:41 2012 +0400 revert fish_pager.cpp commit 72c1bfc7bfa77bb723d6df3f030a1918db2aca8a Merge: ea74ffa 9b781c4 Author: maxfl <gmaxfl@gmail.com> Date: Wed Jun 13 17:54:11 2012 +0400 Merge branch 'master' into maxfl_completions commit ea74ffa08689a35fd08bc3520a0d52cf30365568 Author: maxfl <gmaxfl@gmail.com> Date: Wed Jun 13 17:35:20 2012 +0400 __fish_complete_command now can understand '--arg=option' tokens latexmk completion is updated commit 45b667826f3f181972e4987a3609d4ccac16a675 Author: maxfl <gmaxfl@gmail.com> Date: Wed Jun 13 16:46:47 2012 +0400 . completion commit 1c9f8ffc9e7ea45eee8aedba5a48e21c75a08882 Author: maxfl <gmaxfl@gmail.com> Date: Wed Jun 13 16:46:13 2012 +0400 a lot of new completions commit 8224d9f984a678fd49cdf78f76770977e0ae257b Author: Maxim Gonchar <gonchar@myhost.localdomain> Date: Tue Jun 12 20:19:31 2012 +0400 A lot of new completions. Some small updates and fixes of old functions and completions. commit 234ed8f5dab209fd657ccaf0dd23d636d2a06355 Author: Maxim Gonchar <gonchar@myhost.localdomain> Date: Tue Jun 12 20:03:44 2012 +0400 step-coloring initial set_color correction
2012-06-22 01:24:49 +08:00
{
list.resize(ind+1);
Squashed commit of the following: commit 5b7659ec3d5e67b8dad8d3543d87a0169dc9a9e9 Merge: 57f3df3 22a4cd6 Author: ridiculousfish <corydoras@ridiculousfish.com> Date: Thu Jun 21 10:15:41 2012 -0700 Merge branch 'master' of https://github.com/maxfl/fish-shell into maxfl-master-base commit 22a4cd686fbe4a6730859aa1a84b21bc9c832203 Author: maxfl <gmaxfl@gmail.com> Date: Tue Jun 19 15:51:43 2012 +0400 set now expands the variable size, if index is outside it commit 9b0ffa83157ce0cfa36d246fa2e3179d6b790dea Author: maxfl <gmaxfl@gmail.com> Date: Mon Jun 18 21:30:44 2012 +0400 fixes #78 commit 78387fb3915b01342a981059780a164eedc0f8eb Merge: c0e6096 93dc7d4 Author: maxfl <gmaxfl@gmail.com> Date: Mon Jun 18 21:27:47 2012 +0400 Merge remote-tracking branch 'fishfish/master' commit c0e60963c179f80cb1d5d6a18b44510e55c95e10 Merge: 32a98e7 1bead8a Author: maxfl <gmaxfl@gmail.com> Date: Mon Jun 18 10:29:42 2012 +0400 Merge remote-tracking branch 'fishfish/master' commit 32a98e799eb2f016f8bad5287851f6353b835014 Merge: 6e71021 f2b5292 Author: maxfl <gmaxfl@gmail.com> Date: Sat Jun 16 18:42:07 2012 +0400 Merge remote-tracking branch 'fishfish/master' commit 6e710211bca0bca73d738d71e22d20e700db2a63 Author: maxfl <gmaxfl@gmail.com> Date: Thu Jun 14 11:01:13 2012 +0400 revert fish_pager commit 731a29f35bdbaa4dfaad78c7428ab2e5edb45a6c Author: maxfl <gmaxfl@gmail.com> Date: Thu Jun 14 10:57:41 2012 +0400 revert fish_pager.cpp commit 72c1bfc7bfa77bb723d6df3f030a1918db2aca8a Merge: ea74ffa 9b781c4 Author: maxfl <gmaxfl@gmail.com> Date: Wed Jun 13 17:54:11 2012 +0400 Merge branch 'master' into maxfl_completions commit ea74ffa08689a35fd08bc3520a0d52cf30365568 Author: maxfl <gmaxfl@gmail.com> Date: Wed Jun 13 17:35:20 2012 +0400 __fish_complete_command now can understand '--arg=option' tokens latexmk completion is updated commit 45b667826f3f181972e4987a3609d4ccac16a675 Author: maxfl <gmaxfl@gmail.com> Date: Wed Jun 13 16:46:47 2012 +0400 . completion commit 1c9f8ffc9e7ea45eee8aedba5a48e21c75a08882 Author: maxfl <gmaxfl@gmail.com> Date: Wed Jun 13 16:46:13 2012 +0400 a lot of new completions commit 8224d9f984a678fd49cdf78f76770977e0ae257b Author: Maxim Gonchar <gonchar@myhost.localdomain> Date: Tue Jun 12 20:19:31 2012 +0400 A lot of new completions. Some small updates and fixes of old functions and completions. commit 234ed8f5dab209fd657ccaf0dd23d636d2a06355 Author: Maxim Gonchar <gonchar@myhost.localdomain> Date: Tue Jun 12 20:03:44 2012 +0400 step-coloring initial set_color correction
2012-06-22 01:24:49 +08:00
}
// free((void *) al_get(list, ind));
list[ ind ] = newv;
}
return 0;
}
/**
Erase from a list of wcstring values at specified indexes
*/
static void erase_values(wcstring_list_t &list, const std::vector<long> &indexes)
{
// Make a set of indexes.
// This both sorts them into ascending order and removes duplicates.
const std::set<long> indexes_set(indexes.begin(), indexes.end());
// Now walk the set backwards, so we encounter larger indexes first, and remove elements at the given (1-based) indexes.
std::set<long>::const_reverse_iterator iter;
for (iter = indexes_set.rbegin(); iter != indexes_set.rend(); ++iter)
{
long val = *iter;
if (val > 0 && (size_t)val <= list.size())
{
// One-based indexing!
list.erase(list.begin() + val - 1);
}
}
}
/**
Print the names of all environment variables in the scope, with or without shortening,
with or without values, with or without escaping
*/
static void print_variables(int include_values, int esc, bool shorten_ok, int scope)
{
wcstring_list_t names = env_get_names(scope);
sort(names.begin(), names.end());
for (size_t i = 0; i < names.size(); i++)
{
const wcstring key = names.at(i);
const wcstring e_key = escape_string(key, 0);
stdout_buffer.append(e_key);
if (include_values)
{
env_var_t value = env_get_string(key, scope);
if (!value.missing())
{
int shorten = 0;
if (shorten_ok && value.length() > 64)
{
shorten = 1;
value.resize(60);
}
wcstring e_value = esc ? expand_escape_variable(value) : value;
stdout_buffer.append(L" ");
stdout_buffer.append(e_value);
if (shorten)
{
stdout_buffer.push_back(ellipsis_char);
}
}
}
stdout_buffer.append(L"\n");
}
}
/**
The set builtin. Creates, updates and erases environment variables
and environemnt variable arrays.
*/
static int builtin_set(parser_t &parser, wchar_t **argv)
{
/** Variables used for parsing the argument list */
const 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' },
{ L"long", no_argument, 0, 'L' },
{ L"query", no_argument, 0, 'q' },
{ L"help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
} ;
const wchar_t *short_options = L"+xglenuULqh";
int argc = builtin_count_args(argv);
/*
Flags to set the work mode
*/
int local = 0, global = 0, exportv = 0;
int erase = 0, list = 0, unexport=0;
int universal = 0, query=0;
bool shorten_ok = true;
bool preserve_incoming_failure_exit_status = true;
const int incoming_exit_status = proc_get_last_status();
/*
Variables used for performing the actual work
*/
wchar_t *dest = 0;
int retcode=0;
int scope;
int slice=0;
int i;
const wchar_t *bad_char = NULL;
/* 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)
{
2012-11-19 16:31:03 +08:00
case 0:
break;
2012-11-19 16:31:03 +08:00
case 'e':
erase = 1;
preserve_incoming_failure_exit_status = false;
2012-11-19 16:31:03 +08:00
break;
2012-11-19 16:31:03 +08:00
case 'n':
list = 1;
preserve_incoming_failure_exit_status = false;
2012-11-19 16:31:03 +08:00
break;
2012-11-19 16:31:03 +08:00
case 'x':
exportv = 1;
break;
2012-11-19 16:31:03 +08:00
case 'l':
local = 1;
break;
2012-11-19 16:31:03 +08:00
case 'g':
global = 1;
break;
2012-11-19 16:31:03 +08:00
case 'u':
unexport = 1;
break;
2012-11-19 16:31:03 +08:00
case 'U':
universal = 1;
break;
2012-11-19 16:31:03 +08:00
case 'L':
shorten_ok = false;
break;
2012-11-19 16:31:03 +08:00
case 'q':
query = 1;
preserve_incoming_failure_exit_status = false;
2012-11-19 16:31:03 +08:00
break;
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, argv[0], stdout_buffer);
return 0;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, argv[0], argv[woptind-1]);
return 1;
2012-11-19 16:31:03 +08:00
default:
break;
}
}
/*
Ok, all arguments have been parsed, let's validate them
*/
/*
If we are checking the existance of a variable (-q) we can not
also specify scope
*/
if (query && (erase || list))
{
append_format(stderr_buffer,
BUILTIN_ERR_COMBO,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/* We can't both list and erase variables */
if (erase && list)
{
append_format(stderr_buffer,
BUILTIN_ERR_COMBO,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/*
Variables can only have one scope
*/
if (local + global + universal > 1)
{
append_format(stderr_buffer,
BUILTIN_ERR_GLOCAL,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/*
Variables can only have one export status
*/
if (exportv && unexport)
{
append_format(stderr_buffer,
BUILTIN_ERR_EXPUNEXP,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/*
Calculate the scope value for variable assignement
*/
scope = (local ? ENV_LOCAL : 0) | (global ? ENV_GLOBAL : 0) | (exportv ? ENV_EXPORT : 0) | (unexport ? ENV_UNEXPORT : 0) | (universal ? ENV_UNIVERSAL:0) | ENV_USER;
if (query)
{
/*
Query mode. Return the number of variables that do not exist
out of the specified variables.
*/
int i;
for (i=woptind; i<argc; i++)
{
wchar_t *arg = argv[i];
int slice=0;
if (!(dest = wcsdup(arg)))
{
DIE_MEM();
}
if (wcschr(dest, L'['))
{
slice = 1;
*wcschr(dest, L'[')=0;
}
if (slice)
{
std::vector<long> indexes;
wcstring_list_t result;
size_t j;
env_var_t dest_str = env_get_string(dest, scope);
if (! dest_str.missing())
tokenize_variable_array(dest_str, result);
if (!parse_index(indexes, arg, dest, result.size()))
{
builtin_print_help(parser, argv[0], stderr_buffer);
retcode = 1;
break;
}
for (j=0; j < indexes.size() ; j++)
{
long idx = indexes[j];
if (idx < 1 || (size_t)idx > result.size())
{
retcode++;
}
}
}
else
{
if (!env_exist(arg, scope))
{
retcode++;
}
}
free(dest);
}
return retcode;
}
if (list)
{
/* Maybe we should issue an error if there are any other arguments? */
print_variables(0, 0, shorten_ok, scope);
return 0;
}
if (woptind == argc)
{
/*
Print values of variables
*/
if (erase)
{
append_format(stderr_buffer,
_(L"%ls: Erase needs a variable name\n"),
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
retcode = 1;
}
else
{
print_variables(1, 1, shorten_ok, scope);
}
return retcode;
}
if (!(dest = wcsdup(argv[woptind])))
{
DIE_MEM();
}
if (wcschr(dest, L'['))
{
slice = 1;
*wcschr(dest, L'[')=0;
}
if (!wcslen(dest))
{
free(dest);
append_format(stderr_buffer, BUILTIN_ERR_VARNAME_ZERO, argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
if ((bad_char = wcsvarname(dest)))
{
append_format(stderr_buffer, BUILTIN_ERR_VARCHAR, argv[0], *bad_char);
builtin_print_help(parser, argv[0], stderr_buffer);
free(dest);
return 1;
}
/*
set assignment can work in two modes, either using slices or
using the whole array. We detect which mode is used here.
*/
if (slice)
{
/*
Slice mode
*/
std::vector<long> indexes;
wcstring_list_t result;
const env_var_t dest_str = env_get_string(dest, scope);
if (! dest_str.missing())
{
tokenize_variable_array(dest_str, result);
}
else if (erase)
{
retcode = 1;
}
if (!retcode)
{
for (; woptind<argc; woptind++)
{
if (!parse_index(indexes, argv[woptind], dest, result.size()))
{
builtin_print_help(parser, argv[0], stderr_buffer);
retcode = 1;
break;
}
size_t idx_count = indexes.size();
size_t val_count = argc-woptind-1;
if (!erase)
{
if (val_count < idx_count)
{
append_format(stderr_buffer, _(BUILTIN_SET_ARG_COUNT), argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
retcode=1;
break;
}
if (val_count == idx_count)
{
woptind++;
break;
}
}
}
}
if (!retcode)
{
/*
Slice indexes have been calculated, do the actual work
*/
if (erase)
{
erase_values(result, indexes);
my_env_set(dest, result, scope);
}
else
{
wcstring_list_t value;
while (woptind < argc)
{
value.push_back(argv[woptind++]);
}
if (update_values(result,
indexes,
value))
{
append_format(stderr_buffer, L"%ls: ", argv[0]);
append_format(stderr_buffer, ARRAY_BOUNDS_ERR);
stderr_buffer.push_back(L'\n');
}
my_env_set(dest, result, scope);
}
}
}
else
{
woptind++;
/*
No slicing
*/
if (erase)
{
if (woptind != argc)
{
append_format(stderr_buffer,
_(L"%ls: Values cannot be specfied with erase\n"),
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
retcode=1;
}
else
{
retcode = env_remove(dest, scope);
}
}
else
{
wcstring_list_t val;
for (i=woptind; i<argc; i++)
val.push_back(argv[i]);
retcode = my_env_set(dest, val, scope);
}
}
/* Check if we are setting variables above the effective scope.
See https://github.com/fish-shell/fish-shell/issues/806
*/
env_var_t global_dest = env_get_string(dest, ENV_GLOBAL);
if (universal && ! global_dest.missing())
{
append_format(stderr_buffer, _(L"%ls: Warning: universal scope selected, but a global variable %ls exists.\n"), L"set", dest);
}
free(dest);
if (retcode == STATUS_BUILTIN_OK && preserve_incoming_failure_exit_status)
retcode = incoming_exit_status;
return retcode;
}