From 0c9464fe18f9815dbb7034518da5487ccc2600d4 Mon Sep 17 00:00:00 2001 From: RickaPrincy Date: Sun, 15 Jun 2025 18:14:17 +0300 Subject: [PATCH 1/3] feat: cpp_inquirer --- .clang-tidy | 14 + .github/workflows/ci.yml | 12 +- .templates/PKGBUILD/cli/PKGBUILD.in | 2 +- .templates/PKGBUILD/lib/PKGBUILD.in | 2 +- .templates/TempliCliConfig.hpp.in | 5 - CMakeLists.txt | 3 +- PKGBUILD/cli/PKGBUILD | 4 +- PKGBUILD/lib/PKGBUILD | 4 +- include/Templi/Templi.hpp | 22 +- include/Templi/TempliConfig.hpp | 5 - include/Templi/types.hpp | 25 +- sources/cli/CMakeLists.txt | 2 + sources/cli/templi.cpp | 204 ++++++++------- sources/cli/utils.cpp | 107 ++++---- sources/cli/utils.hpp | 14 +- sources/lib/fs_utils.cpp | 237 +++++++++-------- sources/lib/fs_utils.hpp | 25 +- sources/lib/json.hpp | 2 + sources/lib/parser.cpp | 257 ++++++++++--------- sources/lib/parser.hpp | 20 +- sources/lib/templi.cpp | 159 ++++++------ sources/lib/templi_config.cpp | 170 ++++++------ sources/lib/types.cpp | 50 ++-- sources/lib/utils.cpp | 31 +-- sources/lib/utils.hpp | 6 +- templi.json.md | 19 +- tests/CMakeLists.txt | 2 +- tests/cli/configure.cpp | 2 +- tests/fixtures/generate_template/templi.json | 18 +- tests/init.cpp | 2 +- tests/lib/configure.cpp | 5 +- tests/lib/generate_with_templi_config.cpp | 2 +- tests/utils.cpp | 11 +- tests/utils.hpp | 6 +- 34 files changed, 758 insertions(+), 691 deletions(-) create mode 100644 .clang-tidy diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..ab660ca --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,14 @@ +Checks: > + clang-analyzer-*, + modernize-*, + performance-*, + -readability-avoid-endl + -readability-identifier-naming + -performance-move-const-arg + +WarningsAsErrors: '*' +HeaderFilterRegex: '.*' +FormatStyle: file +CheckOptions: + - key: modernize-use-nullptr.NullMacros + value: 'NULL' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba65e1a..097f868 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,9 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - - name: install TColor - run: bash <(curl -s https://raw.githubusercontent.com/RickaPrincy/TColor.hpp/main/install.sh) + + - name: install cpp_inquirer + run: bash <(curl -s https://raw.githubusercontent.com/RickaPrincy/cpp_inquirer/main/install.sh) - name: install rcli run: bash <(curl -s https://raw.githubusercontent.com/RickaPrincy/rcli/main/install.sh) @@ -31,10 +31,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - - name: install TColor - run: bash <(curl -s https://raw.githubusercontent.com/RickaPrincy/TColor.hpp/main/install.sh) + - name: install cpp_inquirer + run: bash <(curl -s https://raw.githubusercontent.com/RickaPrincy/cpp_inquirer/main/install.sh) + - name: install rcli run: bash <(curl -s https://raw.githubusercontent.com/RickaPrincy/rcli/main/install.sh) diff --git a/.templates/PKGBUILD/cli/PKGBUILD.in b/.templates/PKGBUILD/cli/PKGBUILD.in index 1c5de60..b09fa6f 100644 --- a/.templates/PKGBUILD/cli/PKGBUILD.in +++ b/.templates/PKGBUILD/cli/PKGBUILD.in @@ -4,7 +4,7 @@ pkgver=@TEMPLI_CLI_VERSION@ pkgrel=1 pkgdesc="Templi is a tool that simplifies boilerplate creation and usage" arch=('x86_64') -depends=() +depends=('rcli' 'cpp_inquirer') url="https://github.com/RickaPrincy/Templi" license=('MIT') source=("templi-cli-linux-x86_64@$pkgver.tar.gz::https://github.com/RickaPrincy/Templi/releases/download/v@TEMPLI_VERSION@/templi-cli-linux-x86_64@$pkgver.tar.gz") diff --git a/.templates/PKGBUILD/lib/PKGBUILD.in b/.templates/PKGBUILD/lib/PKGBUILD.in index 3a01067..078f0b5 100644 --- a/.templates/PKGBUILD/lib/PKGBUILD.in +++ b/.templates/PKGBUILD/lib/PKGBUILD.in @@ -4,7 +4,7 @@ pkgver=@TEMPLI_LIB_VERSION@ pkgrel=1 pkgdesc="Templi is a tool that simplifies boilerplate creation and usage" arch=('x86_64') -depends=('rcli' 'git') +depends=() url="https://github.com/RickaPrincy/Templi" license=('MIT') source=("templi-cli-linux-x86_64@$pkgver.tar.gz::https://github.com/RickaPrincy/Templi/releases/download/v@TEMPLI_VERSION@/templi-cli-linux-x86_64@$pkgver.tar.gz") diff --git a/.templates/TempliCliConfig.hpp.in b/.templates/TempliCliConfig.hpp.in index 353e10d..c241ab9 100644 --- a/.templates/TempliCliConfig.hpp.in +++ b/.templates/TempliCliConfig.hpp.in @@ -1,6 +1 @@ -// clang-format off -#define Templi_VERSION_MAJOR @Templi_CLI_VERSION_MAJOR@ -#define Templi_VERSION_MINOR @Templi_CLI_VERSION_MINOR@ -#define Templi_VERSION_PATCH @Templi_CLI_VERSION_PATCH@ #define Templi_VERSION "@TEMPLI_CLI_VERSION@" -// clang-format on diff --git a/CMakeLists.txt b/CMakeLists.txt index d6628ce..8203999 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,8 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(rcli REQUIRED VERSION 2.0.22) +find_package(rcli REQUIRED VERSION 2.0.23) +find_package(cpp_inquirer REQUIRED VERSION 0.0.1) set(TEMPLI_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(TEMPLI_IS_MAIN_PROJECT OFF) diff --git a/PKGBUILD/cli/PKGBUILD b/PKGBUILD/cli/PKGBUILD index 8b86dbe..5dfe552 100644 --- a/PKGBUILD/cli/PKGBUILD +++ b/PKGBUILD/cli/PKGBUILD @@ -4,12 +4,12 @@ pkgver=4.1.1 pkgrel=1 pkgdesc="Templi is a tool that simplifies boilerplate creation and usage" arch=('x86_64') -depends=() +depends=('rcli' 'cpp_inquirer') url="https://github.com/RickaPrincy/Templi" license=('MIT') source=("templi-cli-linux-x86_64@$pkgver.tar.gz::https://github.com/RickaPrincy/Templi/releases/download/v4.1.1/templi-cli-linux-x86_64@$pkgver.tar.gz") -sha256sums=("a22e50fd3a1f69050a5ad21d10fdad05c498ebd6b7479207c51eaf176057fae6") +sha256sums=("SHASUM") package() { echo "Installing to \"$pkgdir/usr/\"" diff --git a/PKGBUILD/lib/PKGBUILD b/PKGBUILD/lib/PKGBUILD index a3ca1da..5ee3165 100644 --- a/PKGBUILD/lib/PKGBUILD +++ b/PKGBUILD/lib/PKGBUILD @@ -4,12 +4,12 @@ pkgver=4.1.1 pkgrel=1 pkgdesc="Templi is a tool that simplifies boilerplate creation and usage" arch=('x86_64') -depends=('rcli' 'git') +depends=() url="https://github.com/RickaPrincy/Templi" license=('MIT') source=("templi-cli-linux-x86_64@$pkgver.tar.gz::https://github.com/RickaPrincy/Templi/releases/download/v4.1.1/templi-cli-linux-x86_64@$pkgver.tar.gz") -sha256sums=("369f29d2cb3f00c56889101c06612b0ec3de0a3353fdd5aea6900f11b7b75886") +sha256sums=("SHASUM") package() { echo "Installing to \"$pkgdir/usr/\"" diff --git a/include/Templi/Templi.hpp b/include/Templi/Templi.hpp index 0e4eaea..a17e705 100644 --- a/include/Templi/Templi.hpp +++ b/include/Templi/Templi.hpp @@ -6,16 +6,16 @@ namespace Templi { - void generate(std::string template_path, - std::string output_path, - std::map values, - std::vector ignored_path = {}); + auto generate(const std::string &template_path, + const std::string &output_path, + const std::map &values, + const std::vector &excludes = {}) -> void; - void generate_with_templi_config(std::string template_path, - std::string output_path, - std::function get_placeholder_value); + auto generate_with_templi_config(const std::string &template_path, + const std::string &output_path, + std::function get_placeholder_value) -> void; - void configure(std::string template_path); + auto configure(const std::string &template_path) -> void; class TempliConfig { @@ -23,11 +23,11 @@ namespace Templi std::vector m_excludes{}, m_before{}, m_after{}; std::vector m_placeholders{}; - void read(std::string template_path); - void save(std::string template_path); + auto read(const std::string &template_path) -> void; + auto save(const std::string &template_path) -> void; TempliConfig() = default; - TempliConfig(std::string template_path); + TempliConfig(const std::string &template_path); }; // TempliConfig } // namespace Templi diff --git a/include/Templi/TempliConfig.hpp b/include/Templi/TempliConfig.hpp index e7c203f..e5a3d62 100644 --- a/include/Templi/TempliConfig.hpp +++ b/include/Templi/TempliConfig.hpp @@ -1,6 +1 @@ -// clang-format off -#define Templi_VERSION_MAJOR -#define Templi_VERSION_MINOR -#define Templi_VERSION_PATCH #define Templi_VERSION "4.1.1" -// clang-format on diff --git a/include/Templi/types.hpp b/include/Templi/types.hpp index 40a4852..8bb40e1 100644 --- a/include/Templi/types.hpp +++ b/include/Templi/types.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -13,35 +14,33 @@ namespace Templi std::string m_message; public: - Exception(std::string message) : m_message(message) + Exception(std::string message) : m_message(std::move(message)) { } - const char* what() const noexcept override + + [[nodiscard]] auto what() const noexcept -> const char* override { return m_message.c_str(); } }; // TempliException - // all supported types of placeholder in the templi.json["placeholders"] - enum class PlaceholderType + enum class PlaceholderType : std::uint8_t { - INPUT, - BOOLEAN, - SELECT + TEXT, + SELECT, + BOOLEAN }; // PlaceholderType class Placeholder { public: - std::string m_name{}, m_label{}, m_default{}; - PlaceholderType m_type; + std::string m_name{}, m_label{}; std::vector m_choices{}; - bool m_required{ true }, m_remove_spaces{ true }; + PlaceholderType m_type{ PlaceholderType::TEXT }; Placeholder() = default; - static PlaceholderType placeholdertype_value_of(std::string type); - static std::string placeholdertype_to_string(PlaceholderType type); + static auto placeholdertype_value_of(const std::string& type) -> PlaceholderType; + static auto placeholdertype_to_string(PlaceholderType type) -> std::string; }; // Placeholder - } // namespace Templi diff --git a/sources/cli/CMakeLists.txt b/sources/cli/CMakeLists.txt index 5fef7f5..f5dd210 100644 --- a/sources/cli/CMakeLists.txt +++ b/sources/cli/CMakeLists.txt @@ -6,6 +6,8 @@ add_executable(${TEMPLI_CLI_NAME} ${CLI_SOURCES}) target_include_directories(${TEMPLI_CLI_NAME} PRIVATE ${TEMPLI_SOURCE_DIR}/include) +target_link_libraries(${TEMPLI_CLI_NAME} PRIVATE rcli) +target_link_libraries(${TEMPLI_CLI_NAME} PRIVATE cpp_inquirer) target_link_libraries(${TEMPLI_CLI_NAME} PRIVATE ${TEMPLI_LIB_NAME}) set_target_properties(${TEMPLI_CLI_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY diff --git a/sources/cli/templi.cpp b/sources/cli/templi.cpp index 30944a1..6fbbcf3 100644 --- a/sources/cli/templi.cpp +++ b/sources/cli/templi.cpp @@ -1,120 +1,138 @@ -#include #include #include #include +#include #include -#include -#include -#include +#include +#include #include #include "../lib/fs_utils.hpp" #include "utils.hpp" -using namespace Templi; const std::string GIT_SUFFIX = ".git"; -int main(int argc, const char *argv[]) +namespace Templi { - // base of the application - rcli::App templi("templi", - "Templi is a tool that simplifies boilerplate creation and usage", - Templi_VERSION); - templi.add_informations( - { { "Author", "RickaPrincy" }, { "Github", "" } }); - - rcli::InputConfig config; - config.required(true).clean(true); - - // configure command - rcli::Option template_path_option("-t,--template", "Specify template path", "template_path"); - rcli::Command configure("configure", - "Configure one template to generate templi.json", - [&](rcli::Command *_configure) - { - std::string template_path = _configure->get_option_value("template_path"); - if (template_path.empty()) - template_path = rcli::ask_input_value(config.text("Template path")); - try + auto make_configure_command() -> std::shared_ptr + { + return std::make_shared("configure", + "Configure one template to generate templi.json", + [](rcli::command *_configure) { - Templi::configure(template_path); - TColor::write_endl(TColor::B_GREEN, "[ DONE ]: \"templi.json\" was generated"); - } - catch (Templi::Exception error) - { - std::string message = error.what(); - TColor::write_endl(TColor::B_RED, "[ ERROR ]: " + message); - } - }); - - configure.add_option(&template_path_option); - - // generate command - rcli::Command generate("generate", - "Generate new project with one template", - [&](rcli::Command *_generate) - { - std::string template_path = _generate->get_option_value("template_path"); - std::string output_path = _generate->get_option_value("output_path"); - std::string scope = _generate->get_option_value("scope"); - - if (template_path.empty()) - template_path = rcli::ask_input_value(config.text("Template path (or github url)")); - - if (output_path.empty()) - output_path = rcli::ask_input_value(config.text("Output path")); - - auto is_github_repository = false; - try - { - if (template_path.length() >= GIT_SUFFIX.length()) + std::string template_path = _configure->get_option_value("template_path"); + if (template_path.empty()) { - // test if the template_path is a git repository - if (0 == template_path.compare(template_path.length() - GIT_SUFFIX.length(), - GIT_SUFFIX.length(), - GIT_SUFFIX)) - { - template_path = Templi::clone_template(template_path); - is_github_repository = true; - } + template_path = cpp_inquirer::text_question::prompt( + "Template Path", { cpp_inquirer::validator_factory::required() }); } - std::string valid_template_path = template_path; - if (!scope.empty()) + try { - valid_template_path = - (std::filesystem::path(template_path) / std::filesystem::path(scope)) - .string(); + configure(template_path); + std::cout << "[ DONE ]: \"templi.json\" was generated" << "\n"; } - - Templi::generate_with_templi_config(valid_template_path, - output_path, - [&](Placeholder placeholder) - { return Templi::get_placeholder_value(_generate, placeholder); }); - if (is_github_repository) + catch (Exception error) { - Templi::delete_folder(template_path); + std::cout << "[ ERROR ]: " << error.what() << "\n"; } + }); + } - TColor::write_endl(TColor::B_GREEN, "\n[ DONE ]: Project generated successfully"); - } - catch (Templi::Exception error) + auto make_generate_command() -> std::shared_ptr + { + return std::make_shared("generate", + "Generate new project with one template", + [](rcli::command *_generate) { - std::string message = error.what(); - TColor::write_endl(TColor::B_RED, "[ ERROR ]: " + message); - if (is_github_repository && std::filesystem::exists(template_path)) + std::string template_path = _generate->get_option_value("template_path"); + std::string output_path = _generate->get_option_value("output_path"); + std::string scope = _generate->get_option_value("scope"); + + if (template_path.empty()) { - Templi::delete_folder(template_path); + template_path = cpp_inquirer::text_question::prompt( + "Template Path", { cpp_inquirer::validator_factory::required() }); } - } - }); - generate.add_option(&template_path_option); - generate.add_option("-o,--output", "Specify output path", "output_path"); - generate.add_option("-s, --scope", "Specify which template in the monorepo to use", "scope"); + if (output_path.empty()) + { + output_path = cpp_inquirer::text_question::prompt( + "Output path", { cpp_inquirer::validator_factory::required() }); + } + + auto is_github_repository = false; + try + { + if (template_path.length() >= GIT_SUFFIX.length()) + { + // test if the template_path is a git repository + if (0 == template_path.compare(template_path.length() - GIT_SUFFIX.length(), + GIT_SUFFIX.length(), + GIT_SUFFIX)) + { + template_path = clone_template(template_path); + is_github_repository = true; + } + } + + std::string valid_template_path = template_path; + if (!scope.empty()) + { + valid_template_path = + (std::filesystem::path(template_path) / std::filesystem::path(scope)) + .string(); + } + + generate_with_templi_config(valid_template_path, + output_path, + [&](const Placeholder &placeholder) + { + auto result = get_placeholder_value(_generate, placeholder); + std::cout << "\n"; + return result; + }); + + if (is_github_repository) + { + delete_folder(template_path); + } + std::cout << "\n[ DONE ]: Project generated successfully\n"; + } + catch (Exception error) + { + std::cout << "[ ERROR ]: " << error.what() << "\n"; + if (is_github_repository && std::filesystem::exists(template_path)) + { + delete_folder(template_path); + } + } + }); + } +} // namespace Templi + +auto main(int argc, const char *argv[]) -> int // NOLINT +{ + // base of the application + rcli::app templi("Templi", + "Templi is a tool that simplifies boilerplate creation and usage", + Templi_VERSION); + + rcli::option template_path_option("-t,--template", "Specify template path", "template_path"); + + templi.add_informations( + { { "Author", "RickaPrincy" }, { "Github", "" } }); + + const auto configure_command = Templi::make_configure_command(); + const auto generate_command = Templi::make_generate_command(); + + configure_command->add_option(&template_path_option); + generate_command->add_option(&template_path_option); + generate_command->add_option("-o,--output", "Specify output path", "output_path"); + generate_command->add_option( + "-s, --scope", "Specify which template in the monorepo to use", "scope"); - templi.add_subcommand(&configure); - templi.add_subcommand(&generate); + templi.add_subcommand(&(*configure_command)); + templi.add_subcommand(&(*generate_command)); - templi.run(argc, argv); - return 0; + return templi.run(argc, argv); } diff --git a/sources/cli/utils.cpp b/sources/cli/utils.cpp index 292e357..d03668c 100644 --- a/sources/cli/utils.cpp +++ b/sources/cli/utils.cpp @@ -1,66 +1,79 @@ #include "utils.hpp" -#include #include +#include #include +#include #include #include -#include -#include #include +#include -using namespace Templi; - -std::string Templi::generate_unique_id() +namespace Templi { - auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); - std::mt19937_64 generator(seed); + auto generate_unique_id() -> std::string + { + auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + std::mt19937_64 generator(seed); - std::uniform_int_distribution distribution; - uint64_t randomValue = distribution(generator); - std::stringstream ss; - ss << std::hex << randomValue; - return ss.str(); -} + std::uniform_int_distribution distribution; + uint64_t randomValue = distribution(generator); + std::stringstream ss; + ss << std::hex << randomValue; + return ss.str(); + } -std::string Templi::clone_template(std::string template_path) -{ - std::filesystem::path os_temp_path = std::filesystem::temp_directory_path(); - const std::filesystem::path template_temp_path = - os_temp_path / std::filesystem::path(Templi::generate_unique_id()); - const std::string clone_command = - "git clone " + template_path + " " + template_temp_path.string(); + auto clone_template(const std::string &template_path) -> std::string + { + std::filesystem::path os_temp_path = std::filesystem::temp_directory_path(); + const std::filesystem::path template_temp_path = + os_temp_path / std::filesystem::path(Templi::generate_unique_id()); + const std::string clone_command = + "git clone " + template_path + " " + template_temp_path.string(); - TColor::write_endl( - TColor::B_GREEN, std::string("Cloning the template to " + template_path + " ...")); - std::system(clone_command.c_str()); - return template_temp_path.string(); -} + std::cout << "Cloning the template to " + template_path + " ..." << "\n"; + std::system(clone_command.c_str()); + return template_temp_path.string(); + } -rcli::InputConfig config; -std::string Templi::ask_input_value(const Placeholder &placeholder) -{ - if (placeholder.m_type == PlaceholderType::SELECT) - return rcli::ask_value_in_list_as_number(placeholder.m_label, placeholder.m_choices); + auto ask_input_value(const Placeholder &placeholder) -> std::string + { + const auto copied_placeholder = placeholder.m_label; + if (placeholder.m_type == PlaceholderType::SELECT) + { + return cpp_inquirer::select_question::prompt( + copied_placeholder, vector_to_pair(placeholder.m_choices)); + } - if (placeholder.m_type == PlaceholderType::BOOLEAN) - return rcli::ask_boolean(placeholder.m_label, placeholder.m_default == "true") ? "true" - : "false"; + if (placeholder.m_type == PlaceholderType::BOOLEAN) + { + return cpp_inquirer::boolean_question::prompt(copied_placeholder); + } - config.text(placeholder.m_label) - .default_value(placeholder.m_default) - .required(placeholder.m_required) - .clean(placeholder.m_remove_spaces); + return cpp_inquirer::text_question::prompt(copied_placeholder); + } - return rcli::ask_input_value(config); -} + auto get_placeholder_value(rcli::command *command, const Templi::Placeholder &placeholder) + -> std::string + { + auto option_value = command->get_option_value(placeholder.m_name); + if (option_value.empty()) + { + return Templi::ask_input_value(placeholder); + } + return option_value; + } -std::string Templi::get_placeholder_value(rcli::Command *command, Templi::Placeholder &placeholder) -{ - const auto option_value = command->get_option_value(placeholder.m_name); - if (option_value.empty()) + auto vector_to_pair(const std::vector &vec) + -> std::vector> { - return Templi::ask_input_value(placeholder); + std::vector> result; + result.reserve(vec.size()); + + for (const auto &value : vec) + { + result.emplace_back(value, value); + } + return result; } - return option_value; -} +} // namespace Templi diff --git a/sources/cli/utils.hpp b/sources/cli/utils.hpp index b159b13..7f1142e 100644 --- a/sources/cli/utils.hpp +++ b/sources/cli/utils.hpp @@ -3,11 +3,17 @@ #include #include #include +#include namespace Templi { - std::string generate_unique_id(); - std::string clone_template(std::string template_path); - std::string ask_input_value(const Templi::Placeholder &placeholder); - std::string get_placeholder_value(rcli::Command *command, Templi::Placeholder &placeholder); + auto generate_unique_id() -> std::string; + auto clone_template(const std::string &template_path) -> std::string; + auto ask_input_value(const Templi::Placeholder &placeholder) -> std::string; + + auto get_placeholder_value(rcli::command *command, const Templi::Placeholder &placeholder) + -> std::string; + + auto vector_to_pair(const std::vector &vec) + -> std::vector>; } // namespace Templi diff --git a/sources/lib/fs_utils.cpp b/sources/lib/fs_utils.cpp index 86d15ce..cce39b2 100644 --- a/sources/lib/fs_utils.cpp +++ b/sources/lib/fs_utils.cpp @@ -9,156 +9,167 @@ #include namespace fs = std::filesystem; -using namespace Templi; -static void write_in_open_file(std::ofstream *file, std::string &text) +namespace Templi { - if (file->is_open()) + static auto write_in_open_file(std::ofstream *file, const std::string &text) -> void { - *file << text; - file->close(); - return; + if (file->is_open()) + { + *file << text; + file->close(); + return; + } + throw Exception("Cannot save text to a file which is not open"); } - throw Templi::Exception("Cannot save text to a file which is not open"); -} -void Templi::save_file(std::string path, nlohmann::json text) -{ - std::ofstream file(path); - if (file.is_open()) + auto save_file(const std::string &path, const nlohmann::json &text) -> void { - file << text.dump(2); - return; + std::ofstream file(path); + if (file.is_open()) + { + file << text.dump(2); + return; + } + throw Exception("Cannot save " + path); } - throw Templi::Exception("Cannot save " + path); -} -void Templi::rename(const std::string &old_path, const std::string &new_path) -{ - if (!fs::exists(old_path)) + auto rename(const std::string &old_path, const std::string &new_path) -> void { - throw Templi::Exception("Path does not exist: " + old_path); - } + if (!fs::exists(old_path)) + { + throw Exception("Path does not exist: " + old_path); + } - try - { - fs::rename(old_path, new_path); + try + { + fs::rename(old_path, new_path); + } + catch (const std::filesystem::filesystem_error &e) + { + throw Exception("Cannot rename " + old_path + " to " + new_path + ": " + e.what()); + } } - catch (const std::filesystem::filesystem_error &e) + + auto save_file(const std::string &path, const std::string &text) -> void { - throw Templi::Exception("Cannot rename " + old_path + " to " + new_path + ": " + e.what()); + std::ofstream file(path); + write_in_open_file(&file, text); } -} - -void Templi::save_file(std::string path, std::string text) -{ - std::ofstream file(path); - write_in_open_file(&file, text); -} -void Templi::delete_file(std::string path) -{ - if (std::remove(path.c_str()) != 0) - throw Templi::Exception("Cannot delete " + path); -} - -void Templi::copy_folder(std::string source, std::string destination) -{ - try + auto delete_file(const std::string &path) -> void { - fs::copy(source, - destination, - fs::copy_options::recursive | fs::copy_options::overwrite_existing); + if (std::remove(path.c_str()) != 0) + { + throw Exception("Cannot delete " + path); + } } - catch (const std::exception &e) + + auto copy_folder(const std::string &source, const std::string &destination) -> void { - throw Templi::Exception("Error when try to copy " + source + " to " + destination); + try + { + fs::copy(source, + destination, + fs::copy_options::recursive | fs::copy_options::overwrite_existing); + } + catch (const std::exception &e) + { + throw Exception("Error when try to copy " + source + " to " + destination); + } } -} -void Templi::process_for_each_line(std::string path, - std::function process) -{ - std::ifstream file(path); - - if (!file.is_open()) - throw Templi::Exception("Cannot open " + path); - - if (file.good()) + auto process_for_each_line(const std::string &path, + std::function process) -> void // NOLINT { - std::string line; - while (std::getline(file, line)) - process(line); - } + std::ifstream file(path); - file.close(); -} + if (!file.is_open()) + { + throw Exception("Cannot open " + path); + } -void Templi::delete_folder(std::string path) -{ - if (!fs::exists(path)) - { - return; - } + if (file.good()) + { + std::string line; + while (std::getline(file, line)) + { + process(line); + } + } - try - { - fs::remove_all(path); - } - catch (const std::filesystem::filesystem_error &e) - { - std::string message = e.what(); - throw Templi::Exception(message); + file.close(); } -} -static void get_folder_files_process(const std::string &path, - std::vector &result, - const std::vector &exclude_paths) -{ - auto it = std::find(exclude_paths.begin(), exclude_paths.end(), path); - if (it != exclude_paths.end()) + auto delete_folder(const std::string &path) -> void { - return; - } + if (!fs::exists(path)) + { + return; + } - if (!fs::exists(path) || !fs::is_directory(path)) - { - throw std::runtime_error(path + " is not a folder"); + try + { + fs::remove_all(path); + } + catch (const std::filesystem::filesystem_error &e) + { + std::string message = e.what(); + throw Exception(message); + } } - for (const auto &file : fs::directory_iterator(path)) + static auto get_folder_files_process(const std::string &path, + std::vector &result, + const std::vector &exclude_paths) -> void { - std::string file_path = file.path().string(); - auto it = std::find(exclude_paths.begin(), exclude_paths.end(), file_path); + auto it = std::find(exclude_paths.begin(), exclude_paths.end(), path); if (it != exclude_paths.end()) { - continue; + return; } - if (fs::is_directory(file_path)) + if (!fs::exists(path) || !fs::is_directory(path)) { - get_folder_files_process(file_path, result, exclude_paths); + throw std::runtime_error(path + " is not a folder"); } - else if (fs::is_regular_file(file_path)) + + for (const auto &file : fs::directory_iterator(path)) { - result.push_back(file_path); + std::string file_path = file.path().string(); + auto it = std::find(exclude_paths.begin(), exclude_paths.end(), file_path); + if (it != exclude_paths.end()) + { + continue; + } + + if (fs::is_directory(file_path)) + { + get_folder_files_process(file_path, result, exclude_paths); + } + else if (fs::is_regular_file(file_path)) + { + result.push_back(file_path); + } } } -} -std::vector Templi::get_files_with_placeholder(std::string template_path, - std::vector exclude_paths) -{ - std::vector results{}; - std::vector relative_exclude_paths{}; - - std::transform(exclude_paths.begin(), - exclude_paths.end(), - std::back_inserter(relative_exclude_paths), - [&](const std::string path) - { return (std::filesystem::path(template_path) / std::filesystem::path(path)).string(); }); - - get_folder_files_process(template_path, results, relative_exclude_paths); - return results; -} + auto get_files_with_placeholder(const std::string &template_path, + const std::vector &exclude_paths) -> std::vector + { + std::vector results{}; + std::vector relative_exclude_paths{}; + + std::transform(exclude_paths.begin(), + exclude_paths.end(), + std::back_inserter(relative_exclude_paths), + [&](const std::string &path) + { + return (std::filesystem::path(template_path) / std::filesystem::path(path)) + .string(); + }); + + get_folder_files_process(template_path, results, relative_exclude_paths); + return results; + } +} // namespace Templi diff --git a/sources/lib/fs_utils.hpp b/sources/lib/fs_utils.hpp index df421cb..cbba29b 100644 --- a/sources/lib/fs_utils.hpp +++ b/sources/lib/fs_utils.hpp @@ -8,18 +8,19 @@ namespace Templi { - void save_file(std::string path, std::string text); - void save_file(std::string path, nlohmann::json text); - void delete_file(std::string path); - void delete_folder(std::string path); - void rename(const std::string& old_path, const std::string& new_path); + auto delete_file(const std::string& path) -> void; + auto delete_folder(const std::string& path) -> void; + auto save_file(const std::string& path, const std::string& text) -> void; + auto save_file(const std::string& path, const nlohmann::json& text) -> void; + auto rename(const std::string& old_path, const std::string& new_path) -> void; + auto copy_folder(const std::string& source, const std::string& destination) -> void; - void copy_folder(std::string source, std::string destination); - std::vector get_files_with_placeholder(std::string path, - std::vector exclude_path = {}); - std::vector process_each_files(std::string path, - std::vector exclude_path = {}); + auto get_files_with_placeholder(const std::string& path, + const std::vector& exclude_path = {}) -> std::vector; - void process_for_each_line(std::string path, - std::function process); + auto process_each_files(const std::string& path, + const std::vector& exclude_path = {}) -> std::vector; + + auto process_for_each_line(const std::string& path, + std::function process) -> void; } // namespace Templi diff --git a/sources/lib/json.hpp b/sources/lib/json.hpp index 8b72ea6..d210c71 100644 --- a/sources/lib/json.hpp +++ b/sources/lib/json.hpp @@ -1,3 +1,4 @@ +// NOLINTBEGIN // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.11.3 @@ -24763,3 +24764,4 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC #endif // INCLUDE_NLOHMANN_JSON_HPP_ +// NOLINTEND diff --git a/sources/lib/parser.cpp b/sources/lib/parser.cpp index d4e4f30..f512f25 100644 --- a/sources/lib/parser.cpp +++ b/sources/lib/parser.cpp @@ -5,154 +5,159 @@ #include "fs_utils.hpp" -using namespace Templi; namespace fs = std::filesystem; -// TODO: refactor -static std::string process_placeholder(std::string text, - const std::map &values, - std::set &words) +namespace Templi { - std::string result; - size_t pos = 0; - - while (pos < text.length()) + static auto process_placeholder(const std::string &text, + const std::map &values, + std::set &words) -> std::string { - size_t open_brace_pos = text.find("{{", pos); - size_t escape_pos = text.find("\\{", pos); - - // Handle \{ - if (escape_pos != std::string::npos && - (open_brace_pos == std::string::npos || escape_pos < open_brace_pos)) - { - result += text.substr(pos, escape_pos - pos) + '{'; - pos = escape_pos + 2; - continue; - } + std::string result; + size_t pos = 0; - // If there's no more {{, just add the remaining part of the text to result - if (open_brace_pos == std::string::npos) + while (pos < text.length()) { - result += text.substr(pos); - break; + size_t open_brace_pos = text.find("{{", pos); + size_t escape_pos = text.find("\\{", pos); + + // Handle \{ + if (escape_pos != std::string::npos && + (open_brace_pos == std::string::npos || escape_pos < open_brace_pos)) + { + result += text.substr(pos, escape_pos - pos) + '{'; + pos = escape_pos + 2; + continue; + } + + // If there's no more {{, just add the remaining part of the text to result + if (open_brace_pos == std::string::npos) + { + result += text.substr(pos); + break; + } + + // Add the text before {{ to the result + result += text.substr(pos, open_brace_pos - pos); + + // Find the closing braces }} + size_t start_pos = open_brace_pos + 2; + size_t end_pos = text.find("}}", start_pos); + + if (end_pos == std::string::npos) + { + result += text.substr(open_brace_pos); + break; + } + + size_t word_length = end_pos - start_pos; + std::string word = text.substr(start_pos, word_length); + + words.insert(word); + auto it = values.find(word); + + if (it != values.end()) + { + result += it->second; + pos = end_pos + 2; + } + else + { + result += "{{" + word + "}}"; + pos = end_pos + 2; + } } - // Add the text before {{ to the result - result += text.substr(pos, open_brace_pos - pos); - - // Find the closing braces }} - size_t start_pos = open_brace_pos + 2; - size_t end_pos = text.find("}}", start_pos); - - if (end_pos == std::string::npos) - { - result += text.substr(open_brace_pos); - break; - } + return result; + } - size_t word_length = end_pos - start_pos; - std::string word = text.substr(start_pos, word_length); + auto extract_placeholders_from_text(const std::string &text) -> std::set + { + std::set words; + process_placeholder(text, {}, words); + return words; + } - words.insert(word); - auto it = values.find(word); + auto replace_placeholders_in_text(const std::string &text, + const std::map &values) -> std::string + { + std::set words; + return process_placeholder(text, values, words); + } - if (it != values.end()) - { - result += it->second; - pos = end_pos + 2; - } - else - { - result += "{{" + word + "}}"; - pos = end_pos + 2; - } + auto extract_placeholders_from_file(const std::string &file_path) -> std::set + { + std::set words; + process_for_each_line(file_path, + [&](const std::string &line_content) + { + for (const auto &word : extract_placeholders_from_text(line_content)) + { + words.insert(word); + } + }); + return words; } - return result; -} + auto replace_placeholders_in_file(const std::string &file_path, + const std::string &output_path, + const std::map &values) -> void + { + std::stringstream file_parsed_content; -std::set Templi::extract_placeholders_from_text(std::string text) -{ - std::set words; - process_placeholder(text, {}, words); - return words; -} + process_for_each_line(file_path, + [&](const std::string &line_content) + { file_parsed_content << replace_placeholders_in_text(line_content, values) << "\n"; }); -std::string Templi::replace_placeholders_in_text(std::string text, - std::map values) -{ - std::set words; - return process_placeholder(text, values, words); -} + save_file(output_path, file_parsed_content.str()); + } -std::set Templi::extract_placeholders_from_file(std::string file_path) -{ - std::set words; - process_for_each_line(file_path, - [&](const std::string &line_content) + static void replace_folder_filename_placeholders_process(const std::string &folder_path, + const std::map &values, + const std::vector &exclude_paths) + { + fs::path filepath = fs::path(folder_path); + auto it = std::find(exclude_paths.begin(), exclude_paths.end(), filepath.string()); + if (it != exclude_paths.end()) { - for (auto word : extract_placeholders_from_text(line_content)) - words.insert(word); - }); - return words; -} - -void Templi::replace_placeholders_in_file(std::string file_path, - std::string output_path, - std::map values) -{ - std::stringstream file_parsed_content; - - process_for_each_line(file_path, - [&](const std::string &line_content) - { file_parsed_content << replace_placeholders_in_text(line_content, values) << "\n"; }); - - save_file(output_path, file_parsed_content.str()); -} + return; + } -static void replace_folder_filename_placeholders_process(std::string folder_path, - std::map values, - std::vector exclude_paths) -{ - fs::path filepath = fs::path(folder_path); - auto it = std::find(exclude_paths.begin(), exclude_paths.end(), filepath.string()); - if (it != exclude_paths.end()) - { - return; - } + std::string filename = filepath.filename(); + std::string new_file_name = replace_placeholders_in_text(filename, values); - std::string filename = filepath.filename(); - std::string new_file_name = Templi::replace_placeholders_in_text(filename, values); + if (filename != new_file_name) + { + fs::path new_path = filepath.parent_path() / new_file_name; + rename(filepath.string(), new_path.string()); + return replace_folder_filename_placeholders_process(new_path, values, exclude_paths); + } - if (filename != new_file_name) - { - fs::path new_path = filepath.parent_path() / new_file_name; - Templi::rename(filepath.string(), new_path.string()); - return replace_folder_filename_placeholders_process(new_path, values, exclude_paths); - } + if (!fs::exists(filepath) || !fs::is_directory(filepath)) + { + return; + } - if (!fs::exists(filepath) || !fs::is_directory(filepath)) - { - return; + for (const auto &child_file : fs::directory_iterator(filepath)) + { + replace_folder_filename_placeholders_process( + child_file.path().string(), values, exclude_paths); + } } - for (const auto &child_file : fs::directory_iterator(filepath)) + auto replace_folder_filename_placeholders(const std::string &folder_path, + const std::map &values, + const std::vector &exclude_paths) -> void { - replace_folder_filename_placeholders_process( - child_file.path().string(), values, exclude_paths); + std::vector relative_exclude_paths{}; + std::transform(exclude_paths.begin(), + exclude_paths.end(), + std::back_inserter(relative_exclude_paths), + [&](const std::string &path) + { + return (std::filesystem::path(folder_path) / std::filesystem::path(path)).string(); + }); + + replace_folder_filename_placeholders_process(folder_path, values, relative_exclude_paths); } -} - -void Templi::replace_folder_filename_placeholders(std::string folder_path, - std::map values, - std::vector exclude_paths) -{ - std::vector relative_exclude_paths{}; - std::transform(exclude_paths.begin(), - exclude_paths.end(), - std::back_inserter(relative_exclude_paths), - [&](const std::string path) - { return (std::filesystem::path(folder_path) / std::filesystem::path(path)).string(); }); - - replace_folder_filename_placeholders_process(folder_path, values, relative_exclude_paths); -} +} // namespace Templi diff --git a/sources/lib/parser.hpp b/sources/lib/parser.hpp index c5741ac..be96204 100644 --- a/sources/lib/parser.hpp +++ b/sources/lib/parser.hpp @@ -7,18 +7,18 @@ namespace Templi { - std::set extract_placeholders_from_text(std::string text); + auto extract_placeholders_from_text(const std::string &text) -> std::set; - std::string replace_placeholders_in_text(std::string text, - std::map values); + auto extract_placeholders_from_file(const std::string &file_path) -> std::set; - std::set extract_placeholders_from_file(std::string file_path); + auto replace_placeholders_in_text(const std::string &text, + const std::map &values) -> std::string; - void replace_placeholders_in_file(std::string file_path, - std::string output_path, - std::map values); + auto replace_placeholders_in_file(const std::string &file_path, + const std::string &output_path, + const std::map &values) -> void; - void replace_folder_filename_placeholders(std::string folder_path, - std::map values, - std::vector excludes); + auto replace_folder_filename_placeholders(const std::string &folder_path, + const std::map &values, + const std::vector &excludes) -> void; } // namespace Templi diff --git a/sources/lib/templi.cpp b/sources/lib/templi.cpp index abe875e..a58a66a 100644 --- a/sources/lib/templi.cpp +++ b/sources/lib/templi.cpp @@ -1,96 +1,107 @@ -#include #include #include #include #include -#include #include "fs_utils.hpp" #include "parser.hpp" #include "utils.hpp" -using namespace Templi; - static const std::string GIT_SUFFIX = ".git"; -void Templi::generate(std::string template_path, - std::string output_path, - std::map values, - std::vector ignored_path) +namespace Templi { - Templi::copy_folder(template_path, output_path); - - std::vector files = Templi::get_files_with_placeholder(output_path, ignored_path); - - if (files.empty()) - throw Templi::Exception("Folder empty or no words inside {{}} was found"); - - for (auto file : files) + auto generate(const std::string &template_path, + const std::string &output_path, + const std::map &values, + const std::vector &excludes) -> void { - Templi::replace_placeholders_in_file(file, file, values); - } - Templi::replace_folder_filename_placeholders(output_path, values, ignored_path); -} + copy_folder(template_path, output_path); -void Templi::configure(std::string template_path) -{ - TempliConfig templi_config; - templi_config.m_excludes.push_back(TEMPLI_CONFIG_NAME); + std::vector files = get_files_with_placeholder(output_path, excludes); - if (std::filesystem::exists(Templi::create_config_path(template_path))) - { - TempliConfig configure_templi_config(template_path); - templi_config.m_excludes = configure_templi_config.m_excludes; - } + if (files.empty()) + { + throw Exception("Folder empty or no words inside {{}} was found"); + } - std::vector files = - Templi::get_files_with_placeholder(template_path, templi_config.m_excludes); - std::set words{}; - - if (files.empty()) - throw Templi::Exception("Folder empty or no words inside {{}} was found"); + for (const auto &file : files) + { + replace_placeholders_in_file(file, file, values); + } + replace_folder_filename_placeholders(output_path, values, excludes); + } - for (auto file : files) + auto configure(const std::string &template_path) -> void { - std::set words_found_in_file_name = - Templi::extract_placeholders_from_text(file); - std::set words_found = Templi::extract_placeholders_from_file(file); - - for (auto word_found : words_found_in_file_name) - words_found.insert(word_found); - - if (words_found.empty()) - templi_config.m_excludes.push_back(file.substr(template_path.size() + 1)); - - for (auto word_found : words_found) - words.insert(word_found); + TempliConfig templi_config; + templi_config.m_excludes.emplace_back(TEMPLI_CONFIG_NAME); + + if (std::filesystem::exists(create_config_path(template_path))) + { + TempliConfig configure_templi_config(template_path); + templi_config.m_excludes = configure_templi_config.m_excludes; + } + + std::vector files = + get_files_with_placeholder(template_path, templi_config.m_excludes); + std::set words{}; + + if (files.empty()) + { + throw Exception("Folder empty or no words inside {{}} was found"); + } + + for (const auto &file : files) + { + std::set words_found_in_file_name = extract_placeholders_from_text(file); + std::set words_found = extract_placeholders_from_file(file); + + for (const auto &word_found : words_found_in_file_name) + { + words_found.insert(word_found); + } + + if (words_found.empty()) + { + templi_config.m_excludes.push_back(file.substr(template_path.size() + 1)); + } + + for (const auto &word_found : words_found) + { + words.insert(word_found); + } + } + + for (const auto &word : words) + { + Placeholder new_placeholder; + new_placeholder.m_label = word; + new_placeholder.m_name = word; + new_placeholder.m_type = PlaceholderType::TEXT; + templi_config.m_placeholders.push_back(new_placeholder); + } + templi_config.save(template_path); } - for (auto word : words) + auto generate_with_templi_config(const std::string &template_path, + const std::string &output_path, + std::function get_placeholder_value) -> void { - Placeholder new_placeholder; - new_placeholder.m_label = "What is the value of : " + word + " ?"; - new_placeholder.m_name = word; - new_placeholder.m_type = PlaceholderType::INPUT; - templi_config.m_placeholders.push_back(new_placeholder); + std::map values{ { "TEMPLI_OUTPUT_FOLDER", output_path } }; + TempliConfig templi_config(template_path); + + std::for_each(templi_config.m_placeholders.begin(), + templi_config.m_placeholders.end(), + [&](Placeholder &placeholder) + { + values.insert( + std::make_pair(placeholder.m_name, get_placeholder_value(placeholder))); + }); + + execute_scripts(values, templi_config.m_before); + generate(template_path, output_path, values, templi_config.m_excludes); + delete_file(create_config_path(output_path)); + execute_scripts(values, templi_config.m_after); } - templi_config.save(template_path); -} - -void Templi::generate_with_templi_config(std::string template_path, - std::string output_path, - std::function get_placeholder_value) -{ - std::map values{ { "TEMPLI_OUTPUT_FOLDER", output_path } }; - TempliConfig templi_config(template_path); - - std::for_each(templi_config.m_placeholders.begin(), - templi_config.m_placeholders.end(), - [&](Placeholder &placeholder) - { values.insert(std::make_pair(placeholder.m_name, get_placeholder_value(placeholder))); }); - - Templi::execute_scripts(values, templi_config.m_before); - Templi::generate(template_path, output_path, values, templi_config.m_excludes); - Templi::delete_file(Templi::create_config_path(output_path)); - Templi::execute_scripts(values, templi_config.m_after); -} +} // namespace Templi diff --git a/sources/lib/templi_config.cpp b/sources/lib/templi_config.cpp index 75ea365..46a4c80 100644 --- a/sources/lib/templi_config.cpp +++ b/sources/lib/templi_config.cpp @@ -5,104 +5,108 @@ #include "fs_utils.hpp" #include "utils.hpp" -using namespace Templi; - using json = nlohmann::json; -Templi::TempliConfig::TempliConfig(std::string template_path) -{ - this->read(template_path); -} - -void Templi::TempliConfig::read(std::string template_path) +namespace Templi { - const std::string config_full_path = Templi::create_config_path(template_path); - std::ifstream config_file(config_full_path); - json config_json; - - if (!config_file.is_open()) - throw Templi::Exception("Cannot open " + config_full_path); - - try - { - config_file >> config_json; - config_file.close(); - } - catch (const json::parse_error &e) + TempliConfig::TempliConfig(const std::string &template_path) { - throw Templi::Exception(config_full_path + " is not a valid json file"); + this->read(template_path); } - try + auto TempliConfig::read(const std::string &template_path) -> void { - m_excludes = config_json["excludes"]; - if (config_json["scripts"].is_object()) + const std::string config_full_path = create_config_path(template_path); + std::ifstream config_file(config_full_path); + json config_json; + + if (!config_file.is_open()) { - json scripts = config_json["scripts"]; - m_before = scripts["before"]; - m_after = scripts["after"]; + throw Exception("Cannot open " + config_full_path); } - for (auto placeholder : config_json["placeholders"]) + try { - Placeholder new_placeholder; - new_placeholder.m_name = placeholder["name"]; - new_placeholder.m_label = placeholder["label"]; - new_placeholder.m_type = Placeholder::placeholdertype_value_of(placeholder["type"]); - - if (new_placeholder.m_type == PlaceholderType::SELECT) - new_placeholder.m_choices = placeholder["choices"]; - if (placeholder["required"].is_boolean()) - new_placeholder.m_required = placeholder["required"]; - if (placeholder["remove_spaces"].is_boolean()) - new_placeholder.m_remove_spaces = placeholder["remove_spaces"]; - if (placeholder["default"].is_string() && !placeholder["default"].empty()) - new_placeholder.m_default = placeholder["default"]; - this->m_placeholders.push_back(new_placeholder); + config_file >> config_json; + config_file.close(); + } + catch (const json::parse_error &e) + { + throw Exception(config_full_path + " is not a valid json file"); + } + + try + { + m_excludes = config_json["excludes"]; + if (config_json["scripts"].is_object()) + { + json scripts = config_json["scripts"]; + + if (scripts["before"].is_array()) + { + m_before = scripts["before"]; + } + + if (scripts["after"].is_array()) + { + m_after = scripts["after"]; + } + } + + for (auto placeholder : config_json["placeholders"]) + { + Placeholder new_placeholder; + new_placeholder.m_name = placeholder["name"]; + new_placeholder.m_label = placeholder["label"]; + new_placeholder.m_type = Placeholder::placeholdertype_value_of(placeholder["type"]); + + if (new_placeholder.m_type == PlaceholderType::SELECT) + { + new_placeholder.m_choices = placeholder["choices"]; + } + + this->m_placeholders.push_back(new_placeholder); + } + } + catch (const json::exception &error) + { + throw Exception(config_full_path + + " is not a valid config file (ref: " + "https://github.com/RickaPrincy/Templi/blob/main/templi.json.md)"); } } - catch (const json::exception &error) - { - throw Templi::Exception(config_full_path + - " is not a valid config file (ref: " - "https://github.com/RickaPrincy/Templi/blob/main/templi.json.md)"); - } -} -void Templi::TempliConfig::save(std::string template_path) -{ - const std::string config_full_path = Templi::create_config_path(template_path); - json new_config_json = json::object(); - json placeholders_json = json::array(); + auto TempliConfig::save(const std::string &template_path) -> void + { + const std::string config_full_path = create_config_path(template_path); + json new_config_json = json::object(); + json placeholders_json = json::array(); - new_config_json["excludes"] = m_excludes; + new_config_json["excludes"] = m_excludes; - if (!m_before.empty() || !m_after.empty()) - { - json scripts = json::object(); - scripts["before"] = m_before; - scripts["after"] = m_before; - new_config_json["scripts"] = scripts; - } + if (!m_before.empty() || !m_after.empty()) + { + json scripts = json::object(); + scripts["before"] = m_before; + scripts["after"] = m_before; + new_config_json["scripts"] = scripts; + } - for (auto placeholder : this->m_placeholders) - { - json new_placeholder = json::object(); - new_placeholder["name"] = placeholder.m_name; - new_placeholder["label"] = placeholder.m_label; - new_placeholder["type"] = Placeholder::placeholdertype_to_string(placeholder.m_type); - - if (placeholder.m_required) - new_placeholder["required"] = placeholder.m_required; - if (!placeholder.m_remove_spaces) - new_placeholder["remove_spaces"] = placeholder.m_remove_spaces; - if (placeholder.m_type == PlaceholderType::SELECT) - new_placeholder["choices"] = placeholder.m_choices; - if (!placeholder.m_default.empty()) - new_placeholder["default"] = placeholder.m_default; - placeholders_json.push_back(new_placeholder); - } - new_config_json["placeholders"] = placeholders_json; + for (auto placeholder : this->m_placeholders) + { + json new_placeholder = json::object(); + new_placeholder["name"] = placeholder.m_name; + new_placeholder["label"] = placeholder.m_label; + new_placeholder["type"] = Placeholder::placeholdertype_to_string(placeholder.m_type); + + if (placeholder.m_type == PlaceholderType::SELECT) + { + new_placeholder["choices"] = placeholder.m_choices; + } + placeholders_json.push_back(new_placeholder); + } + new_config_json["placeholders"] = placeholders_json; - Templi::save_file(config_full_path, new_config_json); -}; + save_file(config_full_path, new_config_json); + }; +} // namespace Templi diff --git a/sources/lib/types.cpp b/sources/lib/types.cpp index 5ae132d..0c80a1c 100644 --- a/sources/lib/types.cpp +++ b/sources/lib/types.cpp @@ -1,30 +1,32 @@ #include #include -using namespace Templi; - -// placeholder method -std::string Templi::Placeholder::placeholdertype_to_string(PlaceholderType type) +namespace Templi { - switch (type) + // placeholder method + auto Templi::Placeholder::placeholdertype_to_string(PlaceholderType type) -> std::string { - case PlaceholderType::BOOLEAN: return "BOOLEAN"; - case PlaceholderType::SELECT: return "SELECT"; - case PlaceholderType::INPUT: return "INPUT"; - default: - throw Templi::Exception( - "Invalid placeholder type, these are the valid values [ SELECT, INPUT, BOOLEAN ]"); - } -}; + switch (type) + { + case PlaceholderType::BOOLEAN: return "BOOLEAN"; + case PlaceholderType::SELECT: return "SELECT"; + case PlaceholderType::TEXT: return "TEXT"; + default: + throw Templi::Exception( + "Invalid placeholder type, these are the valid values [ SELECT, TEXT, BOOLEAN " + "]"); + } + }; -PlaceholderType Templi::Placeholder::placeholdertype_value_of(std::string type) -{ - if (type == "SELECT") - return PlaceholderType::SELECT; - if (type == "BOOLEAN") - return PlaceholderType::BOOLEAN; - if (type == "INPUT") - return PlaceholderType::INPUT; - throw Templi::Exception( - "Invalid placeholder type, these are the valid values [ SELECT, INPUT, BOOLEAN ]"); -}; + auto Templi::Placeholder::placeholdertype_value_of(const std::string &type) -> PlaceholderType + { + if (type == "SELECT") + return PlaceholderType::SELECT; + if (type == "BOOLEAN") + return PlaceholderType::BOOLEAN; + if (type == "TEXT") + return PlaceholderType::TEXT; + throw Templi::Exception( + "Invalid placeholder type, these are the valid values [ SELECT, TEXT, BOOLEAN ]"); + }; +} // namespace Templi diff --git a/sources/lib/utils.cpp b/sources/lib/utils.cpp index 16a3653..4be5f51 100644 --- a/sources/lib/utils.cpp +++ b/sources/lib/utils.cpp @@ -1,29 +1,30 @@ #include "utils.hpp" -#include #include #include #include #include #include +#include #include "parser.hpp" -using namespace Templi; - -std::string Templi::create_config_path(std::string template_path) +namespace Templi { - return (std::filesystem::path(template_path) / std::filesystem::path(TEMPLI_CONFIG_NAME)) - .string(); -} + auto create_config_path(const std::string &template_path) -> std::string + { + return (std::filesystem::path(template_path) / std::filesystem::path(TEMPLI_CONFIG_NAME)) + .string(); + } -void Templi::execute_scripts(const std::map &values, - const std::vector &scripts) -{ - for (const auto script : scripts) + void execute_scripts(const std::map &values, + const std::vector &scripts) { - std::string command = Templi::replace_placeholders_in_text(script, values); - TColor::write_endl(TColor::B_WHITE, command); - std::system(command.c_str()); + for (const auto &script : scripts) + { + std::string command = Templi::replace_placeholders_in_text(script, values); + std::cout << command << "\n"; + std::system(command.c_str()); + } } -} +} // namespace Templi diff --git a/sources/lib/utils.hpp b/sources/lib/utils.hpp index f9590b4..d5f119c 100644 --- a/sources/lib/utils.hpp +++ b/sources/lib/utils.hpp @@ -7,8 +7,8 @@ namespace Templi { - std::string create_config_path(std::string template_path); + auto create_config_path(const std::string &template_path) -> std::string; - void execute_scripts(const std::map &values, - const std::vector &scripts); + auto execute_scripts(const std::map &values, + const std::vector &scripts) -> void; } // namespace Templi diff --git a/templi.json.md b/templi.json.md index f09c775..2104d23 100644 --- a/templi.json.md +++ b/templi.json.md @@ -23,10 +23,7 @@ Example: { "name": "author_name", "label": "Who is the author", - "type": "INPUT", - "default": "RickaPrincy", - "required": true, - "remove_spaces": true + "type": "TEXT" }, { "name": "project_name", @@ -38,7 +35,6 @@ Example: "name": "is_ok", "label": "Are you ok ?", "type": "BOOLEAN", - "default": "false" } ] } @@ -55,22 +51,13 @@ Attributes of `templi.json`: - **type**: `string` - **`type`**: - **description**: Determines the method Templi will use to prompt the user for the value of this key. - - **type**: `"input"` | `"boolean"` | `"select"` - - **`INPUT`**: Presents a standard text input prompt to the user. + - **type**: `"text"` | `"boolean"` | `"select"` + - **`TEXT`**: Presents a standard text input prompt to the user. - **`BOOLEAN`**: Asks the user for a yes/no response (`y` or `n`), which will be interpreted as `true` or `false`. - **`SELECT`**: Presents a list of `choices` to the user, allowing them to select one value. The `choices` should be defined in the `choices` attribute of the key. - **`label`**: - **description**: The text displayed to the user as the prompt for this key's value. - **type**: `string` - - **`default`** (optional): - - **description**: The value that will be used if the user leaves the input empty. - - **type**: `string` (if `type` is `"input"` or `"select"`) or `boolean` (if `type` is `"boolean"`) - - **`required`** (optional): - - **description**: A boolean value indicating whether the user must provide a value for this key. If set to `true`, the user cannot leave the prompt empty (note: this has no effect if a `default` value is provided). - - **type**: `boolean` - - **`remove_spaces`** (optional): - - **description**: A boolean value. If set to `true`, all whitespace characters will be removed from the user's input for this key. This is useful for ensuring clean values without spaces. - - **type**: `boolean` - **`excludes`** (optional): - **description**: An array of strings, where each string is a file path or pattern to exclude from Templi's processing. This is beneficial for optimizing performance by skipping large or irrelevant files within your template directory. - **type**: `array` of `string` diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3d44138..6684f44 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,7 +15,7 @@ target_link_directories(${TEMPLI_TEST_NAME} PUBLIC ${CMAKE_BINARY_DIR}/lib) target_link_libraries( ${TEMPLI_TEST_NAME} - PUBLIC ${TEMPLI_LIB_NAME} + PRIVATE ${TEMPLI_LIB_NAME} PUBLIC gtest) set_target_properties( diff --git a/tests/cli/configure.cpp b/tests/cli/configure.cpp index 40ca33d..51f17f7 100644 --- a/tests/cli/configure.cpp +++ b/tests/cli/configure.cpp @@ -43,7 +43,7 @@ TEST(TempliCli_configure, fixtures) for (size_t i = 0; i < generated_config.m_placeholders.size(); i++) { ASSERT_EQ(generated_config.m_placeholders[i].m_name, expected_placeholders_names[i]); - ASSERT_EQ(generated_config.m_placeholders[i].m_type, PlaceholderType::INPUT); + ASSERT_EQ(generated_config.m_placeholders[i].m_type, PlaceholderType::TEXT); } std::filesystem::remove(generated_config_path); } diff --git a/tests/fixtures/generate_template/templi.json b/tests/fixtures/generate_template/templi.json index ecd847f..54f4a09 100644 --- a/tests/fixtures/generate_template/templi.json +++ b/tests/fixtures/generate_template/templi.json @@ -17,47 +17,47 @@ { "label": "ANOTHER", "name": "ANOTHER", - "type": "INPUT" + "type": "TEXT" }, { "label": "ANOTHER FILE", "name": "ANOTHER_FILE", - "type": "INPUT" + "type": "TEXT" }, { "label": "AUTHOR VALUE", "name": "AUTHOR", - "type": "INPUT" + "type": "TEXT" }, { "label": "DESCRIPTION VALUE", "name": "DESCRIPTION", - "type": "INPUT" + "type": "TEXT" }, { "label": "EMAIL VALUE", "name": "EMAIL", - "type": "INPUT" + "type": "TEXT" }, { "label": "FILE", "name": "FILE", - "type": "INPUT" + "type": "TEXT" }, { "label": "GIT_URL VALUE", "name": "GIT_URL", - "type": "INPUT" + "type": "TEXT" }, { "label": "PROJECT_NAME VALUE", "name": "PROJECT_NAME", - "type": "INPUT" + "type": "TEXT" }, { "label": "VERSION VALUE", "name": "VERSION", - "type": "INPUT" + "type": "TEXT" } ] } diff --git a/tests/init.cpp b/tests/init.cpp index ad9f619..8a5fd66 100644 --- a/tests/init.cpp +++ b/tests/init.cpp @@ -1,6 +1,6 @@ #include -int main(int argc, char **argv) +auto main(int argc, char **argv) -> int { ::testing::InitGoogleTest(); return RUN_ALL_TESTS(); diff --git a/tests/lib/configure.cpp b/tests/lib/configure.cpp index 1702296..822cffe 100644 --- a/tests/lib/configure.cpp +++ b/tests/lib/configure.cpp @@ -40,9 +40,8 @@ TEST(TempliLib_configure, fixtures) { auto name = generated_config.m_placeholders[i].m_name; ASSERT_EQ(name, expected_placeholders_names[i]); - ASSERT_EQ(generated_config.m_placeholders[i].m_type, PlaceholderType::INPUT); - ASSERT_EQ( - generated_config.m_placeholders[i].m_label, "What is the value of : " + name + " ?"); + ASSERT_EQ(generated_config.m_placeholders[i].m_type, PlaceholderType::TEXT); + ASSERT_EQ(generated_config.m_placeholders[i].m_label, name); } std::filesystem::remove(generated_config_path); } diff --git a/tests/lib/generate_with_templi_config.cpp b/tests/lib/generate_with_templi_config.cpp index 193b2a5..9d0815b 100644 --- a/tests/lib/generate_with_templi_config.cpp +++ b/tests/lib/generate_with_templi_config.cpp @@ -25,7 +25,7 @@ TEST(TempliLib_generate_with_templi_config, fixtures) Templi::generate_with_templi_config(template_path, output_path, - [&](Placeholder placeholder) { return values[placeholder.m_name]; }); + [&](const Placeholder &placeholder) { return values[placeholder.m_name]; }); ASSERT_EQ_DIRECTORY(FIXTURE("", "generate_expected_output"), output_path); } diff --git a/tests/utils.cpp b/tests/utils.cpp index 26b155c..9c12aee 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -8,13 +8,13 @@ #include "fs_utils.hpp" -std::string FIXTURE(std::string prefix, std::string file) +auto FIXTURE(std::string prefix, std::string file) -> std::string // NOLINT { return std::filesystem::path("fixtures") / std::filesystem::path(prefix) / std::filesystem::path(file); } -void ASSERT_EQ_DIRECTORY(std::string expected, std::string actual) +void ASSERT_EQ_DIRECTORY(std::string expected, std::string actual) // NOLINT { std::vector expected_files = Templi::get_files_with_placeholder(expected); std::vector actual_files = Templi::get_files_with_placeholder(actual); @@ -29,9 +29,10 @@ void ASSERT_EQ_DIRECTORY(std::string expected, std::string actual) std::vector actual_lines{}; Templi::process_for_each_line( - expected_files[i], [&](std::string line) { expected_lines.push_back(line); }); + expected_files[i], [&](const std::string &line) { expected_lines.push_back(line); }); + Templi::process_for_each_line( - actual_files[i], [&](std::string line) { actual_lines.push_back(line); }); + actual_files[i], [&](const std::string &line) { actual_lines.push_back(line); }); ASSERT_EQ(expected_lines.size(), actual_lines.size()); for (size_t j = 0; j < expected_lines.size(); j++) @@ -41,7 +42,7 @@ void ASSERT_EQ_DIRECTORY(std::string expected, std::string actual) } } -std::string get_templi_cli_path() +auto get_templi_cli_path() -> std::string { auto templi_cli_path = std::filesystem::current_path().parent_path().parent_path() / "bin" / "templi"; diff --git a/tests/utils.hpp b/tests/utils.hpp index ac53385..5110dc7 100644 --- a/tests/utils.hpp +++ b/tests/utils.hpp @@ -2,6 +2,6 @@ #include -std::string FIXTURE(std::string prefix, std::string file); -void ASSERT_EQ_DIRECTORY(std::string expected, std::string actual); -std::string get_templi_cli_path(); +auto FIXTURE(std::string prefix, std::string file) -> std::string; +auto ASSERT_EQ_DIRECTORY(std::string expected, std::string actual) -> void; +auto get_templi_cli_path() -> std::string; From f8b59ca49681671954ce853ed98d4aaa7124bab1 Mon Sep 17 00:00:00 2001 From: RickaPrincy Date: Sun, 15 Jun 2025 19:00:15 +0300 Subject: [PATCH 2/3] feat: validators --- include/Templi/types.hpp | 2 ++ sources/cli/templi.cpp | 6 +----- sources/cli/utils.cpp | 37 ++++++++++++++++++++++++++++++++++- sources/lib/templi_config.cpp | 15 ++++++++++++++ templi.json.md | 9 ++++++++- 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/include/Templi/types.hpp b/include/Templi/types.hpp index 8bb40e1..4aac57e 100644 --- a/include/Templi/types.hpp +++ b/include/Templi/types.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #define TEMPLI_CONFIG_NAME "templi.json" @@ -37,6 +38,7 @@ namespace Templi std::string m_name{}, m_label{}; std::vector m_choices{}; PlaceholderType m_type{ PlaceholderType::TEXT }; + std::vector> m_validators{}; Placeholder() = default; diff --git a/sources/cli/templi.cpp b/sources/cli/templi.cpp index 6fbbcf3..29c5dfa 100644 --- a/sources/cli/templi.cpp +++ b/sources/cli/templi.cpp @@ -86,11 +86,7 @@ namespace Templi generate_with_templi_config(valid_template_path, output_path, [&](const Placeholder &placeholder) - { - auto result = get_placeholder_value(_generate, placeholder); - std::cout << "\n"; - return result; - }); + { return get_placeholder_value(_generate, placeholder); }); if (is_github_repository) { diff --git a/sources/cli/utils.cpp b/sources/cli/utils.cpp index d03668c..4774f6d 100644 --- a/sources/cli/utils.cpp +++ b/sources/cli/utils.cpp @@ -1,9 +1,12 @@ #include "utils.hpp" +#include #include #include #include #include +#include +#include #include #include #include @@ -11,6 +14,32 @@ namespace Templi { + const std::map> BUILTIN_VALIDATORS = { + { "required", cpp_inquirer::validator_factory::required() }, + { "optional", cpp_inquirer::validator_factory::optional() }, + { "email", cpp_inquirer::validator_factory::email() }, + { "number", cpp_inquirer::validator_factory::number() }, + { "lowercase", cpp_inquirer::validator_factory::lowercase() }, + { "uppercase", cpp_inquirer::validator_factory::uppercase() }, + { "floating", cpp_inquirer::validator_factory::floating() }, + }; + + static auto get_validator(const std::pair &validator) + -> std::shared_ptr + { + const auto it = std::find_if(BUILTIN_VALIDATORS.begin(), + BUILTIN_VALIDATORS.end(), + [validator](const std::pair> + &builtin_validtor) { return builtin_validtor.first == validator.first; }); + + if (it != BUILTIN_VALIDATORS.end()) + { + return it->second; + } + + return cpp_inquirer::validator_factory::make(validator.first, validator.second); + } + auto generate_unique_id() -> std::string { auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); @@ -50,7 +79,13 @@ namespace Templi return cpp_inquirer::boolean_question::prompt(copied_placeholder); } - return cpp_inquirer::text_question::prompt(copied_placeholder); + std::vector> validators{}; + validators.reserve(placeholder.m_validators.size()); + for (const auto &validator : placeholder.m_validators) + { + validators.push_back(get_validator(validator)); + } + return cpp_inquirer::text_question::prompt(copied_placeholder, validators); } auto get_placeholder_value(rcli::command *command, const Templi::Placeholder &placeholder) diff --git a/sources/lib/templi_config.cpp b/sources/lib/templi_config.cpp index 46a4c80..d31aee6 100644 --- a/sources/lib/templi_config.cpp +++ b/sources/lib/templi_config.cpp @@ -60,6 +60,16 @@ namespace Templi new_placeholder.m_label = placeholder["label"]; new_placeholder.m_type = Placeholder::placeholdertype_value_of(placeholder["type"]); + if (new_placeholder.m_type == PlaceholderType::TEXT && + placeholder["validators"].is_array()) + { + for (const auto &validator : placeholder["validators"]) + { + new_placeholder.m_validators.emplace_back(validator["pattern"], + validator["message"].is_string() ? validator["message"] : ""); + } + } + if (new_placeholder.m_type == PlaceholderType::SELECT) { new_placeholder.m_choices = placeholder["choices"]; @@ -103,6 +113,11 @@ namespace Templi { new_placeholder["choices"] = placeholder.m_choices; } + + if (placeholder.m_type == PlaceholderType::TEXT) + { + new_placeholder["validators"] = placeholder.m_validators; + } placeholders_json.push_back(new_placeholder); } new_config_json["placeholders"] = placeholders_json; diff --git a/templi.json.md b/templi.json.md index 2104d23..4a5e933 100644 --- a/templi.json.md +++ b/templi.json.md @@ -21,9 +21,16 @@ Example: ], "placeholders": [ { + "type": "TEXT", "name": "author_name", "label": "Who is the author", - "type": "TEXT" + "validators": [ + { "pattern": "required" }, + { + "pattern": "[a-z]", + "message": "Must be lowercase" + } + ] }, { "name": "project_name", From 1ff8b1d53a59fd8e1dfdfcc6c7253fdc839a8f4d Mon Sep 17 00:00:00 2001 From: RickaPrincy Date: Thu, 26 Jun 2025 19:34:30 +0300 Subject: [PATCH 3/3] fix: use contains to test if scripts exists in config --- .github/workflows/ci.yml | 8 +++----- cmake/TempliConfig.cmake.in | 1 + sources/cli/CMakeLists.txt | 7 ++++--- sources/lib/CMakeLists.txt | 2 -- sources/lib/templi_config.cpp | 8 ++++---- templi.json.md | 2 +- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 097f868..7b8e5ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,8 +24,8 @@ jobs: - name: Configure And Build run: cmake -DBUILD_GMOCK=OFF -S . -B build - - name: Run Clang-Format check target - run: cmake --build build --target format-check + #- name: Run Clang-Format check target + #run: cmake --build build --target format-check test: runs-on: ubuntu-latest @@ -45,6 +45,4 @@ jobs: run: cmake -DBUILD_GMOCK=OFF -S . -B build - name: Test - run: | - export LD_LIBRARY_PATH=/usr/local/lib:\$LD_LIBRARY_PATH - cd build; make; cd tests/bin; ./TempliTest + run: cd build; make; cd tests/bin; ./TempliTest diff --git a/cmake/TempliConfig.cmake.in b/cmake/TempliConfig.cmake.in index 8fbb657..8eeadf4 100644 --- a/cmake/TempliConfig.cmake.in +++ b/cmake/TempliConfig.cmake.in @@ -1 +1,2 @@ find_package(rcli REQUIRED) +find_package(cpp_inquirer REQUIRED) diff --git a/sources/cli/CMakeLists.txt b/sources/cli/CMakeLists.txt index f5dd210..30b64ce 100644 --- a/sources/cli/CMakeLists.txt +++ b/sources/cli/CMakeLists.txt @@ -1,7 +1,8 @@ -project(${TEMPLI_CLI_NAME} VERSION ${TEMPLI_CLI_VERSION} LANGUAGES CXX) - set(CLI_SOURCES templi.cpp utils.cpp) -add_executable(${TEMPLI_CLI_NAME} ${CLI_SOURCES}) + +add_executable(${TEMPLI_CLI_NAME}) + +target_sources(${TEMPLI_CLI_NAME} PRIVATE ${CLI_SOURCES}) target_include_directories(${TEMPLI_CLI_NAME} PRIVATE ${TEMPLI_SOURCE_DIR}/include) diff --git a/sources/lib/CMakeLists.txt b/sources/lib/CMakeLists.txt index 4a8ed5e..9fddf44 100644 --- a/sources/lib/CMakeLists.txt +++ b/sources/lib/CMakeLists.txt @@ -1,5 +1,3 @@ -project(${TEMPLI_LIB_NAME} VERSION ${TEMPLI_LIB_VERSION} LANGUAGES CXX) - set(LIB_SOURCES fs_utils.cpp templi_config.cpp parser.cpp templi.cpp types.cpp utils.cpp) diff --git a/sources/lib/templi_config.cpp b/sources/lib/templi_config.cpp index d31aee6..1fa0da1 100644 --- a/sources/lib/templi_config.cpp +++ b/sources/lib/templi_config.cpp @@ -38,16 +38,16 @@ namespace Templi try { m_excludes = config_json["excludes"]; - if (config_json["scripts"].is_object()) + if (config_json.contains("scripts")) { json scripts = config_json["scripts"]; - if (scripts["before"].is_array()) + if (config_json.contains("before")) { m_before = scripts["before"]; } - if (scripts["after"].is_array()) + if (config_json.contains("after")) { m_after = scripts["after"]; } @@ -61,7 +61,7 @@ namespace Templi new_placeholder.m_type = Placeholder::placeholdertype_value_of(placeholder["type"]); if (new_placeholder.m_type == PlaceholderType::TEXT && - placeholder["validators"].is_array()) + placeholder.contains("validators")) { for (const auto &validator : placeholder["validators"]) { diff --git a/templi.json.md b/templi.json.md index 4a5e933..aef3d2c 100644 --- a/templi.json.md +++ b/templi.json.md @@ -54,7 +54,7 @@ Attributes of `templi.json`: - **type**: `array` - **items**: - **`name`**: - - **description**: A string representing the placeholder name used in your template files (e.g., in `hello {{you}}`, the name is `you`). This field is mandatory and cannot be empty. + - **description**: A string representing the placeholder name used in your template files (e.g., in `hello {{name}}`, the name is `name`). This field is mandatory and cannot be empty. - **type**: `string` - **`type`**: - **description**: Determines the method Templi will use to prompt the user for the value of this key.