can now build arrays using wildcards.

main
bog 2023-10-15 12:07:30 +02:00
parent 61e4f761c1
commit 1ef177bfc8
7 changed files with 367 additions and 10 deletions

View File

@ -25,6 +25,7 @@ snake_lib = static_library('snake',
'src/SymTable.cpp',
'src/Loader.cpp',
'src/Executor.cpp',
'src/Finder.cpp',
],
dependencies: [
dependency('sqlite3')

74
src/Finder.cpp Normal file
View File

@ -0,0 +1,74 @@
#include "Finder.hpp"
#include <filesystem>
namespace sn
{
/*explicit*/ Finder::Finder()
{
}
/*virtual*/ Finder::~Finder()
{
}
/*virtual*/ std::vector<std::filesystem::path>
Finder::get_paths(std::filesystem::path root)
{
std::vector<std::filesystem::path> 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;
}
}

27
src/Finder.hpp Normal file
View File

@ -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<std::filesystem::path>
get_paths(std::filesystem::path root);
virtual bool match(std::string const& text,
std::filesystem::path const& path);
private:
};
}
#endif

View File

@ -8,10 +8,12 @@ namespace sn
/*explicit*/ Interpreter::Interpreter(std::shared_ptr<State> state,
std::shared_ptr<Loader> loader,
std::shared_ptr<Executor> executor,
std::shared_ptr<Finder> 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; i<node->size(); i++)
{
auto vals = get_value(*node->get(i));
std::string text = (*node->get(i))->repr();
std::vector<std::string> 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<vals.size(); j++)
{
@ -261,6 +293,96 @@ namespace sn
return result;
}
std::vector<std::filesystem::path>
Interpreter::collect(std::shared_ptr<Node> node) const
{
std::vector<std::filesystem::path> 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<std::string> target_values;
std::vector<std::string> target_values_verbatim;
for (size_t i=0; i<target_node->size(); 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<std::string> dep_values;
std::vector<std::string> dep_values_verbatim;
for (size_t i=0; i<deps->size(); 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; i<node->size(); 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> 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 = " ";
}

View File

@ -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> state,
std::shared_ptr<Loader> loader,
std::shared_ptr<Executor> executor,
std::shared_ptr<Finder> finder,
std::ostream& ostream);
virtual ~Interpreter();
@ -33,7 +35,10 @@ namespace sn
std::shared_ptr<State> m_state;
std::shared_ptr<Loader> m_loader;
std::shared_ptr<Executor> m_executor;
std::shared_ptr<Finder> m_finder;
std::ostream& m_ostream;
std::vector<std::filesystem::path> m_files;
std::unordered_map<std::filesystem::path,
std::vector<std::filesystem::path>> m_dependencies;
@ -46,6 +51,9 @@ namespace sn
std::vector<std::filesystem::path>
targets(std::filesystem::path path) const;
std::vector<std::filesystem::path>
collect(std::shared_ptr<Node> node) const;
void process(std::shared_ptr<Node> node);
std::vector<std::string> scripts(std::shared_ptr<Node> block) const;

View File

@ -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<sn::State>();
auto loader = std::make_shared<sn::Loader>();
auto executor = std::make_shared<sn::Executor>();
auto finder = std::make_shared<sn::Finder>();
sn::Interpreter interpreter {state, loader, executor, std::cout};
sn::Interpreter interpreter {state, loader, executor, finder, std::cout};
interpreter.run();
return 0;

View File

@ -55,6 +55,21 @@ struct ExecutorMock: public Executor {
}
};
struct FinderMock: public Finder {
std::vector<std::filesystem::path> files;
explicit FinderMock(std::vector<std::filesystem::path> const& p_files)
: files { p_files }
{
}
virtual std::vector<std::filesystem::path>
get_paths(std::filesystem::path) override
{
return files;
}
};
class InterpreterTest
{
public:
@ -69,19 +84,63 @@ public:
auto loader = std::make_shared<LoaderMock>(snakefile);
auto executor = std::make_shared<ExecutorMock>();
auto finder = std::make_shared<FinderMock>
(std::vector<std::filesystem::path>{
"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<std::string> 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<std::string> 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<std::string> 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);
}
}