Move autoload class into autoload.h and cpp

This commit is contained in:
ridiculousfish 2012-01-25 18:59:35 -08:00
parent d6545588a3
commit 843ba4ac2c
4 changed files with 327 additions and 321 deletions

View file

@ -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;
}

View file

@ -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

View file

@ -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 )

View file

@ -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.