Просмотр исходного кода

Very rudimentary template rendering, only accesses a small number of resources yet.

default_compile_flags
vector-of-bool 4 лет назад
Родитель
Сommit
69720f0a44
15 измененных файлов: 261 добавлений и 11 удалений
  1. +11
    -0
      catalog.json
  2. +1
    -0
      library.dds
  3. +1
    -0
      library.jsonc
  4. +1
    -0
      package.dds
  5. +2
    -1
      package.jsonc
  6. +14
    -6
      src/dds/build/plan/full.cpp
  7. +179
    -2
      src/dds/build/plan/template.cpp
  8. +3
    -1
      src/dds/build/plan/template.hpp
  9. +2
    -0
      src/dds/error/errors.hpp
  10. +3
    -0
      tests/basics/config_template/simple/library.jsonc
  11. +5
    -0
      tests/basics/config_template/simple/package.jsonc
  12. +5
    -0
      tests/basics/config_template/simple/src/simple/config.config.hpp
  13. +5
    -0
      tests/basics/config_template/simple/src/simple/simple.test.cpp
  14. +18
    -1
      tests/basics/config_template/test_config_template.py
  15. +11
    -0
      tools/gen-catalog-json.py

+ 11
- 0
catalog.json Просмотреть файл

@@ -1,5 +1,16 @@
{
"packages": {
"ctre": {
"2.7.0": {
"depends": {},
"description": "A compile-time PCRE (almost) compatible regular expression matcher",
"git": {
"auto-lib": "hanickadot/ctre",
"ref": "v2.7",
"url": "https://github.com/hanickadot/compile-time-regular-expressions.git"
}
}
},
"fmt": {
"0.10.0": {
"depends": {},

+ 1
- 0
library.dds Просмотреть файл

@@ -10,3 +10,4 @@ Uses: semver/semver
Uses: vob/semester
Uses: pubgrub/pubgrub
Uses: vob/json5
Uses: hanickadot/ctre

+ 1
- 0
library.jsonc Просмотреть файл

@@ -12,5 +12,6 @@
"pubgrub/pubgrub",
"vob/json5",
"vob/semester",
"hanickadot/ctre",
]
}

+ 1
- 0
package.dds Просмотреть файл

@@ -12,5 +12,6 @@ Depends: semver 0.2.1
Depends: pubgrub 0.2.0
Depends: vob-json5 0.1.5
Depends: vob-semester 0.1.0
Depends: ctre 2.7.0

Test-Driver: Catch-Main

+ 2
- 1
package.jsonc Просмотреть файл

@@ -13,7 +13,8 @@
"semver": "0.2.1",
"pubgrub": "0.2.0",
"vob-json5": "0.1.5",
"vob-semester": "0.1.0"
"vob-semester": "0.1.0",
"ctre": "2.7.0",
},
"test_driver": "Catch-Main"
}

+ 14
- 6
src/dds/build/plan/full.cpp Просмотреть файл

@@ -7,7 +7,9 @@
#include <range/v3/view/concat.hpp>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/join.hpp>
#include <range/v3/view/repeat.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/view/zip.hpp>

#include <spdlog/spdlog.h>

@@ -72,16 +74,22 @@ bool parallel_run(Range&& rng, int n_jobs, Fn&& fn) {
return exceptions.empty();
}

template <typename T, typename Range>
decltype(auto) pair_up(T& left, Range& right) {
auto rep = ranges::view::repeat(left);
return ranges::view::zip(rep, right);
}

} // namespace

