diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index f9166fed9ca846f6e56e28b770f00f103b036382..53669cbdcee7efe3971132485641df1cf7ba90af 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -13,11 +13,13 @@ # received a copy of the GNU Lesser General Public License along with # dynamic-graph. If not, see <http://www.gnu.org/licenses/>. -# Add default script directory. +# Add default import directories. # It is used by the import directive of the sot shell to locate -# scripts. -SET(DG_IMPORT_DEFAULT_PATHS - "${CMAKE_INSTALL_PREFIX}/share/dynamic-graph/script") +# scripts and plug-ins. +SET( + DG_IMPORT_DEFAULT_PATHS + "${CMAKE_INSTALL_PREFIX}/share/dynamic-graph/script:${CMAKE_INSTALL_PREFIX}/${PLUGINDIR}" + ) CONFIGURE_FILE( ${PROJECT_NAME}/import-default-paths.h.cmake diff --git a/include/dynamic-graph/import.h b/include/dynamic-graph/import.h index 6a9713cb22ccd839d75f7325fda4fd0e02267c4c..8566df45b4bef5e74c586ba77f653f97f4932a9f 100644 --- a/include/dynamic-graph/import.h +++ b/include/dynamic-graph/import.h @@ -21,6 +21,8 @@ # include <string> # include <vector> +# include <boost/filesystem/path.hpp> + namespace dynamicgraph { class Interpreter; @@ -28,7 +30,13 @@ namespace dynamicgraph { namespace { - extern std::vector<std::string> importPaths; + /// \brief Vector of Boost.Filesystem paths. + /// + /// Used to store paths where the import statement + /// will look for scripts or plug-ins. + typedef std::vector<boost::filesystem::path> paths_t; + + extern paths_t importPaths; } // end of anonymous namespace. /// \brief Implement sot interpretor import command. diff --git a/src/dgraph/import.cpp b/src/dgraph/import.cpp index d2e3f91fddb7cb2e25887badad1029897012cac9..e345057a8598aff5062bbfbe8633850bc451f589 100644 --- a/src/dgraph/import.cpp +++ b/src/dgraph/import.cpp @@ -27,15 +27,17 @@ #include <vector> #include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> #include <boost/foreach.hpp> #include <boost/format.hpp> -#include <dynamic-graph/import-default-paths.h> -#include <dynamic-graph/import.h> #include <dynamic-graph/debug.h> #include <dynamic-graph/exception-abstract.h> #include <dynamic-graph/exception-factory.h> +#include <dynamic-graph/import-default-paths.h> +#include <dynamic-graph/import.h> #include <dynamic-graph/interpreter.h> +#include <dynamic-graph/plugin-loader.h> // The default import paths has to be passed from the build system // as a -D flag for instance. @@ -47,6 +49,16 @@ static const char* ENV_DG_PATH = "DG_PATH"; + +// Define OS specific extensions for shared libraries. +#ifdef _WIN32 +static const char* SHARED_LIBRARY_EXT = ".dll"; +#elif __APPLE__ +static const char* SHARED_LIBRARY_EXT = ".dylib"; +#else +static const char* SHARED_LIBRARY_EXT = ".so"; +#endif + namespace dynamicgraph { namespace command @@ -54,7 +66,7 @@ namespace dynamicgraph namespace { /// Initialize import paths list (called during static initialization). - std::vector<std::string> initializePaths (); + paths_t initializePaths (); /// \brief Import paths list. /// @@ -64,33 +76,28 @@ namespace dynamicgraph /// /// The look-up is made from right to left: /// + /// On Unix: /// importPaths = A:B:C + /// On Microsoft Windows: + /// importPaths = A;B;C /// /// When typing ``import foo'', C will be searched first then B /// and A. The search stops when the file is found. - std::vector<std::string> importPaths = initializePaths (); + paths_t importPaths = initializePaths (); /// Search for a module. /// /// Returns the module full absolute path or an empty string if /// it cannot be found. - std::string searchModule (const std::string& module); + boost::filesystem::path searchModule (const std::string& module); - /// \brief Remove quotes form a string. + /// \brief Remove quotes from a string. /// /// Transform strings such as "foo" or 'foo' into foo. void removeQuotes (std::string& msg); - std::vector<std::string> initializePaths () + paths_t initializePaths () { - std::vector<std::string> importPaths; - importPaths.push_back (DG_IMPORT_DEFAULT_PATHS); - - // Search for the environment variable value. - char* ScriptPath = getenv (ENV_DG_PATH); - if (!ScriptPath) - return importPaths; - // Split the environment variable. std::string environmentSeparator; @@ -102,45 +109,136 @@ namespace dynamicgraph environmentSeparator = ":"; #endif // defined _WIN32 || defined __CYGWIN__ + + // Declare variables that will be returned. + paths_t importPaths; + + // Split the built-in values. + std::vector<std::string> splittedDefaultPaths; + boost::split (splittedDefaultPaths, + DG_IMPORT_DEFAULT_PATHS, + boost::is_any_of (environmentSeparator)); + + // Insert them back. + { + std::back_insert_iterator<paths_t> bi (importPaths); + std::copy (splittedDefaultPaths.begin (), + splittedDefaultPaths.end (), bi); + } + + // Search for the environment variable value. + char* ScriptPath = getenv (ENV_DG_PATH); + if (!ScriptPath) + return importPaths; + std::vector<std::string> splittedEnvironmentVariable; boost::split (splittedEnvironmentVariable, ScriptPath, boost::is_any_of (environmentSeparator)); - // Insert it back. - std::back_insert_iterator<std::vector<std::string> > bi (importPaths); - std::copy (splittedEnvironmentVariable.begin (), - splittedEnvironmentVariable.end (), bi); + // Insert it back + std::back_insert_iterator<paths_t> bi (importPaths); + std::copy (splittedEnvironmentVariable.begin (), + splittedEnvironmentVariable.end (), bi); return importPaths; } - std::string searchModule (const std::string& module) + boost::filesystem::path searchModule (const std::string& module) { - // Make sure the traversal is right to left to enforce - // correct priorities. - typedef std::vector<std::string>::const_reverse_iterator citer_t; - for (citer_t it = importPaths.rbegin (); - it != importPaths.rend (); ++it) + // To ensure correct priorities, the traversal has to + // done left to right. + typedef paths_t::const_iterator citer_t; + for (citer_t it = importPaths.begin (); + it != importPaths.end (); ++it) { - const std::string& path = *it; + const boost::filesystem::path& dir = *it; + if (!boost::filesystem::is_directory (dir)) + continue; + + boost::filesystem::path path = dir / module; + boost::filesystem::path pathPlugin = path; + pathPlugin.replace_extension (SHARED_LIBRARY_EXT); + + if (boost::filesystem::exists (pathPlugin) + && (boost::filesystem::is_regular_file (pathPlugin) + || boost::filesystem::is_symlink (pathPlugin))) + return pathPlugin; + + if (boost::filesystem::exists (path) + && (boost::filesystem::is_regular_file (path) + || boost::filesystem::is_symlink (path))) + return path; + } + + return boost::filesystem::path (); + } - assert (!path.empty ()); + void removeQuotes (std::string& msg) + { + if ((msg[0] == '"' && msg[msg.length () - 1] == '"') + || (msg[0] == '\'' && msg[msg.length () - 1] == '\'')) + msg = msg.substr (1, msg.length () - 2); + } - std::string filename (path); - if (filename[filename.length () - 1] != '/') - filename += "/"; - filename += module; - std::ifstream file (filename.c_str ()); - if (file.is_open () && file.good ()) - return filename; + void importScript (dynamicgraph::Interpreter& interpreter, + const boost::filesystem::path& path, + std::ifstream& file, + std::ostream& os) + { + int lineIdx = 0; + try + { + while (file.good ()) + { + dgDEBUGIN (15); + ++lineIdx; + + std::string line; + std::getline (file, line); + if (line.empty ()) + continue; + + std::istringstream iss (line); + std::string currentCmdName; + std::string currentCmdArgs; + if (iss >> currentCmdName) + { + std::getline (iss, currentCmdArgs); + boost::format fmt ("Run ``%1%'' with args ``%2%''"); + fmt % currentCmdName % currentCmdArgs; + dgDEBUG(25) << fmt.str () << std::endl; + std::istringstream issArgs (currentCmdArgs); + interpreter.cmd (currentCmdName, issArgs, os); + } + dgDEBUGOUT (15); + } + } + catch (ExceptionAbstract& exc) + { + // FIXME: come on... + std::string& msg = + const_cast<std::string&> (exc.getStringMessage ()); + boost::format fmt (" (in line %1% of file ``%2%'')"); + fmt % lineIdx % path.file_string (); + msg = msg + fmt.str (); + std::cout << msg << std::endl; + throw; } - return std::string (); } - void removeQuotes (std::string& msg) + void importPlugin (dynamicgraph::Interpreter& interpreter, + const boost::filesystem::path& path, + std::ostream&) { - if ((msg[0] == '"' && msg[msg.length () - 1] == '"') - || (msg[0] == '\'' && msg[msg.length () - 1] == '\'')) - msg = msg.substr (1, msg.length () - 2); + if (!interpreter.dlPtr) + { + DG_THROW ExceptionFactory + (ExceptionFactory::DYNAMIC_LOADING, + "No plug-in loader available."); + return; + } + interpreter.dlPtr->addPlugin (path.filename (), + path.parent_path ().file_string ()); + interpreter.dlPtr->loadPlugins (); } } // end of anonymous namespace. @@ -167,23 +265,26 @@ namespace dynamicgraph // Get rid of quotes. removeQuotes (module); - std::string filename = searchModule (module); - std::ifstream file (filename.c_str ()); - if (filename.empty () || !file.is_open () || !file.good ()) + // Get the absolute path of the module. + boost::filesystem::path path = searchModule (module); + + std::ifstream file (path.file_string ().c_str ()); + if (!boost::filesystem::is_regular_file (path) + || !file.is_open () || !file.good ()) { std::string scriptDirectories; - if (importPaths.empty ()) + if (importPaths.empty ()) scriptDirectories = "empty"; else { - BOOST_FOREACH (const std::string& path, importPaths) + BOOST_FOREACH (const boost::filesystem::path& path, importPaths) { - scriptDirectories += path; + scriptDirectories += path.file_string (); scriptDirectories += ", "; } scriptDirectories = scriptDirectories.substr - (0, scriptDirectories.length () - 2); + (0, scriptDirectories.length () - 2); } boost::format fmt @@ -191,48 +292,14 @@ namespace dynamicgraph fmt % module; fmt % scriptDirectories; DG_THROW ExceptionFactory - (ExceptionFactory::READ_FILE, fmt.str ()); + (ExceptionFactory::READ_FILE, fmt.str ()); return; } - int lineIdx = 0; - try - { - while (file.good ()) - { - ++lineIdx; - dgDEBUGIN (15); - - std::string line; - std::getline (file, line); - if (line.empty ()) - continue; - - std::istringstream iss (line); - std::string currentCmdName; - std::string currentCmdArgs; - if (iss >> currentCmdName) - { - std::getline (iss, currentCmdArgs); - boost::format fmt ("Run ``%1%'' with args ``%2%''"); - fmt % currentCmdName % currentCmdArgs; - dgDEBUG(25) << fmt.str () << std::endl; - std::istringstream issArgs (currentCmdArgs); - interpreter.cmd (currentCmdName, issArgs, os); - } - dgDEBUGOUT (15); - } - } - catch (ExceptionAbstract& exc) - { - // FIXME: come on... - std::string& msg = const_cast<std::string&> (exc.getStringMessage ()); - boost::format fmt (" (in line %1% of file ``%2%'')"); - fmt % lineIdx % filename; - msg = msg + fmt.str (); - std::cout << msg << std::endl; - throw; - } + if (path.extension () != SHARED_LIBRARY_EXT) + importScript (interpreter, path, file, os); + else + importPlugin (interpreter, path, os); dgDEBUGOUT(15); } @@ -266,8 +333,8 @@ namespace dynamicgraph << std::endl; return; } - if (!importPaths.empty ()) - importPaths.pop_back (); + if (!importPaths.empty ()) + importPaths.pop_back (); } } // end of namespace command. diff --git a/tests/data/empty.sot b/tests/data/empty.dg similarity index 100% rename from tests/data/empty.sot rename to tests/data/empty.dg diff --git a/tests/interpreter.cpp b/tests/interpreter.cpp index 477fa53a03cfac060461aa8e45f3e40c8b1657ad..1412cc1b1af412fd740d864c2ff5520797e482ee 100644 --- a/tests/interpreter.cpp +++ b/tests/interpreter.cpp @@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE (cmd_help) BOOST_AUTO_TEST_CASE (cmd_run_emptyfile) { dynamicgraph::Interpreter shell; - RUN_COMMAND ("run", TESTS_DATADIR "/empty.sot"); + RUN_COMMAND ("run", TESTS_DATADIR "/empty.dg"); BOOST_CHECK (output.is_empty ()); } @@ -164,7 +164,7 @@ BOOST_AUTO_TEST_CASE (cmd_import_push) // Import empty file. { - RUN_COMMAND ("import", "empty.sot"); + RUN_COMMAND ("import", "empty.dg"); BOOST_CHECK (output.is_empty ()); } @@ -175,11 +175,11 @@ BOOST_AUTO_TEST_CASE (cmd_import_push) } // Import empty file again: this will fail - // as empty.sot is *NOT* in a standard location. + // as empty.dg is *NOT* in a standard location. { output_test_stream output; std::stringstream ss; - ss << "empty.sot"; + ss << "empty.dg"; std::istringstream args (ss.str ()); // Make sure this trigger an error.