From 1ef177bfc822ce442f9117d00b5ab80438974a33 Mon Sep 17 00:00:00 2001 From: bog Date: Sun, 15 Oct 2023 12:07:30 +0200 Subject: [PATCH] :sparkles: can now build arrays using wildcards. --- meson.build | 1 + src/Finder.cpp | 74 +++++++++++++++++++++++ src/Finder.hpp | 27 +++++++++ src/Interpreter.cpp | 136 ++++++++++++++++++++++++++++++++++++++++-- src/Interpreter.hpp | 8 +++ src/main.cpp | 4 +- tests/Interpreter.cpp | 127 +++++++++++++++++++++++++++++++++++++-- 7 files changed, 367 insertions(+), 10 deletions(-) create mode 100644 src/Finder.cpp create mode 100644 src/Finder.hpp diff --git a/meson.build b/meson.build index 2320cb1..48c306f 100644 --- a/meson.build +++ b/meson.build @@ -25,6 +25,7 @@ snake_lib = static_library('snake', 'src/SymTable.cpp', 'src/Loader.cpp', 'src/Executor.cpp', + 'src/Finder.cpp', ], dependencies: [ dependency('sqlite3') diff --git a/src/Finder.cpp b/src/Finder.cpp new file mode 100644 index 0000000..9a1f0ef --- /dev/null +++ b/src/Finder.cpp @@ -0,0 +1,74 @@ +#include "Finder.hpp" +#include + +namespace sn +{ + /*explicit*/ Finder::Finder() + { + } + + /*virtual*/ Finder::~Finder() + { + } + + /*virtual*/ std::vector + Finder::get_paths(std::filesystem::path root) + { + std::vector paths; + + for (auto entry: std::filesystem::directory_iterator(root)) + { + if (std::filesystem::is_directory(entry.path())) + { + auto more = get_paths(entry.path()); + + std::copy(std::begin(more), std::end(more), + std::back_inserter(paths)); + } + + paths.push_back(entry.path()); + } + + return paths; + } + + /* virtual*/ bool Finder::match(std::string const& text, + std::filesystem::path const& path) + { + if (text == "*") + { + return true; + } + + if (text.size() > 1 && text.substr(0, 2) == "*.") + { + auto ext = text.substr(1); + return path.extension() == ext; + } + + if (auto wildcard_pos = text.find("*"); + wildcard_pos != std::string::npos) + { + auto dir = std::filesystem::path(text.substr(0, wildcard_pos)); + auto ext = text.substr(wildcard_pos + 1); + + if (dir.empty() == false) + { + dir = dir.string().substr(0, dir.string().size() - 1); + } + + if (path.parent_path().is_absolute()) + { + dir = std::filesystem::absolute(dir); + } + + return path.parent_path() == dir + && (ext.empty() + || path.has_extension() == false + || path.extension() == ext); + } + + return false; + } + +} diff --git a/src/Finder.hpp b/src/Finder.hpp new file mode 100644 index 0000000..e294465 --- /dev/null +++ b/src/Finder.hpp @@ -0,0 +1,27 @@ +#ifndef sn_FINDER_HPP +#define sn_FINDER_HPP + +#include "commons.hpp" + +namespace sn +{ + /** + * Find files from a wildcard expression. + **/ + class Finder + { + public: + explicit Finder(); + virtual ~Finder(); + + virtual std::vector + get_paths(std::filesystem::path root); + + virtual bool match(std::string const& text, + std::filesystem::path const& path); + + private: + }; +} + +#endif diff --git a/src/Interpreter.cpp b/src/Interpreter.cpp index c0904ad..2c5d21b 100644 --- a/src/Interpreter.cpp +++ b/src/Interpreter.cpp @@ -8,10 +8,12 @@ namespace sn /*explicit*/ Interpreter::Interpreter(std::shared_ptr state, std::shared_ptr loader, std::shared_ptr executor, + std::shared_ptr finder, std::ostream& ostream) : m_state { state } , m_loader { loader } , m_executor { executor } + , m_finder { finder } , m_ostream { ostream } { } @@ -25,6 +27,9 @@ namespace sn // Process the document ast // ------------------------ auto node = m_loader->parse_snakefile(); + + m_files = collect(node); + process(node); // Get all files @@ -52,10 +57,10 @@ namespace sn } } + // Get modified files // ------------------ m_state->init(files); - auto modified_files = m_state->get_modified_files(files); // Build DAG @@ -159,7 +164,34 @@ namespace sn for (size_t i=0; isize(); i++) { - auto vals = get_value(*node->get(i)); + std::string text = (*node->get(i))->repr(); + std::vector vals; + + if (text.find('*') != std::string::npos) + // wildcard array constructor + { + auto candidate = + m_finder->get_paths(m_loader->find_snakefile().parent_path()); + + std::copy(std::begin(m_files), std::end(m_files), + std::back_inserter(candidate)); + + std::sort(std::begin(candidate), std::end(candidate)); + candidate.erase(std::unique(std::begin(candidate), + std::end(candidate)), + std::end(candidate)); + + std::copy_if(std::begin(candidate), + std::end(candidate), + std::back_inserter(vals), + [&](auto f){ + return m_finder->match(text, f); + }); + } + else + { + vals = get_value(*node->get(i)); + } for (size_t j=0; j + Interpreter::collect(std::shared_ptr node) const + { + std::vector results; + + switch (node->type()) + { + case NODE_RULE: { + auto target_node = *node->get(0); + auto deps = *node->get(1); + auto block = *node->get(2); + + std::vector target_values; + std::vector target_values_verbatim; + + for (size_t i=0; isize(); i++) + { + auto t = *target_node->get(i); + + try + { + auto values = get_value(t); + + std::copy(std::begin(values), std::end(values), + std::back_inserter(target_values_verbatim)); + + std::transform(std::begin(values), + std::end(values), + std::back_inserter(target_values), + std::bind(&Interpreter::format_path, this, + std::placeholders::_1)); + } + catch(...) + { + } + } + + std::vector dep_values; + std::vector dep_values_verbatim; + + for (size_t i=0; isize(); i++) + { + auto d = *deps->get(i); + + try + { + auto values = get_value(d); + + std::copy(std::begin(values), std::end(values), + std::back_inserter(dep_values_verbatim)); + + std::transform(std::begin(values), std::end(values), + std::back_inserter(dep_values), + std::bind(&Interpreter::format_path, this, + std::placeholders::_1)); + } + catch (...) + { + } + } + + std::copy(std::begin(target_values_verbatim), + std::end(target_values_verbatim), + std::back_inserter(results)); + + std::copy(std::begin(dep_values_verbatim), + std::end(dep_values_verbatim), + std::back_inserter(results)); + } break; + + default: { + for (size_t i=0; isize(); i++) + { + auto r = collect(*node->get(i)); + std::copy(std::begin(r), std::end(r), + std::back_inserter(results)); + + } + } break; + } + + std::sort(std::begin(results), std::end(results)); + + results.erase(std::unique(std::begin(results), + std::end(results)), + std::end(results)); + + return results; + } + void Interpreter::process(std::shared_ptr node) { switch (node->type()) @@ -276,9 +398,8 @@ namespace sn std::string name = (*node->get(0))->repr(); auto val = *node->get(1); - - m_sym.declare(name, get_value(val)); - + auto values = get_value(val); + m_sym.declare(name, values); } break; case NODE_RULE: { @@ -363,6 +484,11 @@ namespace sn for (auto val: values) { + if (std::filesystem::is_regular_file(val)) + { + val = std::filesystem::relative(val, m_loader->find_snakefile().parent_path()).string(); + } + script += vsep + val; vsep = " "; } diff --git a/src/Interpreter.hpp b/src/Interpreter.hpp index aa85392..134d334 100644 --- a/src/Interpreter.hpp +++ b/src/Interpreter.hpp @@ -8,6 +8,7 @@ #include "SymTable.hpp" #include "Loader.hpp" #include "Executor.hpp" +#include "Finder.hpp" namespace sn { @@ -24,6 +25,7 @@ namespace sn explicit Interpreter(std::shared_ptr state, std::shared_ptr loader, std::shared_ptr executor, + std::shared_ptr finder, std::ostream& ostream); virtual ~Interpreter(); @@ -33,7 +35,10 @@ namespace sn std::shared_ptr m_state; std::shared_ptr m_loader; std::shared_ptr m_executor; + std::shared_ptr m_finder; std::ostream& m_ostream; + std::vector m_files; + std::unordered_map> m_dependencies; @@ -46,6 +51,9 @@ namespace sn std::vector targets(std::filesystem::path path) const; + std::vector + collect(std::shared_ptr node) const; + void process(std::shared_ptr node); std::vector scripts(std::shared_ptr block) const; diff --git a/src/main.cpp b/src/main.cpp index e655120..d08576f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,14 +4,16 @@ #include "Interpreter.hpp" #include "State.hpp" #include "Executor.hpp" +#include "Finder.hpp" int main(int, char**) { auto state = std::make_shared(); auto loader = std::make_shared(); auto executor = std::make_shared(); + auto finder = std::make_shared(); - sn::Interpreter interpreter {state, loader, executor, std::cout}; + sn::Interpreter interpreter {state, loader, executor, finder, std::cout}; interpreter.run(); return 0; diff --git a/tests/Interpreter.cpp b/tests/Interpreter.cpp index 7f547d4..b746663 100644 --- a/tests/Interpreter.cpp +++ b/tests/Interpreter.cpp @@ -55,6 +55,21 @@ struct ExecutorMock: public Executor { } }; +struct FinderMock: public Finder { + std::vector files; + + explicit FinderMock(std::vector const& p_files) + : files { p_files } + { + } + + virtual std::vector + get_paths(std::filesystem::path) override + { + return files; + } +}; + class InterpreterTest { public: @@ -69,19 +84,63 @@ public: auto loader = std::make_shared(snakefile); auto executor = std::make_shared(); + auto finder = std::make_shared + (std::vector{ + "first.cpp", + "src/second.py", + "third.py", + "src/fourth.hpp", + "fifth", + "src/build/target/sixth" + }); std::stringstream ss; - Interpreter interpreter {state, loader, executor, ss}; + Interpreter interpreter {state, loader, executor, finder, ss}; interpreter.run(); return executor->history; } + std::string sort_line(std::string const& line) + { + std::vector words; + std::string word; + std::stringstream ss; + ss << line; + + while (std::getline(ss, word, ' ')) + { + words.push_back(word); + } + + std::sort(std::begin(words), std::end(words)); + + std::string res = ""; + + std::string sep = ""; + + for (auto w: words) + { + res += sep + w; + sep = " "; + } + + return res; + } + void test_contains(std::string const& str, std::vector vec) { - INFO("'" << str << "' not found, got '" << vec[0] << "'"); - REQUIRE(std::find(std::begin(vec), std::end(vec), str) - != std::end(vec)); + if (vec.empty()) + { + INFO("'" << str << "' not found, got nothing"); + REQUIRE(false); + return; + } + + INFO(str << "not found"); + REQUIRE(std::any_of(std::begin(vec), std::end(vec), [&](auto const& line){ + return sort_line(line) == sort_line(str); + })); } void test_not_contains(std::string const& str, std::vector vec) @@ -333,3 +392,63 @@ TEST_CASE_METHOD(InterpreterTest, "Interpreter_in_out") test_contains("a b c x y z", res); } } + +TEST_CASE_METHOD(InterpreterTest, "Interpreter_array_constructor") +{ + SECTION("extension I") + { + std::stringstream ss; + ss << "x y z -> (*.py) {" << std::endl; + ss << "$IN" << std::endl; + ss << "}" << std::endl; + + std::string result; + result = "src/second.py third.py"; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("third.py")); + test_contains(result, res); + } + + SECTION("extension II") + { + std::stringstream ss; + ss << "x y z -> (*.cpp) {" << std::endl; + ss << "$IN" << std::endl; + ss << "}" << std::endl; + + std::string result; + result = "first.cpp"; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("first.cpp")); + test_contains(result, res); + } + + SECTION("all in directory") + { + std::stringstream ss; + ss << "x y z -> (src/*) {" << std::endl; + ss << "$IN" << std::endl; + ss << "}" << std::endl; + + std::string result; + result = "src/second.py src/fourth.hpp"; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("src/second.py")); + test_contains(result, res); + } + + SECTION("all in sub directories") + { + std::stringstream ss; + ss << "x y z -> (src/build/target/*) {" << std::endl; + ss << "$IN" << std::endl; + ss << "}" << std::endl; + + std::string result; + result = "src/build/target/sixth"; + + auto res = TEST_RUN(ss.str(), + std::filesystem::path("src/build/target/sixth")); + test_contains(result, res); + } +}