diff --git a/autoload.cpp b/autoload.cpp index 0bae1a265..4eaab7cb5 100644 --- a/autoload.cpp +++ b/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 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 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; iaccess.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; +} diff --git a/autoload.h b/autoload.h index ca3d61cfb..0a480f4b5 100644 --- a/autoload.h +++ b/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 { +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 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 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 diff --git a/parse_util.cpp b/parse_util.cpp index 3ca8d610e..90b609978 100644 --- a/parse_util.cpp +++ b/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 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; iaccess.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 ) diff --git a/parse_util.h b/parse_util.h index 5e92d39c8..28c4f003b 100644 --- a/parse_util.h +++ b/parse_util.h @@ -12,114 +12,6 @@ #include #include -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 { -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 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 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.