void build_plan::render_all(build_env_ref env) const {
auto templates = _packages //
| ranges::view::transform(&package_plan::libraries) //
| ranges::view::join //
| ranges::view::transform(&library_plan::templates) //
auto templates = _packages //
| ranges::view::transform(&package_plan::libraries) //
| ranges::view::join //
| ranges::view::transform([](const auto& lib) { return pair_up(lib, lib.templates()); }) //
| ranges::view::join;
for (const render_template_plan& tmpl : templates) {
tmpl.render(env);
for (const auto& [lib, tmpl] : templates) {
tmpl.render(env, lib.library_());
}
}


+ 179
- 2
src/dds/build/plan/template.cpp Просмотреть файл

@@ -1,10 +1,176 @@
#include <dds/build/plan/template.hpp>

#include <dds/error/errors.hpp>
#include <dds/library/root.hpp>
#include <dds/util/fs.hpp>
#include <dds/util/string.hpp>

#include <ctre.hpp>
#include <semester/json.hpp>

#include <string>
#include <string_view>

using namespace dds;

void render_template_plan::render(build_env_ref env) const {
using json_data = semester::basic_data<semester::json_traits<std::allocator<void>>>;

namespace {

static constexpr ctll::fixed_string IDENT_RE{"([_a-zA-Z]\\w*)(.*)"};

std::string_view skip(std::string_view in) {
auto nspace_pos = in.find_first_not_of(" \t\n\r\f");
in = in.substr(nspace_pos);
if (starts_with(in, "/*")) {
// It's a block comment. Find the block-end marker.
auto block_end = in.find("*/");
if (block_end == in.npos) {
throw_user_error<errc::template_error>("Unterminated block comment");
}
in = in.substr(block_end + 2);
// Recursively skip some more
return skip(in);
}
if (starts_with(in, "//")) {
more:
// It's a line comment. Find the next not-continued newline
auto cn_nl = in.find("\\\n");
auto nl = in.find("\n");
if (cn_nl < nl) {
// The next newline is a continuation of the comment. Keep looking
in = in.substr(nl + 1);
goto more;
}
if (nl == in.npos) {
// We've reached the end. Okay.
return in.substr(nl);
}
}
// Not a comment, and not whitespace. Okay.
return in;
}

std::string stringify(const json_data& dat) {
if (dat.is_bool()) {
return dat.as_bool() ? "true" : "false";
} else if (dat.is_double()) {
return std::to_string(dat.as_double());
} else if (dat.is_null()) {
return "nullptr";
} else if (dat.is_string()) {
/// XXX: This probably isn't quite enough sanitization for edge cases.
auto str = dat.as_string();
str = replace(str, "\n", "\\n");
str = replace(str, "\"", "\\\"");
return "\"" + str + "\"";
} else {
throw_user_error<errc::template_error>("Cannot render un-stringable data type");
}
}

std::pair<std::string, std::string_view> eval_expr_tail(std::string_view in, const json_data& dat) {
in = skip(in);
if (starts_with(in, ".")) {
// Accessing a subproperty of the data
in.remove_prefix(1);
in = skip(in);
// We _must_ see an identifier
auto [is_ident, ident, tail] = ctre::match<IDENT_RE>(in);
if (!is_ident) {
throw_user_error<errc::template_error>("Expected identifier following dot `.`");
}
if (!dat.is_mapping()) {
throw_user_error<errc::template_error>("Cannot use dot `.` on non-mapping object");
}
auto& map = dat.as_mapping();
auto found = map.find(ident.to_view());
if (found == map.end()) {
throw_user_error<errc::template_error>("No subproperty '{}'", ident.to_view());
}
return eval_expr_tail(tail, found->second);
}
return {stringify(dat), in};
}

std::pair<std::string, std::string_view> eval_primary_expr(std::string_view in,
const json_data& dat) {
in = skip(in);

if (in.empty()) {
throw_user_error<errc::template_error>("Expected primary expression");
}

if (in.front() == '(') {
in = in.substr(1);
auto [ret, tail] = eval_primary_expr(in, dat);
if (!starts_with(tail, ")")) {
throw_user_error<errc::template_error>(
"Expected closing parenthesis `)` following expression");
}
return {ret, tail.substr(1)};
}

auto [is_ident, ident, tail_1] = ctre::match<IDENT_RE>(in);

if (is_ident) {
auto& map = dat.as_mapping();
auto found = map.find(ident.to_view());
if (found == map.end()) {
throw_user_error<errc::template_error>("Unknown top-level identifier '{}'",
ident.to_view());
}

return eval_expr_tail(tail_1, found->second);
}

return {"nope", in};
}

std::string render_template(std::string_view tmpl, const library_root& lib) {
std::string acc;
std::string_view MARKER_STRING = "__dds";

// Fill out a data structure that will be exposed to the template
json_data dat = json_data::mapping_type({
{
"lib",
json_data::mapping_type{
{"name", lib.manifest().name},
{"root", lib.path().string()},
},
},
});

while (!tmpl.empty()) {
// Find the next marker in the template string
auto next_marker = tmpl.find(MARKER_STRING);
if (next_marker == tmpl.npos) {
// We've reached the end of the template. Stop
acc.append(tmpl);
break;
}
// Append the string up to the next marker
acc.append(tmpl.substr(0, next_marker));
// Consume up to the next marker
tmpl = tmpl.substr(next_marker + MARKER_STRING.size());
auto next_not_space = tmpl.find_first_not_of(" \t");
if (next_not_space == tmpl.npos || tmpl[next_not_space] != '(') {
throw_user_error<errc::template_error>(
"Expected `(` following `__dds` identifier in template file");
}

auto [inner, tail] = eval_primary_expr(tmpl, dat);
acc.append(inner);
tmpl = tail;
}

return acc;
}

} // namespace

void render_template_plan::render(build_env_ref env, const library_root& lib) const {
auto content = slurp_file(_source.path);

// Calculate the destination of the template rendering
@@ -12,5 +178,16 @@ void render_template_plan::render(build_env_ref env) const {
dest.replace_filename(dest.stem().stem().filename().string() + dest.extension().string());
fs::create_directories(dest.parent_path());

fs::copy_file(_source.path, dest, fs::copy_options::overwrite_existing);
auto result = render_template(content, lib);
if (fs::is_regular_file(dest)) {
auto existing_content = slurp_file(dest);
if (result == existing_content) {
/// The content of the file has not changed. Do not write a file.
return;
}
}

auto ofile = open(dest, std::ios::binary | std::ios::out);
ofile << result;
ofile.close(); // Throw any exceptions while closing the file
}

+ 3
- 1
src/dds/build/plan/template.hpp Просмотреть файл

@@ -7,6 +7,8 @@

namespace dds {

class library_root;

class render_template_plan {
/**
* The source file that defines the config template
@@ -30,7 +32,7 @@ public:
/**
* Render the template into its output directory
*/
void render(build_env_ref) const;
void render(build_env_ref, const library_root& owning_library) const;
};

} // namespace dds

+ 2
- 0
src/dds/error/errors.hpp Просмотреть файл

@@ -43,6 +43,8 @@ enum class errc {

invalid_lib_filesystem,
invalid_pkg_filesystem,

template_error,
};

std::string error_reference_of(errc) noexcept;

+ 3
- 0
tests/basics/config_template/simple/library.jsonc Просмотреть файл

@@ -0,0 +1,3 @@
{
"name": "test-library"
}

+ 5
- 0
tests/basics/config_template/simple/package.jsonc Просмотреть файл

@@ -0,0 +1,5 @@
{
"name": "test-simple",
"version": "1.2.3-gamma",
"namespace": "test"
}

+ 5
- 0
tests/basics/config_template/simple/src/simple/config.config.hpp Просмотреть файл

@@ -0,0 +1,5 @@
#pragma once

#include <string_view>

std::string_view lib_name = __dds(lib.name);

+ 5
- 0
tests/basics/config_template/simple/src/simple/simple.test.cpp Просмотреть файл

@@ -0,0 +1,5 @@
#include <simple/config.hpp>

#include <cassert>

int main() { assert(lib_name == "test-library"); }

+ 18
- 1
tests/basics/config_template/test_config_template.py Просмотреть файл

@@ -1,9 +1,26 @@
import pytest
from time import sleep

from tests import DDS, dds_fixture_conf_1


@dds_fixture_conf_1('copy_only')
def test_config_template(dds: DDS):
generated_fpath = dds.build_dir / '__dds/gen/info.hpp'
assert not generated_fpath.is_file()
dds.build()
assert generated_fpath.is_file()

# Check that re-running the build will not update the generated file (the
# file's content has not changed. Re-generating it would invalidate the
# cache and force a false-rebuild.)
start_time = generated_fpath.stat().st_mtime
sleep(0.1) # Wait just long enough to register a new stamp time
dds.build()
new_time = generated_fpath.stat().st_mtime
assert new_time == start_time


@dds_fixture_conf_1('simple')
def test_simple_substitution(dds: DDS):
dds.build()
assert (dds.build_dir / '__dds/gen/info.hpp').is_file()

+ 11
- 0
tools/gen-catalog-json.py Просмотреть файл

@@ -167,6 +167,17 @@ packages = [
'neo-concepts': '^0.2.1',
}),
]),
Package('ctre', [
Version(
'2.7.0',
description=
'A compile-time PCRE (almost) compatible regular expression matcher',
remote=Git(
'https://github.com/hanickadot/compile-time-regular-expressions.git',
'v2.7',
auto_lib='hanickadot/ctre',
))
]),
many_versions(
'spdlog',
(

Загрузка…
Отмена
Сохранить