Merge branch 'fix_set_scopes' of github.com:kballard/fish-shell into kballard-fix_set_scopes

This commit is contained in:
ridiculousfish 2014-07-13 14:12:51 -07:00
commit b667eee351
4 changed files with 188 additions and 152 deletions

View file

@ -69,7 +69,7 @@ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope)
/* Don't bother validating (or complaining about) values that are already present */ /* Don't bother validating (or complaining about) values that are already present */
wcstring_list_t existing_values; wcstring_list_t existing_values;
const env_var_t existing_variable = env_get_string(key); const env_var_t existing_variable = env_get_string(key, scope);
if (! existing_variable.missing_or_empty()) if (! existing_variable.missing_or_empty())
tokenize_variable_array(existing_variable, existing_values); tokenize_variable_array(existing_variable, existing_values);
@ -157,6 +157,13 @@ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope)
break; 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: case ENV_INVALID:
{ {
append_format(stderr_buffer, _(L"%ls: Unknown error"), L"set"); append_format(stderr_buffer, _(L"%ls: Unknown error"), L"set");
@ -353,7 +360,7 @@ static void print_variables(int include_values, int esc, bool shorten_ok, int sc
if (include_values) if (include_values)
{ {
env_var_t value = env_get_string(key); env_var_t value = env_get_string(key, scope);
if (!value.missing()) if (!value.missing())
{ {
int shorten = 0; int shorten = 0;
@ -519,7 +526,7 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
} }
/* We can't both list and erase varaibles */ /* We can't both list and erase variables */
if (erase && list) if (erase && list)
{ {
append_format(stderr_buffer, append_format(stderr_buffer,
@ -588,7 +595,7 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
wcstring_list_t result; wcstring_list_t result;
size_t j; size_t j;
env_var_t dest_str = env_get_string(dest); env_var_t dest_str = env_get_string(dest, scope);
if (! dest_str.missing()) if (! dest_str.missing())
tokenize_variable_array(dest_str, result); tokenize_variable_array(dest_str, result);
@ -678,14 +685,6 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
return 1; return 1;
} }
if (slice && erase && (scope != ENV_USER))
{
free(dest);
append_format(stderr_buffer, _(L"%ls: Can not specify scope when erasing array slice\n"), argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/* /*
set assignment can work in two modes, either using slices or set assignment can work in two modes, either using slices or
using the whole array. We detect which mode is used here. using the whole array. We detect which mode is used here.
@ -700,35 +699,44 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
std::vector<long> indexes; std::vector<long> indexes;
wcstring_list_t result; wcstring_list_t result;
const env_var_t dest_str = env_get_string(dest); const env_var_t dest_str = env_get_string(dest, scope);
if (! dest_str.missing()) if (! dest_str.missing())
tokenize_variable_array(dest_str, result);
for (; woptind<argc; woptind++)
{ {
if (!parse_index(indexes, argv[woptind], dest, result.size())) tokenize_variable_array(dest_str, result);
{ }
builtin_print_help(parser, argv[0], stderr_buffer); else if (erase)
retcode = 1; {
break; retcode = 1;
} }
size_t idx_count = indexes.size(); if (!retcode)
size_t val_count = argc-woptind-1; {
for (; woptind<argc; woptind++)
if (!erase)
{ {
if (val_count < idx_count) if (!parse_index(indexes, argv[woptind], dest, result.size()))
{ {
append_format(stderr_buffer, _(BUILTIN_SET_ARG_COUNT), argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer); builtin_print_help(parser, argv[0], stderr_buffer);
retcode=1; retcode = 1;
break; break;
} }
if (val_count == idx_count)
size_t idx_count = indexes.size();
size_t val_count = argc-woptind-1;
if (!erase)
{ {
woptind++; if (val_count < idx_count)
break; {
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;
}
} }
} }
} }

View file

@ -27,13 +27,13 @@ The following options control variable scope:
- <code>-l</code> or <code>--local</code> forces the specified shell variable to be given a scope that is local to the current block, even if a variable with the given name exists and is non-local - <code>-l</code> or <code>--local</code> forces the specified shell variable to be given a scope that is local to the current block, even if a variable with the given name exists and is non-local
- <code>-g</code> or <code>--global</code> causes the specified shell variable to be given a global scope. Non-global variables disappear when the block they belong to ends - <code>-g</code> or <code>--global</code> causes the specified shell variable to be given a global scope. Non-global variables disappear when the block they belong to ends
- <code>-U</code> or <code>--universal</code> causes the specified shell variable to be given a universal scope. If this option is supplied, the variable will be shared between all the current users fish instances on the current computer, and will be preserved across restarts of the shell. - <code>-U</code> or <code>--universal</code> causes the specified shell variable to be given a universal scope. If this option is supplied, the variable will be shared between all the current users fish instances on the current computer, and will be preserved across restarts of the shell.
- <code>-n</code> or <code>--names</code> List only the names of all defined variables, not their value
- <code>-x</code> or <code>--export</code> causes the specified shell variable to be exported to child processes (making it an "environment variable") - <code>-x</code> or <code>--export</code> causes the specified shell variable to be exported to child processes (making it an "environment variable")
- <code>-u</code> or <code>--unexport</code> causes the specified shell variable to NOT be exported to child processes - <code>-u</code> or <code>--unexport</code> causes the specified shell variable to NOT be exported to child processes
The following options are available: The following options are available:
- <code>-e</code> or <code>--erase</code> causes the specified shell variable to be erased - <code>-e</code> or <code>--erase</code> causes the specified shell variable to be erased
- <code>-q</code> or <code>--query</code> test if the specified variable names are defined. Does not output anything, but the builtins exit status is the number of variables specified that were not defined. - <code>-q</code> or <code>--query</code> test if the specified variable names are defined. Does not output anything, but the builtins exit status is the number of variables specified that were not defined.
- <code>-n</code> or <code>--names</code> List only the names of all defined variables, not their value
- <code>-L</code> or <code>--long</code> do not abbreviate long values when printing set variables - <code>-L</code> or <code>--long</code> do not abbreviate long values when printing set variables
If a variable is set to more than one value, the variable will be an If a variable is set to more than one value, the variable will be an
@ -65,11 +65,7 @@ to the scoping rules for variables:
In query mode, the scope to be examined can be specified. In query mode, the scope to be examined can be specified.
In erase mode, if variable indices are specified, only the specified In erase mode, if variable indices are specified, only the specified
slices of the array variable will be erased. When erasing an entire slices of the array variable will be erased.
variable (i.e. no slicing), the scope of the variable to be erased can
be specified. That way, a global variable can be erased even if a
local variable with the same name exists. Scope can not be specified
when erasing a slice of an array. The innermost scope is always used.
\c set requires all options to come before any \c set requires all options to come before any
other arguments. For example, <code>set flags -l</code> will have other arguments. For example, <code>set flags -l</code> will have

249
env.cpp
View file

@ -490,6 +490,8 @@ void env_init(const struct config_paths_t *paths /* or NULL */)
env_electric.insert(L"history"); env_electric.insert(L"history");
env_electric.insert(L"status"); env_electric.insert(L"status");
env_electric.insert(L"umask"); env_electric.insert(L"umask");
env_electric.insert(L"COLUMNS");
env_electric.insert(L"LINES");
top = new env_node_t; top = new env_node_t;
global_env = top; global_env = top;
@ -510,11 +512,13 @@ void env_init(const struct config_paths_t *paths /* or NULL */)
if (eql == wcstring::npos) if (eql == wcstring::npos)
{ {
// no equals found // no equals found
env_set(key_and_val, L"", ENV_EXPORT); if (is_read_only(key_and_val) || is_electric(key_and_val)) continue;
env_set(key_and_val, L"", ENV_EXPORT | ENV_GLOBAL);
} }
else else
{ {
wcstring key = key_and_val.substr(0, eql); wcstring key = key_and_val.substr(0, eql);
if (is_read_only(key) || is_electric(key)) continue;
wcstring val = key_and_val.substr(eql + 1); wcstring val = key_and_val.substr(eql + 1);
if (variable_can_be_array(val)) if (variable_can_be_array(val))
{ {
@ -589,6 +593,14 @@ void env_init(const struct config_paths_t *paths /* or NULL */)
/* Set fish_bind_mode to "default" */ /* Set fish_bind_mode to "default" */
env_set(FISH_BIND_MODE_VAR, DEFAULT_BIND_MODE, ENV_GLOBAL); env_set(FISH_BIND_MODE_VAR, DEFAULT_BIND_MODE, ENV_GLOBAL);
/*
Now that the global scope is fully initialized, add a toplevel local
scope. This same local scope will persist throughout the lifetime of the
fish process, and it will ensure that `set -l` commands run at the
command-line don't affect the global scope.
*/
env_push(false);
} }
/** /**
@ -630,6 +642,15 @@ int env_set(const wcstring &key, const wchar_t *val, int var_mode)
} }
} }
if ((var_mode & (ENV_LOCAL | ENV_UNIVERSAL)) && (is_read_only(key) || is_electric(key)))
{
return ENV_SCOPE;
}
if ((var_mode & ENV_EXPORT) && is_electric(key))
{
return ENV_SCOPE;
}
if ((var_mode & ENV_USER) && is_read_only(key)) if ((var_mode & ENV_USER) && is_read_only(key))
{ {
return ENV_PERM; return ENV_PERM;
@ -923,82 +944,107 @@ const wchar_t *env_var_t::c_str(void) const
return wcstring::c_str(); return wcstring::c_str();
} }
env_var_t env_get_string(const wcstring &key) env_var_t env_get_string(const wcstring &key, int mode)
{ {
/* Big hack...we only allow getting the history on the main thread. Note that history_t may ask for an environment variable, so don't take the lock here (we don't need it) */ const bool has_scope = mode & (ENV_LOCAL | ENV_GLOBAL | ENV_UNIVERSAL);
const bool is_main = is_main_thread(); const bool search_local = !has_scope || (mode & ENV_LOCAL);
if (key == L"history" && is_main) const bool search_global = !has_scope || (mode & ENV_GLOBAL);
{ const bool search_universal = !has_scope || (mode & ENV_UNIVERSAL);
env_var_t result;
history_t *history = reader_get_history(); const bool search_exported = (mode & ENV_EXPORT) || !(mode & ENV_UNEXPORT);
if (! history) const bool search_unexported = (mode & ENV_UNEXPORT) || !(mode & ENV_EXPORT);
/* Make the assumption that electric keys can't be shadowed elsewhere, since we currently block that in env_set() */
if (is_electric(key))
{
if (!search_global) return env_var_t::missing_var();
/* Big hack...we only allow getting the history on the main thread. Note that history_t may ask for an environment variable, so don't take the lock here (we don't need it) */
if (key == L"history" && is_main_thread())
{ {
history = &history_t::history_with_name(L"fish"); env_var_t result;
}
if (history)
history->get_string_representation(result, ARRAY_SEP_STR);
return result;
}
else if (key == L"COLUMNS")
{
return to_string(common_get_width());
}
else if (key == L"LINES")
{
return to_string(common_get_height());
}
else if (key == L"status")
{
return to_string(proc_get_last_status());
}
else if (key == L"umask")
{
return format_string(L"0%0.3o", get_umask());
}
else
{
{
/* Lock around a local region */
scoped_lock lock(env_lock);
env_node_t *env = top; history_t *history = reader_get_history();
wcstring result; if (! history)
while (env != NULL)
{ {
const var_entry_t *entry = env->find_entry(key); history = &history_t::history_with_name(L"fish");
if (entry != NULL) }
{ if (history)
if (entry->val == ENV_NULL) history->get_string_representation(result, ARRAY_SEP_STR);
{ return result;
return env_var_t::missing_var(); }
} else if (key == L"COLUMNS")
else {
{ return to_string(common_get_width());
return entry->val; }
} else if (key == L"LINES")
} {
return to_string(common_get_height());
}
else if (key == L"status")
{
return to_string(proc_get_last_status());
}
else if (key == L"umask")
{
return format_string(L"0%0.3o", get_umask());
}
// we should never get here unless the electric var list is out of sync
}
if (search_local || search_global) {
/* Lock around a local region */
scoped_lock lock(env_lock);
env_node_t *env = search_local ? top : global_env;
wcstring result;
while (env != NULL)
{
const var_entry_t *entry = env->find_entry(key);
if (entry != NULL && (entry->exportv ? search_exported : search_unexported))
{
if (entry->val == ENV_NULL)
{
return env_var_t::missing_var();
}
else
{
return entry->val;
}
}
if (has_scope)
{
if (!search_global || env == global_env) break;
env = global_env;
}
else
{
env = env->next_scope_to_search(); env = env->next_scope_to_search();
} }
} }
}
/* Another big hack - only do a universal barrier on the main thread (since it can change variable values) if (!search_universal) return env_var_t::missing_var();
Make sure we do this outside the env_lock because it may itself call env_get_string */
if (is_main && ! get_proc_had_barrier())
{
set_proc_had_barrier(true);
env_universal_barrier();
}
env_var_t env_var = uvars() ? uvars()->get(key) : env_var_t::missing_var(); /* Another big hack - only do a universal barrier on the main thread (since it can change variable values)
if (env_var == ENV_NULL) Make sure we do this outside the env_lock because it may itself call env_get_string */
if (is_main_thread() && ! get_proc_had_barrier())
{
set_proc_had_barrier(true);
env_universal_barrier();
}
if (uvars())
{
env_var_t env_var = uvars()->get(key);
if (env_var == ENV_NULL || !(uvars()->get_export(key) ? search_exported : search_unexported))
{ {
env_var = env_var_t::missing_var(); env_var = env_var_t::missing_var();
} }
return env_var; return env_var;
} }
return env_var_t::missing_var();
} }
bool env_exist(const wchar_t *key, int mode) bool env_exist(const wchar_t *key, int mode)
@ -1007,59 +1053,54 @@ bool env_exist(const wchar_t *key, int mode)
CHECK(key, false); CHECK(key, false);
/* const bool has_scope = mode & (ENV_LOCAL | ENV_GLOBAL | ENV_UNIVERSAL);
Read only variables all exist, and they are all global. A local const bool test_local = !has_scope || (mode & ENV_LOCAL);
version can not exist. const bool test_global = !has_scope || (mode & ENV_GLOBAL);
*/ const bool test_universal = !has_scope || (mode & ENV_UNIVERSAL);
if (!(mode & ENV_LOCAL) && !(mode & ENV_UNIVERSAL))
const bool test_exported = (mode & ENV_EXPORT) || !(mode & ENV_UNEXPORT);
const bool test_unexported = (mode & ENV_UNEXPORT) || !(mode & ENV_EXPORT);
if (is_electric(key))
{ {
if (is_read_only(key) || is_electric(key)) /*
Electric variables all exist, and they are all global. A local or
universal version can not exist. They are also never exported.
*/
if (test_global && test_unexported)
{ {
//Such variables are never exported
if (mode & ENV_EXPORT)
{
return false;
}
else if (mode & ENV_UNEXPORT)
{
return true;
}
return true; return true;
} }
return false;
} }
if (!(mode & ENV_UNIVERSAL)) if (test_local || test_global)
{ {
env = (mode & ENV_GLOBAL)?global_env:top; env = test_local ? top : global_env;
while (env != 0) while (env)
{ {
var_table_t::iterator result = env->env.find(key); var_table_t::iterator result = env->env.find(key);
if (result != env->env.end()) if (result != env->env.end())
{ {
const var_entry_t &res = result->second; const var_entry_t &res = result->second;
return res.exportv ? test_exported : test_unexported;
if (mode & ENV_EXPORT)
{
return res.exportv;
}
else if (mode & ENV_UNEXPORT)
{
return ! res.exportv;
}
return true;
} }
if (mode & ENV_LOCAL) if (has_scope)
break; {
if (!test_global || env == global_env) break;
env = env->next_scope_to_search(); env = global_env;
}
else
{
env = env->next_scope_to_search();
}
} }
} }
if (!(mode & ENV_LOCAL) && !(mode & ENV_GLOBAL)) if (test_universal)
{ {
if (! get_proc_had_barrier()) if (! get_proc_had_barrier())
{ {
@ -1069,16 +1110,7 @@ bool env_exist(const wchar_t *key, int mode)
if (uvars() && ! uvars()->get(key).missing()) if (uvars() && ! uvars()->get(key).missing())
{ {
if (mode & ENV_EXPORT) return uvars()->get_export(key) ? test_exported : test_unexported;
{
return uvars()->get_export(key);
}
else if (mode & ENV_UNEXPORT)
{
return ! uvars()->get_export(key);
}
return 1;
} }
} }
@ -1233,13 +1265,6 @@ wcstring_list_t env_get_names(int flags)
{ {
result.insert(result.end(), env_electric.begin(), env_electric.end()); result.insert(result.end(), env_electric.begin(), env_electric.end());
} }
if (show_exported)
{
result.push_back(L"COLUMNS");
result.push_back(L"LINES");
}
} }
if (show_universal && uvars()) if (show_universal && uvars())

11
env.h
View file

@ -49,6 +49,7 @@
enum enum
{ {
ENV_PERM = 1, ENV_PERM = 1,
ENV_SCOPE,
ENV_INVALID ENV_INVALID
} }
; ;
@ -82,6 +83,7 @@ void env_init(const struct config_paths_t *paths = NULL);
The current error codes are: The current error codes are:
* ENV_PERM, can only be returned when setting as a user, e.g. ENV_USER is set. This means that the user tried to change a read-only variable. * ENV_PERM, can only be returned when setting as a user, e.g. ENV_USER is set. This means that the user tried to change a read-only variable.
* ENV_SCOPE, the variable cannot be set in the given scope. This applies to readonly/electric variables set from the local or universal scopes, or set as exported.
* ENV_INVALID, the variable name or mode was invalid * ENV_INVALID, the variable name or mode was invalid
*/ */
@ -167,8 +169,13 @@ public:
}; };
/** Gets the variable with the specified name, or env_var_t::missing_var if it does not exist. */ /**
env_var_t env_get_string(const wcstring &key); Gets the variable with the specified name, or env_var_t::missing_var if it does not exist.
\param key The name of the variable to get
\param mode An optional scope to search in. All scopes are searched if unset
*/
env_var_t env_get_string(const wcstring &key, int mode = 0);
/** /**
Returns true if the specified key exists. This can't be reliably done Returns true if the specified key exists. This can't be reliably done