mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 12:53:13 +00:00
Move autoload class into autoload.h and cpp
This commit is contained in:
parent
d6545588a3
commit
843ba4ac2c
4 changed files with 327 additions and 321 deletions
219
autoload.cpp
219
autoload.cpp
|
@ -6,6 +6,11 @@ The classes responsible for autoloading functions and completions.
|
|||
#include "config.h"
|
||||
#include "autoload.h"
|
||||
#include "wutil.h"
|
||||
#include "common.h"
|
||||
#include "signal.h"
|
||||
#include "env.h"
|
||||
#include "builtin_scripts.h"
|
||||
#include "exec.h"
|
||||
#include <assert.h>
|
||||
|
||||
static const size_t kLRULimit = 16;
|
||||
|
@ -131,3 +136,217 @@ void lru_cache_impl_t::evict_all_nodes() {
|
|||
evict_last_node();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
autoload_t::autoload_t(const wcstring &env_var_name_var, const builtin_script_t * const scripts, size_t script_count) :
|
||||
env_var_name(env_var_name_var),
|
||||
builtin_scripts(scripts),
|
||||
builtin_script_count(script_count)
|
||||
{
|
||||
}
|
||||
|
||||
void autoload_t::node_was_evicted(autoload_function_t *node) {
|
||||
// Tell ourselves that the command was removed, unless it was a placeholder
|
||||
if (! node->is_placeholder)
|
||||
this->command_removed(node->key);
|
||||
delete node;
|
||||
}
|
||||
|
||||
void autoload_t::reset( )
|
||||
{
|
||||
this->evict_all_nodes();
|
||||
}
|
||||
|
||||
int autoload_t::unload( const wcstring &cmd )
|
||||
{
|
||||
return this->evict_node(cmd);
|
||||
}
|
||||
|
||||
int autoload_t::load( const wcstring &cmd, bool reload )
|
||||
{
|
||||
int res;
|
||||
int c, c2;
|
||||
|
||||
CHECK_BLOCK( 0 );
|
||||
|
||||
const env_var_t path_var = env_get_string( env_var_name.c_str() );
|
||||
|
||||
/*
|
||||
Do we know where to look?
|
||||
*/
|
||||
if( path_var.empty() )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Check if the lookup path has changed. If so, drop all loaded
|
||||
files.
|
||||
*/
|
||||
if( path_var != this->path )
|
||||
{
|
||||
this->path = path_var;
|
||||
this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
Warn and fail on infinite recursion
|
||||
*/
|
||||
if (this->is_loading(cmd))
|
||||
{
|
||||
debug( 0,
|
||||
_( L"Could not autoload item '%ls', it is already being autoloaded. "
|
||||
L"This is a circular dependency in the autoloading scripts, please remove it."),
|
||||
cmd.c_str() );
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::vector<wcstring> path_list;
|
||||
tokenize_variable_array2( path_var, path_list );
|
||||
|
||||
c = path_list.size();
|
||||
|
||||
is_loading_set.insert(cmd);
|
||||
|
||||
/*
|
||||
Do the actual work in the internal helper function
|
||||
*/
|
||||
res = this->load_internal( cmd, reload, path_list );
|
||||
|
||||
int erased = is_loading_set.erase(cmd);
|
||||
assert(erased);
|
||||
|
||||
c2 = path_list.size();
|
||||
|
||||
/**
|
||||
Make sure we didn't 'drop' something
|
||||
*/
|
||||
|
||||
assert( c == c2 );
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool script_name_precedes_script_name(const builtin_script_t &script1, const builtin_script_t &script2)
|
||||
{
|
||||
return wcscmp(script1.name, script2.name) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
|
||||
int autoload_t::load_internal( const wcstring &cmd,
|
||||
int reload,
|
||||
const wcstring_list_t &path_list )
|
||||
{
|
||||
|
||||
size_t i;
|
||||
int reloaded = 0;
|
||||
|
||||
/* Get the function */
|
||||
autoload_function_t * func = this->get_function_with_name(cmd);
|
||||
|
||||
/* Return if already loaded and we are skipping reloading */
|
||||
if( !reload && func )
|
||||
return 0;
|
||||
|
||||
/* Nothing to do if we just checked it */
|
||||
if (func && time(NULL) - func->access.last_checked <= 1)
|
||||
return 0;
|
||||
|
||||
/* The source of the script will end up here */
|
||||
wcstring script_source;
|
||||
bool has_script_source = 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);
|
||||
}
|
||||
|
||||
if (! has_script_source)
|
||||
{
|
||||
/*
|
||||
Iterate over path searching for suitable completion files
|
||||
*/
|
||||
for( 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) {
|
||||
if (! func || access.mod_time != func->access.mod_time) {
|
||||
wcstring esc = escape_string(path, 1);
|
||||
script_source = L". " + esc;
|
||||
has_script_source = true;
|
||||
|
||||
if( !func )
|
||||
func = new autoload_function_t(cmd);
|
||||
func->access = access;
|
||||
|
||||
// Remove this command because we are going to reload it
|
||||
command_removed(cmd);
|
||||
|
||||
reloaded = 1;
|
||||
}
|
||||
else if( func )
|
||||
{
|
||||
/*
|
||||
If we are rechecking an autoload file, and it hasn't
|
||||
changed, update the 'last check' timestamp.
|
||||
*/
|
||||
func->access = access;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
If no file 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( !func )
|
||||
{
|
||||
func = new autoload_function_t(cmd);
|
||||
func->access.last_checked = time(NULL);
|
||||
func->is_placeholder = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have a script, either built-in or a file source, then run it */
|
||||
if (has_script_source)
|
||||
{
|
||||
if( exec_subshell( script_source.c_str(), 0 ) == -1 )
|
||||
{
|
||||
/*
|
||||
Do nothing on failiure
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return reloaded;
|
||||
}
|
||||
|
|
108
autoload.h
108
autoload.h
|
@ -92,4 +92,112 @@ protected:
|
|||
};
|
||||
|
||||
|
||||
struct autoload_function_t : public lru_node_t
|
||||
{
|
||||
autoload_function_t(const wcstring &key) : lru_node_t(key), is_placeholder(false) { bzero(&access, sizeof access); }
|
||||
file_access_attempt_t access; /** The last access attempt */
|
||||
bool is_placeholder; /** Whether we are a placeholder that stands in for "no such function" */
|
||||
};
|
||||
|
||||
|
||||
struct builtin_script_t;
|
||||
|
||||
/**
|
||||
A class that represents a path from which we can autoload, and the autoloaded contents.
|
||||
*/
|
||||
class autoload_t : private lru_cache_t<autoload_function_t> {
|
||||
private:
|
||||
|
||||
/** The environment variable name */
|
||||
const wcstring env_var_name;
|
||||
|
||||
/** Builtin script array */
|
||||
const struct builtin_script_t *const builtin_scripts;
|
||||
|
||||
/** Builtin script count */
|
||||
const size_t builtin_script_count;
|
||||
|
||||
/** The path from which we most recently autoloaded */
|
||||
wcstring path;
|
||||
|
||||
/** The map from function name to the function. */
|
||||
typedef std::map<wcstring, autoload_function_t> autoload_functions_map_t;
|
||||
autoload_functions_map_t autoload_functions;
|
||||
|
||||
/**
|
||||
A table containing all the files that are currently being
|
||||
loaded. This is here to help prevent recursion.
|
||||
*/
|
||||
std::set<wcstring> is_loading_set;
|
||||
|
||||
bool is_loading(const wcstring &name) const {
|
||||
return is_loading_set.find(name) != is_loading_set.end();
|
||||
}
|
||||
|
||||
bool remove_function_with_name(const wcstring &name) {
|
||||
return autoload_functions.erase(name) > 0;
|
||||
}
|
||||
|
||||
autoload_function_t *get_function_with_name(const wcstring &name)
|
||||
{
|
||||
autoload_function_t *result = NULL;
|
||||
autoload_functions_map_t::iterator iter = autoload_functions.find(name);
|
||||
if (iter != autoload_functions.end())
|
||||
result = &iter->second;
|
||||
return result;
|
||||
}
|
||||
|
||||
void remove_all_functions(void) {
|
||||
autoload_functions.clear();
|
||||
}
|
||||
|
||||
size_t function_count(void) const {
|
||||
return autoload_functions.size();
|
||||
}
|
||||
|
||||
int load_internal( const wcstring &cmd, int reload, const wcstring_list_t &path_list );
|
||||
|
||||
virtual void node_was_evicted(autoload_function_t *node);
|
||||
|
||||
|
||||
protected:
|
||||
/** Overridable callback for when a command is removed */
|
||||
virtual void command_removed(const wcstring &cmd) { }
|
||||
|
||||
public:
|
||||
|
||||
/** Create an autoload_t for the given environment variable name */
|
||||
autoload_t(const wcstring &env_var_name_var, const builtin_script_t *scripts, size_t script_count );
|
||||
|
||||
/**
|
||||
Autoload the specified file, if it exists in the specified path. Do
|
||||
not load it multiple times unless it's timestamp changes or
|
||||
parse_util_unload is called.
|
||||
|
||||
Autoloading one file may unload another.
|
||||
|
||||
\param cmd the filename to search for. The suffix '.fish' is always added to this name
|
||||
\param on_unload a callback function to run if a suitable file is found, which has not already been run. unload will also be called for old files which are unloaded.
|
||||
\param reload wheter to recheck file timestamps on already loaded files
|
||||
*/
|
||||
int load( const wcstring &cmd, bool reload );
|
||||
/**
|
||||
Reset the loader for the specified path variable. This will cause
|
||||
all information on loaded files in the specified directory to be
|
||||
reset.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
Tell the autoloader that the specified file, in the specified path,
|
||||
is no longer loaded.
|
||||
|
||||
\param cmd the filename to search for. The suffix '.fish' is always added to this name
|
||||
\param on_unload a callback function which will be called before (re)loading a file, may be used to unload the previous file.
|
||||
\return non-zero if the file was removed, zero if the file had not yet been loaded
|
||||
*/
|
||||
int unload( const wcstring &cmd );
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
213
parse_util.cpp
213
parse_util.cpp
|
@ -39,7 +39,6 @@
|
|||
#include "signal.h"
|
||||
#include "wildcard.h"
|
||||
#include "halloc_util.h"
|
||||
#include "builtin_scripts.h"
|
||||
|
||||
/**
|
||||
Maximum number of autoloaded items opf a specific type to keep in
|
||||
|
@ -601,218 +600,6 @@ void parse_util_token_extent( const wchar_t *buff,
|
|||
|
||||
}
|
||||
|
||||
autoload_t::autoload_t(const wcstring &env_var_name_var, const builtin_script_t * const scripts, size_t script_count) :
|
||||
env_var_name(env_var_name_var),
|
||||
builtin_scripts(scripts),
|
||||
builtin_script_count(script_count)
|
||||
{
|
||||
}
|
||||
|
||||
void autoload_t::node_was_evicted(autoload_function_t *node) {
|
||||
// Tell ourselves that the command was removed, unless it was a placeholder
|
||||
if (! node->is_placeholder)
|
||||
this->command_removed(node->key);
|
||||
delete node;
|
||||
}
|
||||
|
||||
void autoload_t::reset( )
|
||||
{
|
||||
this->evict_all_nodes();
|
||||
}
|
||||
|
||||
int autoload_t::unload( const wcstring &cmd )
|
||||
{
|
||||
return this->evict_node(cmd);
|
||||
}
|
||||
|
||||
int autoload_t::load( const wcstring &cmd, bool reload )
|
||||
{
|
||||
int res;
|
||||
int c, c2;
|
||||
|
||||
CHECK_BLOCK( 0 );
|
||||
|
||||
const env_var_t path_var = env_get_string( env_var_name.c_str() );
|
||||
|
||||
/*
|
||||
Do we know where to look?
|
||||
*/
|
||||
if( path_var.empty() )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Check if the lookup path has changed. If so, drop all loaded
|
||||
files.
|
||||
*/
|
||||
if( path_var != this->path )
|
||||
{
|
||||
this->path = path_var;
|
||||
this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
Warn and fail on infinite recursion
|
||||
*/
|
||||
if (this->is_loading(cmd))
|
||||
{
|
||||
debug( 0,
|
||||
_( L"Could not autoload item '%ls', it is already being autoloaded. "
|
||||
L"This is a circular dependency in the autoloading scripts, please remove it."),
|
||||
cmd.c_str() );
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::vector<wcstring> path_list;
|
||||
tokenize_variable_array2( path_var, path_list );
|
||||
|
||||
c = path_list.size();
|
||||
|
||||
is_loading_set.insert(cmd);
|
||||
|
||||
/*
|
||||
Do the actual work in the internal helper function
|
||||
*/
|
||||
res = this->load_internal( cmd, reload, path_list );
|
||||
|
||||
int erased = is_loading_set.erase(cmd);
|
||||
assert(erased);
|
||||
|
||||
c2 = path_list.size();
|
||||
|
||||
/**
|
||||
Make sure we didn't 'drop' something
|
||||
*/
|
||||
|
||||
assert( c == c2 );
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool script_name_precedes_script_name(const builtin_script_t &script1, const builtin_script_t &script2)
|
||||
{
|
||||
return wcscmp(script1.name, script2.name) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
|
||||
int autoload_t::load_internal( const wcstring &cmd,
|
||||
int reload,
|
||||
const wcstring_list_t &path_list )
|
||||
{
|
||||
|
||||
size_t i;
|
||||
int reloaded = 0;
|
||||
|
||||
/* Get the function */
|
||||
autoload_function_t * func = this->get_function_with_name(cmd);
|
||||
|
||||
/* Return if already loaded and we are skipping reloading */
|
||||
if( !reload && func )
|
||||
return 0;
|
||||
|
||||
/* Nothing to do if we just checked it */
|
||||
if (func && time(NULL) - func->access.last_checked <= 1)
|
||||
return 0;
|
||||
|
||||
/* The source of the script will end up here */
|
||||
wcstring script_source;
|
||||
bool has_script_source = 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);
|
||||
}
|
||||
|
||||
if (! has_script_source)
|
||||
{
|
||||
/*
|
||||
Iterate over path searching for suitable completion files
|
||||
*/
|
||||
for( 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) {
|
||||
if (! func || access.mod_time != func->access.mod_time) {
|
||||
wcstring esc = escape_string(path, 1);
|
||||
script_source = L". " + esc;
|
||||
has_script_source = true;
|
||||
|
||||
if( !func )
|
||||
func = new autoload_function_t(cmd);
|
||||
func->access = access;
|
||||
|
||||
// Remove this command because we are going to reload it
|
||||
command_removed(cmd);
|
||||
|
||||
reloaded = 1;
|
||||
}
|
||||
else if( func )
|
||||
{
|
||||
/*
|
||||
If we are rechecking an autoload file, and it hasn't
|
||||
changed, update the 'last check' timestamp.
|
||||
*/
|
||||
func->access = access;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
If no file 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( !func )
|
||||
{
|
||||
func = new autoload_function_t(cmd);
|
||||
func->access.last_checked = time(NULL);
|
||||
func->is_placeholder = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have a script, either built-in or a file source, then run it */
|
||||
if (has_script_source)
|
||||
{
|
||||
if( exec_subshell( script_source.c_str(), 0 ) == -1 )
|
||||
{
|
||||
/*
|
||||
Do nothing on failiure
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return reloaded;
|
||||
}
|
||||
|
||||
void parse_util_set_argv( wchar_t **argv, const wcstring_list_t &named_arguments )
|
||||
{
|
||||
if( *argv )
|
||||
|
|
108
parse_util.h
108
parse_util.h
|
@ -12,114 +12,6 @@
|
|||
#include <map>
|
||||
#include <set>
|
||||
|
||||
struct autoload_function_t : public lru_node_t
|
||||
{
|
||||
autoload_function_t(const wcstring &key) : lru_node_t(key), is_placeholder(false) { bzero(&access, sizeof access); }
|
||||
file_access_attempt_t access; /** The last access attempt */
|
||||
bool is_placeholder; /** Whether we are a placeholder that stands in for "no such function" */
|
||||
};
|
||||
|
||||
|
||||
struct builtin_script_t;
|
||||
|
||||
/**
|
||||
A class that represents a path from which we can autoload, and the autoloaded contents.
|
||||
*/
|
||||
class autoload_t : private lru_cache_t<autoload_function_t> {
|
||||
private:
|
||||
|
||||
/** The environment variable name */
|
||||
const wcstring env_var_name;
|
||||
|
||||
/** Builtin script array */
|
||||
const struct builtin_script_t *const builtin_scripts;
|
||||
|
||||
/** Builtin script count */
|
||||
const size_t builtin_script_count;
|
||||
|
||||
/** The path from which we most recently autoloaded */
|
||||
wcstring path;
|
||||
|
||||
/** The map from function name to the function. */
|
||||
typedef std::map<wcstring, autoload_function_t> autoload_functions_map_t;
|
||||
autoload_functions_map_t autoload_functions;
|
||||
|
||||
/**
|
||||
A table containing all the files that are currently being
|
||||
loaded. This is here to help prevent recursion.
|
||||
*/
|
||||
std::set<wcstring> is_loading_set;
|
||||
|
||||
bool is_loading(const wcstring &name) const {
|
||||
return is_loading_set.find(name) != is_loading_set.end();
|
||||
}
|
||||
|
||||
bool remove_function_with_name(const wcstring &name) {
|
||||
return autoload_functions.erase(name) > 0;
|
||||
}
|
||||
|
||||
autoload_function_t *get_function_with_name(const wcstring &name)
|
||||
{
|
||||
autoload_function_t *result = NULL;
|
||||
autoload_functions_map_t::iterator iter = autoload_functions.find(name);
|
||||
if (iter != autoload_functions.end())
|
||||
result = &iter->second;
|
||||
return result;
|
||||
}
|
||||
|
||||
void remove_all_functions(void) {
|
||||
autoload_functions.clear();
|
||||
}
|
||||
|
||||
size_t function_count(void) const {
|
||||
return autoload_functions.size();
|
||||
}
|
||||
|
||||
int load_internal( const wcstring &cmd, int reload, const wcstring_list_t &path_list );
|
||||
|
||||
virtual void node_was_evicted(autoload_function_t *node);
|
||||
|
||||
|
||||
protected:
|
||||
/** Overridable callback for when a command is removed */
|
||||
virtual void command_removed(const wcstring &cmd) { }
|
||||
|
||||
public:
|
||||
|
||||
/** Create an autoload_t for the given environment variable name */
|
||||
autoload_t(const wcstring &env_var_name_var, const builtin_script_t *scripts, size_t script_count );
|
||||
|
||||
/**
|
||||
Autoload the specified file, if it exists in the specified path. Do
|
||||
not load it multiple times unless it's timestamp changes or
|
||||
parse_util_unload is called.
|
||||
|
||||
Autoloading one file may unload another.
|
||||
|
||||
\param cmd the filename to search for. The suffix '.fish' is always added to this name
|
||||
\param on_unload a callback function to run if a suitable file is found, which has not already been run. unload will also be called for old files which are unloaded.
|
||||
\param reload wheter to recheck file timestamps on already loaded files
|
||||
*/
|
||||
int load( const wcstring &cmd, bool reload );
|
||||
/**
|
||||
Reset the loader for the specified path variable. This will cause
|
||||
all information on loaded files in the specified directory to be
|
||||
reset.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
Tell the autoloader that the specified file, in the specified path,
|
||||
is no longer loaded.
|
||||
|
||||
\param cmd the filename to search for. The suffix '.fish' is always added to this name
|
||||
\param on_unload a callback function which will be called before (re)loading a file, may be used to unload the previous file.
|
||||
\return non-zero if the file was removed, zero if the file had not yet been loaded
|
||||
*/
|
||||
int unload( const wcstring &cmd );
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
Find the beginning and end of the first subshell in the specified string.
|
||||
|
||||
|
|
Loading…
Reference in a new issue