diff --git a/.github/workflows/dev-pull.yml b/.github/workflows/dev-pull.yml index 216d765..7708000 100644 --- a/.github/workflows/dev-pull.yml +++ b/.github/workflows/dev-pull.yml @@ -2,7 +2,7 @@ name: CMake on a single platform on: - push: + pull_request: branches: [ "dev" ] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) diff --git a/.gitignore b/.gitignore index 73a1f72..61d143d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ install_manifest.txt build/* +build .cache .cache/* .cmake Makefile +DartConfiguration.txt config.toml CMakeFiles CMakeCache.txt diff --git a/CLEAN_EVERYTHIN.sh b/CLEAN_EVERYTHIN.sh index 038076d..a03f5db 100755 --- a/CLEAN_EVERYTHIN.sh +++ b/CLEAN_EVERYTHIN.sh @@ -1,4 +1,4 @@ -rm -rf ./_deps ./CMakeCache.txt ./CMakeFiles +rm -rf ./_deps ./CMakeCache.txt ./CMakeFiles build/* make clean rm -rf ./Makefile sudo xargs rm -rf < install_manifest.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index d9990b9..69f367e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,20 +28,18 @@ FetchContent_Declare( GIT_REPOSITORY "https://github.com/curl/curl.git" GIT_TAG "curl-8_3_0" ) + FetchContent_Declare( termcolor GIT_REPOSITORY "https://github.com/ikalnytskyi/termcolor.git" GIT_TAG "v2.1.0" - ) +) FetchContent_MakeAvailable(nlohmann_json) FetchContent_MakeAvailable(cxxopts) FetchContent_MakeAvailable(curl) FetchContent_MakeAvailable(termcolor) - - - file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_SOURCE_DIR} "src/**.cpp" "src/**.c" @@ -98,12 +96,27 @@ else() endif() endif() -target_link_libraries(${PROJECT_NAME} curl) +target_link_libraries(${PROJECT_NAME} libcurl) target_link_libraries(${PROJECT_NAME} cxxopts) target_link_libraries(${PROJECT_NAME} nlohmann_json) target_link_libraries(${PROJECT_NAME} termcolor::termcolor) - set(SOURCE_DIR src) set(BUILD_DIR build) set_target_properties(cmaker PROPERTIES RUNTIME_OUTPUT_DIRECTORY build) install(TARGETS cmaker DESTINATION bin) +install(FILES ./completions/cmaker-completion.zsh + DESTINATION /usr/local/share/zsh/site-functions/ + RENAME _cmaker) +install(FILES ./completions/cmaker-completion.bash + DESTINATION /etc/bash_completion.d/ + RENAME cmaker) +install(FILES ./completions/cmaker-completion.fish + DESTINATION /usr/share/fish/vendor_completions.d + RENAME cmaker.fish) + +if(NOT DEFINED MAN_INSTALL_DIR) + set(MAN_INSTALL_DIR "share/man/man1") +endif() + +add_custom_target(man ALL DEPENDS docs/man/cmaker.1) +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/docs/man/cmaker.1" DESTINATION "${MAN_INSTALL_DIR}") diff --git a/CTestTestfile.cmake b/CTestTestfile.cmake index 61e30e2..d4b4dc1 100644 --- a/CTestTestfile.cmake +++ b/CTestTestfile.cmake @@ -6,7 +6,6 @@ # testing this directory and lists subdirectories to be tested as well. subdirs("_deps/nlohmann_json-build") subdirs("_deps/cxxopts-build") -subdirs("_deps/tomlplusplus-build") subdirs("_deps/curl-build") subdirs("_deps/termcolor-build") subdirs("_deps/catch2-build") diff --git a/DartConfiguration.tcl b/DartConfiguration.tcl deleted file mode 100644 index ac4d6b8..0000000 --- a/DartConfiguration.tcl +++ /dev/null @@ -1,106 +0,0 @@ -# This file is configured by CMake automatically as DartConfiguration.tcl -# If you choose not to use CMake, this file may be hand configured, by -# filling in the required variables. - - -# Configuration directories and files -SourceDirectory: /home/deastl/repos/cpp/cmake-generator -BuildDirectory: /home/deastl/repos/cpp/cmake-generator - -# Where to place the cost data store -CostDataFile: - -# Site is something like machine.domain, i.e. pragmatic.crd -Site: arch-deastl - -# Build name is osname-revision-compiler, i.e. Linux-2.4.2-2smp-c++ -BuildName: Linux-c++ - -# Subprojects -LabelsForSubprojects: - -# Submission information -SubmitURL: http:// -SubmitInactivityTimeout: - -# Dashboard start time -NightlyStartTime: 00:00:00 EDT - -# Commands for the build/test/submit cycle -ConfigureCommand: "/usr/bin/cmake" "/home/deastl/repos/cpp/cmake-generator" -MakeCommand: /usr/bin/cmake --build . --config "${CTEST_CONFIGURATION_TYPE}" -DefaultCTestConfigurationType: Release - -# version control -UpdateVersionOnly: - -# CVS options -# Default is "-d -P -A" -CVSCommand: -CVSUpdateOptions: - -# Subversion options -SVNCommand: -SVNOptions: -SVNUpdateOptions: - -# Git options -GITCommand: /usr/bin/git -GITInitSubmodules: -GITUpdateOptions: -GITUpdateCustom: - -# Perforce options -P4Command: -P4Client: -P4Options: -P4UpdateOptions: -P4UpdateCustom: - -# Generic update command -UpdateCommand: /usr/bin/git -UpdateOptions: -UpdateType: git - -# Compiler info -Compiler: /usr/bin/c++ -CompilerVersion: 13.2.1 - -# Dynamic analysis (MemCheck) -PurifyCommand: -ValgrindCommand: -ValgrindCommandOptions: -DrMemoryCommand: -DrMemoryCommandOptions: -CudaSanitizerCommand: -CudaSanitizerCommandOptions: -MemoryCheckType: -MemoryCheckSanitizerOptions: -MemoryCheckCommand: /usr/bin/valgrind -MemoryCheckCommandOptions: -MemoryCheckSuppressionFile: - -# Coverage -CoverageCommand: /usr/bin/gcov -CoverageExtraFlags: -l - -# Testing options -# TimeOut is the amount of time in seconds to wait for processes -# to complete during testing. After TimeOut seconds, the -# process will be summarily terminated. -# Currently set to 25 minutes -TimeOut: 1500 - -# During parallel testing CTest will not start a new test if doing -# so would cause the system load to exceed this value. -TestLoad: - -UseLaunchers: -CurlOptions: -# warning, if you add new options here that have to do with submit, -# you have to update cmCTestSubmitCommand.cxx - -# For CTest submissions that timeout, these options -# specify behavior for retrying the submission -CTestSubmitRetryDelay: 5 -CTestSubmitRetryCount: 3 diff --git a/completions/cmaker-completion.bash b/completions/cmaker-completion.bash new file mode 100644 index 0000000..46838c0 --- /dev/null +++ b/completions/cmaker-completion.bash @@ -0,0 +1,33 @@ +_cmaker() { + local cur prev words cword + _get_comp_words_by_ref -n : cur prev words cword + + local commands="init run watch add ftp help" + local add_subcommands="dep lib flags" + + case "${prev}" in + add) + COMPREPLY=($(compgen -W "${add_subcommands}" -- "${cur}")) + return 0 + ;; + esac + + case "${words[1]}" in + add) + if [ "$cword" -eq 2 ]; then + COMPREPLY=($(compgen -W "${add_subcommands}" -- "${cur}")) + return 0 + fi + ;; + *) + if [ "$cword" -eq 1 ]; then + COMPREPLY=($(compgen -W "${commands}" -- "${cur}")) + return 0 + fi + ;; + esac + + COMPREPLY=() +} + +complete -F _cmaker cmaker diff --git a/completions/cmaker-completion.fish b/completions/cmaker-completion.fish new file mode 100644 index 0000000..7348f61 --- /dev/null +++ b/completions/cmaker-completion.fish @@ -0,0 +1,27 @@ +function __fish_cmaker_needs_command + set -l cmd (commandline -opc) + if test (count $cmd) -eq 1 + return 0 + end + return 1 +end + +function __fish_cmaker_using_command + set -l cmd (commandline -opc) + set -e cmd[1] + if contains -- $argv[1] $cmd + return 0 + end + return 1 +end + +function __fish_cmaker_needs_subcommand + set -l cmd (commandline -opc) + if test (count $cmd) -eq 2 + return 0 + end + return 1 +end + +complete -c cmaker -n '__fish_cmaker_needs_command' -a 'init run watch add ftp help' -f +complete -c cmaker -n '__fish_cmaker_using_command add' -a 'dep lib flags' -f diff --git a/completions/cmaker-completion.zsh b/completions/cmaker-completion.zsh new file mode 100644 index 0000000..069eeb9 --- /dev/null +++ b/completions/cmaker-completion.zsh @@ -0,0 +1,44 @@ +#compdef cmaker + +_cmaker() { + local state + local -a options + local -a cmd + + _arguments -C \ + '1: :->command' \ + '2: :->subcommand' \ + '*::arg:->args' + + options=( + 'init:Initializes your project' + 'run:Builds and runs your project' + 'watch:Watches builds and runs your project on changes' + 'add:[dep, lib, flags] Add library, dependecy or flags to your project' + 'ftp:Delets the entire project (F*ck this project)' + 'help:Print help' + ) + + add_options=( + 'dep:Add a dependecy ' + 'lib:Add a library ' + 'flags:Add a flag ' + ) + + case $state in + command) + _describe -t commands "command" options + ;; + subcommand) + case $words[2] in + add) + _describe -t subcommands "subcommand" add_options + esac + ;; + args) + # Leaving empty for now + ;; + esac +} + +_cmaker "$@" diff --git a/docs/man/cmaker.1 b/docs/man/cmaker.1 new file mode 100644 index 0000000..c0a3439 --- /dev/null +++ b/docs/man/cmaker.1 @@ -0,0 +1,61 @@ +.TH "cmaker" "1" "2023-11-05" "cmaker" "cmaker manual" +.SH "NAME" +\fBcmaker - c/cpp package manager\fR +.SH SYNOPSIS +.P +\fBcmaker \fR +.SH DESCRIPTION +.P +\fBCmaker\fR came to fruition out of the desperate hopes and dreams of a few people looking to softly lighten their suffering while using c++\. +.SH init +.P +Initializes your project\. You'll be prompted with information to enter and cmaker will take care of the rest\. +.br +This will create a cmake file, 1 source file in src directory and config\.toml, where cmaker specific configuration is stored\. +.SS options +.SS \-y | \-\-skip\-init +.P +Skips initialization\. +.SS \-n | \-\-name example\-name +.P +Name of your project +.SS \-l | \-\-language cpp/c +.P +Language of your choice, c or cpp\. +.SH run +.P +Builds and runs your project\. +.SH watch +.P +Same a run command, but also watches file changes and rebuilds your project accordingly\. +.SH add +.P +This command adds a dependency, library or flag to your project\. +.br +\fBSubcommands:\fR +.SS dep +.P +Searches and adds dependency to your project\. +.br +Example: +.RS 2 +.nf +cmaker add dep sdl +.fi +.RE +.P +You'll be shown found results and will be promted to choose from the list\. +.SS lib +.P +Searches and adds library to your project\. +.SS flags +.P +Adds flag to your project\. +.SH ftp +.P +Deletes the entire project :)\. +.br +Why? +.br +Why not? + diff --git a/include/inja.hpp b/include/inja.hpp new file mode 100644 index 0000000..b737824 --- /dev/null +++ b/include/inja.hpp @@ -0,0 +1,2937 @@ +/* + ___ _ Version 3.4.0 + |_ _|_ __ (_) __ _ https://github.com/pantor/inja + | || '_ \ | |/ _` | Licensed under the MIT License . + | || | | || | (_| | + |___|_| |_|/ |\__,_| Copyright (c) 2018-2022 Lars Berscheid + |__/ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef INCLUDE_INJA_INJA_HPP_ +#define INCLUDE_INJA_INJA_HPP_ + +#include + +namespace inja { +#ifndef INJA_DATA_TYPE +using json = nlohmann::json; +#else +using json = INJA_DATA_TYPE; +#endif +} // namespace inja + +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(INJA_NOEXCEPTION) +#ifndef INJA_THROW +#define INJA_THROW(exception) throw exception +#endif +#else +#include +#ifndef INJA_THROW +#define INJA_THROW(exception) \ + std::abort(); \ + std::ignore = exception +#endif +#ifndef INJA_NOEXCEPTION +#define INJA_NOEXCEPTION +#endif +#endif + +// #include "environment.hpp" +#ifndef INCLUDE_INJA_ENVIRONMENT_HPP_ +#define INCLUDE_INJA_ENVIRONMENT_HPP_ + +#include +#include +#include +#include +#include +#include + +// #include "config.hpp" +#ifndef INCLUDE_INJA_CONFIG_HPP_ +#define INCLUDE_INJA_CONFIG_HPP_ + +#include +#include + +// #include "template.hpp" +#ifndef INCLUDE_INJA_TEMPLATE_HPP_ +#define INCLUDE_INJA_TEMPLATE_HPP_ + +#include +#include +#include +#include + +// #include "node.hpp" +#ifndef INCLUDE_INJA_NODE_HPP_ +#define INCLUDE_INJA_NODE_HPP_ + +#include +#include +#include + +// #include "function_storage.hpp" +#ifndef INCLUDE_INJA_FUNCTION_STORAGE_HPP_ +#define INCLUDE_INJA_FUNCTION_STORAGE_HPP_ + +#include +#include + +namespace inja { + +using Arguments = std::vector; +using CallbackFunction = std::function; +using VoidCallbackFunction = std::function; + +/*! + * \brief Class for builtin functions and user-defined callbacks. + */ +class FunctionStorage { +public: + enum class Operation { + Not, + And, + Or, + In, + Equal, + NotEqual, + Greater, + GreaterEqual, + Less, + LessEqual, + Add, + Subtract, + Multiplication, + Division, + Power, + Modulo, + AtId, + At, + Default, + DivisibleBy, + Even, + Exists, + ExistsInObject, + First, + Float, + Int, + IsArray, + IsBoolean, + IsFloat, + IsInteger, + IsNumber, + IsObject, + IsString, + Last, + Length, + Lower, + Max, + Min, + Odd, + Range, + Round, + Sort, + Upper, + Super, + Join, + Callback, + None, + }; + + struct FunctionData { + explicit FunctionData(const Operation& op, const CallbackFunction& cb = CallbackFunction {}): operation(op), callback(cb) {} + const Operation operation; + const CallbackFunction callback; + }; + +private: + const int VARIADIC {-1}; + + std::map, FunctionData> function_storage = { + {std::make_pair("at", 2), FunctionData {Operation::At}}, + {std::make_pair("default", 2), FunctionData {Operation::Default}}, + {std::make_pair("divisibleBy", 2), FunctionData {Operation::DivisibleBy}}, + {std::make_pair("even", 1), FunctionData {Operation::Even}}, + {std::make_pair("exists", 1), FunctionData {Operation::Exists}}, + {std::make_pair("existsIn", 2), FunctionData {Operation::ExistsInObject}}, + {std::make_pair("first", 1), FunctionData {Operation::First}}, + {std::make_pair("float", 1), FunctionData {Operation::Float}}, + {std::make_pair("int", 1), FunctionData {Operation::Int}}, + {std::make_pair("isArray", 1), FunctionData {Operation::IsArray}}, + {std::make_pair("isBoolean", 1), FunctionData {Operation::IsBoolean}}, + {std::make_pair("isFloat", 1), FunctionData {Operation::IsFloat}}, + {std::make_pair("isInteger", 1), FunctionData {Operation::IsInteger}}, + {std::make_pair("isNumber", 1), FunctionData {Operation::IsNumber}}, + {std::make_pair("isObject", 1), FunctionData {Operation::IsObject}}, + {std::make_pair("isString", 1), FunctionData {Operation::IsString}}, + {std::make_pair("last", 1), FunctionData {Operation::Last}}, + {std::make_pair("length", 1), FunctionData {Operation::Length}}, + {std::make_pair("lower", 1), FunctionData {Operation::Lower}}, + {std::make_pair("max", 1), FunctionData {Operation::Max}}, + {std::make_pair("min", 1), FunctionData {Operation::Min}}, + {std::make_pair("odd", 1), FunctionData {Operation::Odd}}, + {std::make_pair("range", 1), FunctionData {Operation::Range}}, + {std::make_pair("round", 2), FunctionData {Operation::Round}}, + {std::make_pair("sort", 1), FunctionData {Operation::Sort}}, + {std::make_pair("upper", 1), FunctionData {Operation::Upper}}, + {std::make_pair("super", 0), FunctionData {Operation::Super}}, + {std::make_pair("super", 1), FunctionData {Operation::Super}}, + {std::make_pair("join", 2), FunctionData {Operation::Join}}, + }; + +public: + void add_builtin(std::string_view name, int num_args, Operation op) { + function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData {op}); + } + + void add_callback(std::string_view name, int num_args, const CallbackFunction& callback) { + function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData {Operation::Callback, callback}); + } + + FunctionData find_function(std::string_view name, int num_args) const { + auto it = function_storage.find(std::make_pair(static_cast(name), num_args)); + if (it != function_storage.end()) { + return it->second; + + // Find variadic function + } else if (num_args > 0) { + it = function_storage.find(std::make_pair(static_cast(name), VARIADIC)); + if (it != function_storage.end()) { + return it->second; + } + } + + return FunctionData {Operation::None}; + } +}; + +} // namespace inja + +#endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_ + +// #include "utils.hpp" +#ifndef INCLUDE_INJA_UTILS_HPP_ +#define INCLUDE_INJA_UTILS_HPP_ + +#include +#include +#include +#include +#include + +// #include "exceptions.hpp" +#ifndef INCLUDE_INJA_EXCEPTIONS_HPP_ +#define INCLUDE_INJA_EXCEPTIONS_HPP_ + +#include +#include + +namespace inja { + +struct SourceLocation { + size_t line; + size_t column; +}; + +struct InjaError : public std::runtime_error { + const std::string type; + const std::string message; + + const SourceLocation location; + + explicit InjaError(const std::string& type, const std::string& message) + : std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message), location({0, 0}) {} + + explicit InjaError(const std::string& type, const std::string& message, SourceLocation location) + : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" + std::to_string(location.column) + ") " + message), + type(type), message(message), location(location) {} +}; + +struct ParserError : public InjaError { + explicit ParserError(const std::string& message, SourceLocation location): InjaError("parser_error", message, location) {} +}; + +struct RenderError : public InjaError { + explicit RenderError(const std::string& message, SourceLocation location): InjaError("render_error", message, location) {} +}; + +struct FileError : public InjaError { + explicit FileError(const std::string& message): InjaError("file_error", message) {} + explicit FileError(const std::string& message, SourceLocation location): InjaError("file_error", message, location) {} +}; + +struct DataError : public InjaError { + explicit DataError(const std::string& message, SourceLocation location): InjaError("data_error", message, location) {} +}; + +} // namespace inja + +#endif // INCLUDE_INJA_EXCEPTIONS_HPP_ + + +namespace inja { + +namespace string_view { +inline std::string_view slice(std::string_view view, size_t start, size_t end) { + start = std::min(start, view.size()); + end = std::min(std::max(start, end), view.size()); + return view.substr(start, end - start); +} + +inline std::pair split(std::string_view view, char Separator) { + size_t idx = view.find(Separator); + if (idx == std::string_view::npos) { + return std::make_pair(view, std::string_view()); + } + return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, std::string_view::npos)); +} + +inline bool starts_with(std::string_view view, std::string_view prefix) { + return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0); +} +} // namespace string_view + +inline SourceLocation get_source_location(std::string_view content, size_t pos) { + // Get line and offset position (starts at 1:1) + auto sliced = string_view::slice(content, 0, pos); + std::size_t last_newline = sliced.rfind("\n"); + + if (last_newline == std::string_view::npos) { + return {1, sliced.length() + 1}; + } + + // Count newlines + size_t count_lines = 0; + size_t search_start = 0; + while (search_start <= sliced.size()) { + search_start = sliced.find("\n", search_start) + 1; + if (search_start == 0) { + break; + } + count_lines += 1; + } + + return {count_lines + 1, sliced.length() - last_newline}; +} + +inline void replace_substring(std::string& s, const std::string& f, const std::string& t) { + if (f.empty()) { + return; + } + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +} // namespace inja + +#endif // INCLUDE_INJA_UTILS_HPP_ + + +namespace inja { + +class NodeVisitor; +class BlockNode; +class TextNode; +class ExpressionNode; +class LiteralNode; +class DataNode; +class FunctionNode; +class ExpressionListNode; +class StatementNode; +class ForStatementNode; +class ForArrayStatementNode; +class ForObjectStatementNode; +class IfStatementNode; +class IncludeStatementNode; +class ExtendsStatementNode; +class BlockStatementNode; +class SetStatementNode; + +class NodeVisitor { +public: + virtual ~NodeVisitor() = default; + + virtual void visit(const BlockNode& node) = 0; + virtual void visit(const TextNode& node) = 0; + virtual void visit(const ExpressionNode& node) = 0; + virtual void visit(const LiteralNode& node) = 0; + virtual void visit(const DataNode& node) = 0; + virtual void visit(const FunctionNode& node) = 0; + virtual void visit(const ExpressionListNode& node) = 0; + virtual void visit(const StatementNode& node) = 0; + virtual void visit(const ForStatementNode& node) = 0; + virtual void visit(const ForArrayStatementNode& node) = 0; + virtual void visit(const ForObjectStatementNode& node) = 0; + virtual void visit(const IfStatementNode& node) = 0; + virtual void visit(const IncludeStatementNode& node) = 0; + virtual void visit(const ExtendsStatementNode& node) = 0; + virtual void visit(const BlockStatementNode& node) = 0; + virtual void visit(const SetStatementNode& node) = 0; +}; + +/*! + * \brief Base node class for the abstract syntax tree (AST). + */ +class AstNode { +public: + virtual void accept(NodeVisitor& v) const = 0; + + size_t pos; + + AstNode(size_t pos): pos(pos) {} + virtual ~AstNode() {} +}; + +class BlockNode : public AstNode { +public: + std::vector> nodes; + + explicit BlockNode(): AstNode(0) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class TextNode : public AstNode { +public: + const size_t length; + + explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class ExpressionNode : public AstNode { +public: + explicit ExpressionNode(size_t pos): AstNode(pos) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class LiteralNode : public ExpressionNode { +public: + const json value; + + explicit LiteralNode(std::string_view data_text, size_t pos): ExpressionNode(pos), value(json::parse(data_text)) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class DataNode : public ExpressionNode { +public: + const std::string name; + const json::json_pointer ptr; + + static std::string convert_dot_to_ptr(std::string_view ptr_name) { + std::string result; + do { + std::string_view part; + std::tie(part, ptr_name) = string_view::split(ptr_name, '.'); + result.push_back('/'); + result.append(part.begin(), part.end()); + } while (!ptr_name.empty()); + return result; + } + + explicit DataNode(std::string_view ptr_name, size_t pos): ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_ptr(ptr_name))) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class FunctionNode : public ExpressionNode { + using Op = FunctionStorage::Operation; + +public: + enum class Associativity { + Left, + Right, + }; + + unsigned int precedence; + Associativity associativity; + + Op operation; + + std::string name; + int number_args; // Can also be negative -> -1 for unknown number + std::vector> arguments; + CallbackFunction callback; + + explicit FunctionNode(std::string_view name, size_t pos) + : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(0) {} + explicit FunctionNode(Op operation, size_t pos): ExpressionNode(pos), operation(operation), number_args(1) { + switch (operation) { + case Op::Not: { + number_args = 1; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::And: { + number_args = 2; + precedence = 1; + associativity = Associativity::Left; + } break; + case Op::Or: { + number_args = 2; + precedence = 1; + associativity = Associativity::Left; + } break; + case Op::In: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Equal: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::NotEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Greater: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::GreaterEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Less: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::LessEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Add: { + number_args = 2; + precedence = 3; + associativity = Associativity::Left; + } break; + case Op::Subtract: { + number_args = 2; + precedence = 3; + associativity = Associativity::Left; + } break; + case Op::Multiplication: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::Division: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::Power: { + number_args = 2; + precedence = 5; + associativity = Associativity::Right; + } break; + case Op::Modulo: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::AtId: { + number_args = 2; + precedence = 8; + associativity = Associativity::Left; + } break; + default: { + precedence = 1; + associativity = Associativity::Left; + } + } + } + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class ExpressionListNode : public AstNode { +public: + std::shared_ptr root; + + explicit ExpressionListNode(): AstNode(0) {} + explicit ExpressionListNode(size_t pos): AstNode(pos) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class StatementNode : public AstNode { +public: + StatementNode(size_t pos): AstNode(pos) {} + + virtual void accept(NodeVisitor& v) const = 0; +}; + +class ForStatementNode : public StatementNode { +public: + ExpressionListNode condition; + BlockNode body; + BlockNode* const parent; + + ForStatementNode(BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent) {} + + virtual void accept(NodeVisitor& v) const = 0; +}; + +class ForArrayStatementNode : public ForStatementNode { +public: + const std::string value; + + explicit ForArrayStatementNode(const std::string& value, BlockNode* const parent, size_t pos): ForStatementNode(parent, pos), value(value) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class ForObjectStatementNode : public ForStatementNode { +public: + const std::string key; + const std::string value; + + explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode* const parent, size_t pos) + : ForStatementNode(parent, pos), key(key), value(value) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class IfStatementNode : public StatementNode { +public: + ExpressionListNode condition; + BlockNode true_statement; + BlockNode false_statement; + BlockNode* const parent; + + const bool is_nested; + bool has_false_statement {false}; + + explicit IfStatementNode(BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent), is_nested(false) {} + explicit IfStatementNode(bool is_nested, BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent), is_nested(is_nested) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class IncludeStatementNode : public StatementNode { +public: + const std::string file; + + explicit IncludeStatementNode(const std::string& file, size_t pos): StatementNode(pos), file(file) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class ExtendsStatementNode : public StatementNode { +public: + const std::string file; + + explicit ExtendsStatementNode(const std::string& file, size_t pos): StatementNode(pos), file(file) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + }; +}; + +class BlockStatementNode : public StatementNode { +public: + const std::string name; + BlockNode block; + BlockNode* const parent; + + explicit BlockStatementNode(BlockNode* const parent, const std::string& name, size_t pos): StatementNode(pos), name(name), parent(parent) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + }; +}; + +class SetStatementNode : public StatementNode { +public: + const std::string key; + ExpressionListNode expression; + + explicit SetStatementNode(const std::string& key, size_t pos): StatementNode(pos), key(key) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +} // namespace inja + +#endif // INCLUDE_INJA_NODE_HPP_ + +// #include "statistics.hpp" +#ifndef INCLUDE_INJA_STATISTICS_HPP_ +#define INCLUDE_INJA_STATISTICS_HPP_ + +// #include "node.hpp" + + +namespace inja { + +/*! + * \brief A class for counting statistics on a Template. + */ +class StatisticsVisitor : public NodeVisitor { + void visit(const BlockNode& node) { + for (auto& n : node.nodes) { + n->accept(*this); + } + } + + void visit(const TextNode&) {} + void visit(const ExpressionNode&) {} + void visit(const LiteralNode&) {} + + void visit(const DataNode&) { + variable_counter += 1; + } + + void visit(const FunctionNode& node) { + for (auto& n : node.arguments) { + n->accept(*this); + } + } + + void visit(const ExpressionListNode& node) { + node.root->accept(*this); + } + + void visit(const StatementNode&) {} + void visit(const ForStatementNode&) {} + + void visit(const ForArrayStatementNode& node) { + node.condition.accept(*this); + node.body.accept(*this); + } + + void visit(const ForObjectStatementNode& node) { + node.condition.accept(*this); + node.body.accept(*this); + } + + void visit(const IfStatementNode& node) { + node.condition.accept(*this); + node.true_statement.accept(*this); + node.false_statement.accept(*this); + } + + void visit(const IncludeStatementNode&) {} + + void visit(const ExtendsStatementNode&) {} + + void visit(const BlockStatementNode& node) { + node.block.accept(*this); + } + + void visit(const SetStatementNode&) {} + +public: + unsigned int variable_counter; + + explicit StatisticsVisitor(): variable_counter(0) {} +}; + +} // namespace inja + +#endif // INCLUDE_INJA_STATISTICS_HPP_ + + +namespace inja { + +/*! + * \brief The main inja Template. + */ +struct Template { + BlockNode root; + std::string content; + std::map> block_storage; + + explicit Template() {} + explicit Template(const std::string& content): content(content) {} + + /// Return number of variables (total number, not distinct ones) in the template + int count_variables() { + auto statistic_visitor = StatisticsVisitor(); + root.accept(statistic_visitor); + return statistic_visitor.variable_counter; + } +}; + +using TemplateStorage = std::map; + +} // namespace inja + +#endif // INCLUDE_INJA_TEMPLATE_HPP_ + + +namespace inja { + +/*! + * \brief Class for lexer configuration. + */ +struct LexerConfig { + std::string statement_open {"{%"}; + std::string statement_open_no_lstrip {"{%+"}; + std::string statement_open_force_lstrip {"{%-"}; + std::string statement_close {"%}"}; + std::string statement_close_force_rstrip {"-%}"}; + std::string line_statement {"##"}; + std::string expression_open {"{{"}; + std::string expression_open_force_lstrip {"{{-"}; + std::string expression_close {"}}"}; + std::string expression_close_force_rstrip {"-}}"}; + std::string comment_open {"{#"}; + std::string comment_open_force_lstrip {"{#-"}; + std::string comment_close {"#}"}; + std::string comment_close_force_rstrip {"-#}"}; + std::string open_chars {"#{"}; + + bool trim_blocks {false}; + bool lstrip_blocks {false}; + + void update_open_chars() { + open_chars = ""; + if (open_chars.find(line_statement[0]) == std::string::npos) { + open_chars += line_statement[0]; + } + if (open_chars.find(statement_open[0]) == std::string::npos) { + open_chars += statement_open[0]; + } + if (open_chars.find(statement_open_no_lstrip[0]) == std::string::npos) { + open_chars += statement_open_no_lstrip[0]; + } + if (open_chars.find(statement_open_force_lstrip[0]) == std::string::npos) { + open_chars += statement_open_force_lstrip[0]; + } + if (open_chars.find(expression_open[0]) == std::string::npos) { + open_chars += expression_open[0]; + } + if (open_chars.find(expression_open_force_lstrip[0]) == std::string::npos) { + open_chars += expression_open_force_lstrip[0]; + } + if (open_chars.find(comment_open[0]) == std::string::npos) { + open_chars += comment_open[0]; + } + if (open_chars.find(comment_open_force_lstrip[0]) == std::string::npos) { + open_chars += comment_open_force_lstrip[0]; + } + } +}; + +/*! + * \brief Class for parser configuration. + */ +struct ParserConfig { + bool search_included_templates_in_files {true}; + + std::function include_callback; +}; + +/*! + * \brief Class for render configuration. + */ +struct RenderConfig { + bool throw_at_missing_includes {true}; +}; + +} // namespace inja + +#endif // INCLUDE_INJA_CONFIG_HPP_ + +// #include "function_storage.hpp" + +// #include "parser.hpp" +#ifndef INCLUDE_INJA_PARSER_HPP_ +#define INCLUDE_INJA_PARSER_HPP_ + +#include +#include +#include +#include +#include + +// #include "config.hpp" + +// #include "exceptions.hpp" + +// #include "function_storage.hpp" + +// #include "lexer.hpp" +#ifndef INCLUDE_INJA_LEXER_HPP_ +#define INCLUDE_INJA_LEXER_HPP_ + +#include +#include + +// #include "config.hpp" + +// #include "token.hpp" +#ifndef INCLUDE_INJA_TOKEN_HPP_ +#define INCLUDE_INJA_TOKEN_HPP_ + +#include +#include + +namespace inja { + +/*! + * \brief Helper-class for the inja Lexer. + */ +struct Token { + enum class Kind { + Text, + ExpressionOpen, // {{ + ExpressionClose, // }} + LineStatementOpen, // ## + LineStatementClose, // \n + StatementOpen, // {% + StatementClose, // %} + CommentOpen, // {# + CommentClose, // #} + Id, // this, this.foo + Number, // 1, 2, -1, 5.2, -5.3 + String, // "this" + Plus, // + + Minus, // - + Times, // * + Slash, // / + Percent, // % + Power, // ^ + Comma, // , + Dot, // . + Colon, // : + LeftParen, // ( + RightParen, // ) + LeftBracket, // [ + RightBracket, // ] + LeftBrace, // { + RightBrace, // } + Equal, // == + NotEqual, // != + GreaterThan, // > + GreaterEqual, // >= + LessThan, // < + LessEqual, // <= + Unknown, + Eof, + }; + + Kind kind {Kind::Unknown}; + std::string_view text; + + explicit constexpr Token() = default; + explicit constexpr Token(Kind kind, std::string_view text): kind(kind), text(text) {} + + std::string describe() const { + switch (kind) { + case Kind::Text: + return ""; + case Kind::LineStatementClose: + return ""; + case Kind::Eof: + return ""; + default: + return static_cast(text); + } + } +}; + +} // namespace inja + +#endif // INCLUDE_INJA_TOKEN_HPP_ + +// #include "utils.hpp" + + +namespace inja { + +/*! + * \brief Class for lexing an inja Template. + */ +class Lexer { + enum class State { + Text, + ExpressionStart, + ExpressionStartForceLstrip, + ExpressionBody, + LineStart, + LineBody, + StatementStart, + StatementStartNoLstrip, + StatementStartForceLstrip, + StatementBody, + CommentStart, + CommentStartForceLstrip, + CommentBody, + }; + + enum class MinusState { + Operator, + Number, + }; + + const LexerConfig& config; + + State state; + MinusState minus_state; + std::string_view m_in; + size_t tok_start; + size_t pos; + + Token scan_body(std::string_view close, Token::Kind closeKind, std::string_view close_trim = std::string_view(), bool trim = false) { + again: + // skip whitespace (except for \n as it might be a close) + if (tok_start >= m_in.size()) { + return make_token(Token::Kind::Eof); + } + const char ch = m_in[tok_start]; + if (ch == ' ' || ch == '\t' || ch == '\r') { + tok_start += 1; + goto again; + } + + // check for close + if (!close_trim.empty() && inja::string_view::starts_with(m_in.substr(tok_start), close_trim)) { + state = State::Text; + pos = tok_start + close_trim.size(); + const Token tok = make_token(closeKind); + skip_whitespaces_and_newlines(); + return tok; + } + + if (inja::string_view::starts_with(m_in.substr(tok_start), close)) { + state = State::Text; + pos = tok_start + close.size(); + const Token tok = make_token(closeKind); + if (trim) { + skip_whitespaces_and_first_newline(); + } + return tok; + } + + // skip \n + if (ch == '\n') { + tok_start += 1; + goto again; + } + + pos = tok_start + 1; + if (std::isalpha(ch)) { + minus_state = MinusState::Operator; + return scan_id(); + } + + const MinusState current_minus_state = minus_state; + if (minus_state == MinusState::Operator) { + minus_state = MinusState::Number; + } + + switch (ch) { + case '+': + return make_token(Token::Kind::Plus); + case '-': + if (current_minus_state == MinusState::Operator) { + return make_token(Token::Kind::Minus); + } + return scan_number(); + case '*': + return make_token(Token::Kind::Times); + case '/': + return make_token(Token::Kind::Slash); + case '^': + return make_token(Token::Kind::Power); + case '%': + return make_token(Token::Kind::Percent); + case '.': + return make_token(Token::Kind::Dot); + case ',': + return make_token(Token::Kind::Comma); + case ':': + return make_token(Token::Kind::Colon); + case '(': + return make_token(Token::Kind::LeftParen); + case ')': + minus_state = MinusState::Operator; + return make_token(Token::Kind::RightParen); + case '[': + return make_token(Token::Kind::LeftBracket); + case ']': + minus_state = MinusState::Operator; + return make_token(Token::Kind::RightBracket); + case '{': + return make_token(Token::Kind::LeftBrace); + case '}': + minus_state = MinusState::Operator; + return make_token(Token::Kind::RightBrace); + case '>': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::GreaterEqual); + } + return make_token(Token::Kind::GreaterThan); + case '<': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::LessEqual); + } + return make_token(Token::Kind::LessThan); + case '=': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::Equal); + } + return make_token(Token::Kind::Unknown); + case '!': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::NotEqual); + } + return make_token(Token::Kind::Unknown); + case '\"': + return scan_string(); + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + minus_state = MinusState::Operator; + return scan_number(); + case '_': + case '@': + case '$': + minus_state = MinusState::Operator; + return scan_id(); + default: + return make_token(Token::Kind::Unknown); + } + } + + Token scan_id() { + for (;;) { + if (pos >= m_in.size()) { + break; + } + const char ch = m_in[pos]; + if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') { + break; + } + pos += 1; + } + return make_token(Token::Kind::Id); + } + + Token scan_number() { + for (;;) { + if (pos >= m_in.size()) { + break; + } + const char ch = m_in[pos]; + // be very permissive in lexer (we'll catch errors when conversion happens) + if (!(std::isdigit(ch) || ch == '.' || ch == 'e' || ch == 'E' || (ch == '+' && (pos == 0 || m_in[pos-1] == 'e' || m_in[pos-1] == 'E')) || (ch == '-' && (pos == 0 || m_in[pos-1] == 'e' || m_in[pos-1] == 'E')))) { + break; + } + pos += 1; + } + return make_token(Token::Kind::Number); + } + + Token scan_string() { + bool escape {false}; + for (;;) { + if (pos >= m_in.size()) { + break; + } + const char ch = m_in[pos++]; + if (ch == '\\') { + escape = true; + } else if (!escape && ch == m_in[tok_start]) { + break; + } else { + escape = false; + } + } + return make_token(Token::Kind::String); + } + + Token make_token(Token::Kind kind) const { + return Token(kind, string_view::slice(m_in, tok_start, pos)); + } + + void skip_whitespaces_and_newlines() { + if (pos < m_in.size()) { + while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t' || m_in[pos] == '\n' || m_in[pos] == '\r')) { + pos += 1; + } + } + } + + void skip_whitespaces_and_first_newline() { + if (pos < m_in.size()) { + while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t')) { + pos += 1; + } + } + + if (pos < m_in.size()) { + const char ch = m_in[pos]; + if (ch == '\n') { + pos += 1; + } else if (ch == '\r') { + pos += 1; + if (pos < m_in.size() && m_in[pos] == '\n') { + pos += 1; + } + } + } + } + + static std::string_view clear_final_line_if_whitespace(std::string_view text) { + std::string_view result = text; + while (!result.empty()) { + const char ch = result.back(); + if (ch == ' ' || ch == '\t') { + result.remove_suffix(1); + } else if (ch == '\n' || ch == '\r') { + break; + } else { + return text; + } + } + return result; + } + +public: + explicit Lexer(const LexerConfig& config): config(config), state(State::Text), minus_state(MinusState::Number) {} + + SourceLocation current_position() const { + return get_source_location(m_in, tok_start); + } + + void start(std::string_view input) { + m_in = input; + tok_start = 0; + pos = 0; + state = State::Text; + minus_state = MinusState::Number; + + // Consume byte order mark (BOM) for UTF-8 + if (inja::string_view::starts_with(m_in, "\xEF\xBB\xBF")) { + m_in = m_in.substr(3); + } + } + + Token scan() { + tok_start = pos; + + again: + if (tok_start >= m_in.size()) { + return make_token(Token::Kind::Eof); + } + + switch (state) { + default: + case State::Text: { + // fast-scan to first open character + const size_t open_start = m_in.substr(pos).find_first_of(config.open_chars); + if (open_start == std::string_view::npos) { + // didn't find open, return remaining text as text token + pos = m_in.size(); + return make_token(Token::Kind::Text); + } + pos += open_start; + + // try to match one of the opening sequences, and get the close + std::string_view open_str = m_in.substr(pos); + bool must_lstrip = false; + if (inja::string_view::starts_with(open_str, config.expression_open)) { + if (inja::string_view::starts_with(open_str, config.expression_open_force_lstrip)) { + state = State::ExpressionStartForceLstrip; + must_lstrip = true; + } else { + state = State::ExpressionStart; + } + } else if (inja::string_view::starts_with(open_str, config.statement_open)) { + if (inja::string_view::starts_with(open_str, config.statement_open_no_lstrip)) { + state = State::StatementStartNoLstrip; + } else if (inja::string_view::starts_with(open_str, config.statement_open_force_lstrip)) { + state = State::StatementStartForceLstrip; + must_lstrip = true; + } else { + state = State::StatementStart; + must_lstrip = config.lstrip_blocks; + } + } else if (inja::string_view::starts_with(open_str, config.comment_open)) { + if (inja::string_view::starts_with(open_str, config.comment_open_force_lstrip)) { + state = State::CommentStartForceLstrip; + must_lstrip = true; + } else { + state = State::CommentStart; + must_lstrip = config.lstrip_blocks; + } + } else if ((pos == 0 || m_in[pos - 1] == '\n') && inja::string_view::starts_with(open_str, config.line_statement)) { + state = State::LineStart; + } else { + pos += 1; // wasn't actually an opening sequence + goto again; + } + + std::string_view text = string_view::slice(m_in, tok_start, pos); + if (must_lstrip) { + text = clear_final_line_if_whitespace(text); + } + + if (text.empty()) { + goto again; // don't generate empty token + } + return Token(Token::Kind::Text, text); + } + case State::ExpressionStart: { + state = State::ExpressionBody; + pos += config.expression_open.size(); + return make_token(Token::Kind::ExpressionOpen); + } + case State::ExpressionStartForceLstrip: { + state = State::ExpressionBody; + pos += config.expression_open_force_lstrip.size(); + return make_token(Token::Kind::ExpressionOpen); + } + case State::LineStart: { + state = State::LineBody; + pos += config.line_statement.size(); + return make_token(Token::Kind::LineStatementOpen); + } + case State::StatementStart: { + state = State::StatementBody; + pos += config.statement_open.size(); + return make_token(Token::Kind::StatementOpen); + } + case State::StatementStartNoLstrip: { + state = State::StatementBody; + pos += config.statement_open_no_lstrip.size(); + return make_token(Token::Kind::StatementOpen); + } + case State::StatementStartForceLstrip: { + state = State::StatementBody; + pos += config.statement_open_force_lstrip.size(); + return make_token(Token::Kind::StatementOpen); + } + case State::CommentStart: { + state = State::CommentBody; + pos += config.comment_open.size(); + return make_token(Token::Kind::CommentOpen); + } + case State::CommentStartForceLstrip: { + state = State::CommentBody; + pos += config.comment_open_force_lstrip.size(); + return make_token(Token::Kind::CommentOpen); + } + case State::ExpressionBody: + return scan_body(config.expression_close, Token::Kind::ExpressionClose, config.expression_close_force_rstrip); + case State::LineBody: + return scan_body("\n", Token::Kind::LineStatementClose); + case State::StatementBody: + return scan_body(config.statement_close, Token::Kind::StatementClose, config.statement_close_force_rstrip, config.trim_blocks); + case State::CommentBody: { + // fast-scan to comment close + const size_t end = m_in.substr(pos).find(config.comment_close); + if (end == std::string_view::npos) { + pos = m_in.size(); + return make_token(Token::Kind::Eof); + } + + // Check for trim pattern + const bool must_rstrip = inja::string_view::starts_with(m_in.substr(pos + end - 1), config.comment_close_force_rstrip); + + // return the entire comment in the close token + state = State::Text; + pos += end + config.comment_close.size(); + Token tok = make_token(Token::Kind::CommentClose); + + if (must_rstrip || config.trim_blocks) { + skip_whitespaces_and_first_newline(); + } + return tok; + } + } + } + + const LexerConfig& get_config() const { + return config; + } +}; + +} // namespace inja + +#endif // INCLUDE_INJA_LEXER_HPP_ + +// #include "node.hpp" + +// #include "template.hpp" + +// #include "token.hpp" + +// #include "utils.hpp" + + +namespace inja { + +/*! + * \brief Class for parsing an inja Template. + */ +class Parser { + using Arguments = std::vector>; + using OperatorStack = std::stack>; + + const ParserConfig& config; + + Lexer lexer; + TemplateStorage& template_storage; + const FunctionStorage& function_storage; + + Token tok, peek_tok; + bool have_peek_tok {false}; + + std::string_view literal_start; + + BlockNode* current_block {nullptr}; + ExpressionListNode* current_expression_list {nullptr}; + + std::stack if_statement_stack; + std::stack for_statement_stack; + std::stack block_statement_stack; + + inline void throw_parser_error(const std::string& message) const { + INJA_THROW(ParserError(message, lexer.current_position())); + } + + inline void get_next_token() { + if (have_peek_tok) { + tok = peek_tok; + have_peek_tok = false; + } else { + tok = lexer.scan(); + } + } + + inline void get_peek_token() { + if (!have_peek_tok) { + peek_tok = lexer.scan(); + have_peek_tok = true; + } + } + + inline void add_literal(Arguments &arguments, const char* content_ptr) { + std::string_view data_text(literal_start.data(), tok.text.data() - literal_start.data() + tok.text.size()); + arguments.emplace_back(std::make_shared(data_text, data_text.data() - content_ptr)); + } + + inline void add_operator(Arguments &arguments, OperatorStack &operator_stack) { + auto function = operator_stack.top(); + operator_stack.pop(); + + if (static_cast(arguments.size()) < function->number_args) { + throw_parser_error("too few arguments"); + } + + for (int i = 0; i < function->number_args; ++i) { + function->arguments.insert(function->arguments.begin(), arguments.back()); + arguments.pop_back(); + } + arguments.emplace_back(function); + } + + void add_to_template_storage(std::string_view path, std::string& template_name) { + if (template_storage.find(template_name) != template_storage.end()) { + return; + } + + std::string original_path = static_cast(path); + std::string original_name = template_name; + + if (config.search_included_templates_in_files) { + // Build the relative path + template_name = original_path + original_name; + if (template_name.compare(0, 2, "./") == 0) { + template_name.erase(0, 2); + } + + if (template_storage.find(template_name) == template_storage.end()) { + // Load file + std::ifstream file; + file.open(template_name); + if (!file.fail()) { + std::string text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + auto include_template = Template(text); + template_storage.emplace(template_name, include_template); + parse_into_template(template_storage[template_name], template_name); + return; + } else if (!config.include_callback) { + INJA_THROW(FileError("failed accessing file at '" + template_name + "'")); + } + } + } + + // Try include callback + if (config.include_callback) { + auto include_template = config.include_callback(original_path, original_name); + template_storage.emplace(template_name, include_template); + } + } + + std::string parse_filename() const { + if (tok.kind != Token::Kind::String) { + throw_parser_error("expected string, got '" + tok.describe() + "'"); + } + + if (tok.text.length() < 2) { + throw_parser_error("expected filename, got '" + static_cast(tok.text) + "'"); + } + + // Remove first and last character "" + return std::string {tok.text.substr(1, tok.text.length() - 2)}; + } + + bool parse_expression(Template& tmpl, Token::Kind closing) { + current_expression_list->root = parse_expression(tmpl); + return tok.kind == closing; + } + + std::shared_ptr parse_expression(Template& tmpl) { + size_t current_bracket_level {0}; + size_t current_brace_level {0}; + Arguments arguments; + OperatorStack operator_stack; + + while (tok.kind != Token::Kind::Eof) { + // Literals + switch (tok.kind) { + case Token::Kind::String: { + if (current_brace_level == 0 && current_bracket_level == 0) { + literal_start = tok.text; + add_literal(arguments, tmpl.content.c_str()); + } + } break; + case Token::Kind::Number: { + if (current_brace_level == 0 && current_bracket_level == 0) { + literal_start = tok.text; + add_literal(arguments, tmpl.content.c_str()); + } + } break; + case Token::Kind::LeftBracket: { + if (current_brace_level == 0 && current_bracket_level == 0) { + literal_start = tok.text; + } + current_bracket_level += 1; + } break; + case Token::Kind::LeftBrace: { + if (current_brace_level == 0 && current_bracket_level == 0) { + literal_start = tok.text; + } + current_brace_level += 1; + } break; + case Token::Kind::RightBracket: { + if (current_bracket_level == 0) { + throw_parser_error("unexpected ']'"); + } + + current_bracket_level -= 1; + if (current_brace_level == 0 && current_bracket_level == 0) { + add_literal(arguments, tmpl.content.c_str()); + } + } break; + case Token::Kind::RightBrace: { + if (current_brace_level == 0) { + throw_parser_error("unexpected '}'"); + } + + current_brace_level -= 1; + if (current_brace_level == 0 && current_bracket_level == 0) { + add_literal(arguments, tmpl.content.c_str()); + } + } break; + case Token::Kind::Id: { + get_peek_token(); + + // Data Literal + if (tok.text == static_cast("true") || tok.text == static_cast("false") || + tok.text == static_cast("null")) { + if (current_brace_level == 0 && current_bracket_level == 0) { + literal_start = tok.text; + add_literal(arguments, tmpl.content.c_str()); + } + + // Operator + } else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") { + goto parse_operator; + + // Functions + } else if (peek_tok.kind == Token::Kind::LeftParen) { + auto func = std::make_shared(tok.text, tok.text.data() - tmpl.content.c_str()); + get_next_token(); + do { + get_next_token(); + auto expr = parse_expression(tmpl); + if (!expr) { + break; + } + func->number_args += 1; + func->arguments.emplace_back(expr); + } while (tok.kind == Token::Kind::Comma); + if (tok.kind != Token::Kind::RightParen) { + throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'"); + } + + auto function_data = function_storage.find_function(func->name, func->number_args); + if (function_data.operation == FunctionStorage::Operation::None) { + throw_parser_error("unknown function " + func->name); + } + func->operation = function_data.operation; + if (function_data.operation == FunctionStorage::Operation::Callback) { + func->callback = function_data.callback; + } + arguments.emplace_back(func); + + // Variables + } else { + arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); + } + + // Operators + } break; + case Token::Kind::Equal: + case Token::Kind::NotEqual: + case Token::Kind::GreaterThan: + case Token::Kind::GreaterEqual: + case Token::Kind::LessThan: + case Token::Kind::LessEqual: + case Token::Kind::Plus: + case Token::Kind::Minus: + case Token::Kind::Times: + case Token::Kind::Slash: + case Token::Kind::Power: + case Token::Kind::Percent: + case Token::Kind::Dot: { + + parse_operator: + FunctionStorage::Operation operation; + switch (tok.kind) { + case Token::Kind::Id: { + if (tok.text == "and") { + operation = FunctionStorage::Operation::And; + } else if (tok.text == "or") { + operation = FunctionStorage::Operation::Or; + } else if (tok.text == "in") { + operation = FunctionStorage::Operation::In; + } else if (tok.text == "not") { + operation = FunctionStorage::Operation::Not; + } else { + throw_parser_error("unknown operator in parser."); + } + } break; + case Token::Kind::Equal: { + operation = FunctionStorage::Operation::Equal; + } break; + case Token::Kind::NotEqual: { + operation = FunctionStorage::Operation::NotEqual; + } break; + case Token::Kind::GreaterThan: { + operation = FunctionStorage::Operation::Greater; + } break; + case Token::Kind::GreaterEqual: { + operation = FunctionStorage::Operation::GreaterEqual; + } break; + case Token::Kind::LessThan: { + operation = FunctionStorage::Operation::Less; + } break; + case Token::Kind::LessEqual: { + operation = FunctionStorage::Operation::LessEqual; + } break; + case Token::Kind::Plus: { + operation = FunctionStorage::Operation::Add; + } break; + case Token::Kind::Minus: { + operation = FunctionStorage::Operation::Subtract; + } break; + case Token::Kind::Times: { + operation = FunctionStorage::Operation::Multiplication; + } break; + case Token::Kind::Slash: { + operation = FunctionStorage::Operation::Division; + } break; + case Token::Kind::Power: { + operation = FunctionStorage::Operation::Power; + } break; + case Token::Kind::Percent: { + operation = FunctionStorage::Operation::Modulo; + } break; + case Token::Kind::Dot: { + operation = FunctionStorage::Operation::AtId; + } break; + default: { + throw_parser_error("unknown operator in parser."); + } + } + auto function_node = std::make_shared(operation, tok.text.data() - tmpl.content.c_str()); + + while (!operator_stack.empty() && + ((operator_stack.top()->precedence > function_node->precedence) || + (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left))) { + add_operator(arguments, operator_stack); + } + + operator_stack.emplace(function_node); + } break; + case Token::Kind::Comma: { + if (current_brace_level == 0 && current_bracket_level == 0) { + goto break_loop; + } + } break; + case Token::Kind::Colon: { + if (current_brace_level == 0 && current_bracket_level == 0) { + throw_parser_error("unexpected ':'"); + } + } break; + case Token::Kind::LeftParen: { + get_next_token(); + auto expr = parse_expression(tmpl); + if (tok.kind != Token::Kind::RightParen) { + throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'"); + } + if (!expr) { + throw_parser_error("empty expression in parentheses"); + } + arguments.emplace_back(expr); + } break; + default: + goto break_loop; + } + + get_next_token(); + } + + break_loop: + while (!operator_stack.empty()) { + add_operator(arguments, operator_stack); + } + + std::shared_ptr expr; + if (arguments.size() == 1) { + expr = arguments[0]; + arguments = {}; + } else if (arguments.size() > 1) { + throw_parser_error("malformed expression"); + } + return expr; + } + + bool parse_statement(Template& tmpl, Token::Kind closing, std::string_view path) { + if (tok.kind != Token::Kind::Id) { + return false; + } + + if (tok.text == static_cast("if")) { + get_next_token(); + + auto if_statement_node = std::make_shared(current_block, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(if_statement_node); + if_statement_stack.emplace(if_statement_node.get()); + current_block = &if_statement_node->true_statement; + current_expression_list = &if_statement_node->condition; + + if (!parse_expression(tmpl, closing)) { + return false; + } + } else if (tok.text == static_cast("else")) { + if (if_statement_stack.empty()) { + throw_parser_error("else without matching if"); + } + auto& if_statement_data = if_statement_stack.top(); + get_next_token(); + + if_statement_data->has_false_statement = true; + current_block = &if_statement_data->false_statement; + + // Chained else if + if (tok.kind == Token::Kind::Id && tok.text == static_cast("if")) { + get_next_token(); + + auto if_statement_node = std::make_shared(true, current_block, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(if_statement_node); + if_statement_stack.emplace(if_statement_node.get()); + current_block = &if_statement_node->true_statement; + current_expression_list = &if_statement_node->condition; + + if (!parse_expression(tmpl, closing)) { + return false; + } + } + } else if (tok.text == static_cast("endif")) { + if (if_statement_stack.empty()) { + throw_parser_error("endif without matching if"); + } + + // Nested if statements + while (if_statement_stack.top()->is_nested) { + if_statement_stack.pop(); + } + + auto& if_statement_data = if_statement_stack.top(); + get_next_token(); + + current_block = if_statement_data->parent; + if_statement_stack.pop(); + } else if (tok.text == static_cast("block")) { + get_next_token(); + + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected block name, got '" + tok.describe() + "'"); + } + + const std::string block_name = static_cast(tok.text); + + auto block_statement_node = std::make_shared(current_block, block_name, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(block_statement_node); + block_statement_stack.emplace(block_statement_node.get()); + current_block = &block_statement_node->block; + auto success = tmpl.block_storage.emplace(block_name, block_statement_node); + if (!success.second) { + throw_parser_error("block with the name '" + block_name + "' does already exist"); + } + + get_next_token(); + } else if (tok.text == static_cast("endblock")) { + if (block_statement_stack.empty()) { + throw_parser_error("endblock without matching block"); + } + + auto& block_statement_data = block_statement_stack.top(); + get_next_token(); + + current_block = block_statement_data->parent; + block_statement_stack.pop(); + } else if (tok.text == static_cast("for")) { + get_next_token(); + + // options: for a in arr; for a, b in obj + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected id, got '" + tok.describe() + "'"); + } + + Token value_token = tok; + get_next_token(); + + // Object type + std::shared_ptr for_statement_node; + if (tok.kind == Token::Kind::Comma) { + get_next_token(); + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected id, got '" + tok.describe() + "'"); + } + + Token key_token = std::move(value_token); + value_token = tok; + get_next_token(); + + for_statement_node = std::make_shared(static_cast(key_token.text), static_cast(value_token.text), + current_block, tok.text.data() - tmpl.content.c_str()); + + // Array type + } else { + for_statement_node = + std::make_shared(static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); + } + + current_block->nodes.emplace_back(for_statement_node); + for_statement_stack.emplace(for_statement_node.get()); + current_block = &for_statement_node->body; + current_expression_list = &for_statement_node->condition; + + if (tok.kind != Token::Kind::Id || tok.text != static_cast("in")) { + throw_parser_error("expected 'in', got '" + tok.describe() + "'"); + } + get_next_token(); + + if (!parse_expression(tmpl, closing)) { + return false; + } + } else if (tok.text == static_cast("endfor")) { + if (for_statement_stack.empty()) { + throw_parser_error("endfor without matching for"); + } + + auto& for_statement_data = for_statement_stack.top(); + get_next_token(); + + current_block = for_statement_data->parent; + for_statement_stack.pop(); + } else if (tok.text == static_cast("include")) { + get_next_token(); + + std::string template_name = parse_filename(); + add_to_template_storage(path, template_name); + + current_block->nodes.emplace_back(std::make_shared(template_name, tok.text.data() - tmpl.content.c_str())); + + get_next_token(); + } else if (tok.text == static_cast("extends")) { + get_next_token(); + + std::string template_name = parse_filename(); + add_to_template_storage(path, template_name); + + current_block->nodes.emplace_back(std::make_shared(template_name, tok.text.data() - tmpl.content.c_str())); + + get_next_token(); + } else if (tok.text == static_cast("set")) { + get_next_token(); + + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected variable name, got '" + tok.describe() + "'"); + } + + std::string key = static_cast(tok.text); + get_next_token(); + + auto set_statement_node = std::make_shared(key, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(set_statement_node); + current_expression_list = &set_statement_node->expression; + + if (tok.text != static_cast("=")) { + throw_parser_error("expected '=', got '" + tok.describe() + "'"); + } + get_next_token(); + + if (!parse_expression(tmpl, closing)) { + return false; + } + } else { + return false; + } + return true; + } + + void parse_into(Template& tmpl, std::string_view path) { + lexer.start(tmpl.content); + current_block = &tmpl.root; + + for (;;) { + get_next_token(); + switch (tok.kind) { + case Token::Kind::Eof: { + if (!if_statement_stack.empty()) { + throw_parser_error("unmatched if"); + } + if (!for_statement_stack.empty()) { + throw_parser_error("unmatched for"); + } + } + return; + case Token::Kind::Text: { + current_block->nodes.emplace_back(std::make_shared(tok.text.data() - tmpl.content.c_str(), tok.text.size())); + } break; + case Token::Kind::StatementOpen: { + get_next_token(); + if (!parse_statement(tmpl, Token::Kind::StatementClose, path)) { + throw_parser_error("expected statement, got '" + tok.describe() + "'"); + } + if (tok.kind != Token::Kind::StatementClose) { + throw_parser_error("expected statement close, got '" + tok.describe() + "'"); + } + } break; + case Token::Kind::LineStatementOpen: { + get_next_token(); + if (!parse_statement(tmpl, Token::Kind::LineStatementClose, path)) { + throw_parser_error("expected statement, got '" + tok.describe() + "'"); + } + if (tok.kind != Token::Kind::LineStatementClose && tok.kind != Token::Kind::Eof) { + throw_parser_error("expected line statement close, got '" + tok.describe() + "'"); + } + } break; + case Token::Kind::ExpressionOpen: { + get_next_token(); + + auto expression_list_node = std::make_shared(tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(expression_list_node); + current_expression_list = expression_list_node.get(); + + if (!parse_expression(tmpl, Token::Kind::ExpressionClose)) { + throw_parser_error("expected expression close, got '" + tok.describe() + "'"); + } + } break; + case Token::Kind::CommentOpen: { + get_next_token(); + if (tok.kind != Token::Kind::CommentClose) { + throw_parser_error("expected comment close, got '" + tok.describe() + "'"); + } + } break; + default: { + throw_parser_error("unexpected token '" + tok.describe() + "'"); + } break; + } + } + } + +public: + explicit Parser(const ParserConfig& parser_config, const LexerConfig& lexer_config, TemplateStorage& template_storage, + const FunctionStorage& function_storage) + : config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) {} + + Template parse(std::string_view input, std::string_view path) { + auto result = Template(static_cast(input)); + parse_into(result, path); + return result; + } + + void parse_into_template(Template& tmpl, std::string_view filename) { + std::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1); + + // StringRef path = sys::path::parent_path(filename); + auto sub_parser = Parser(config, lexer.get_config(), template_storage, function_storage); + sub_parser.parse_into(tmpl, path); + } + + std::string load_file(const std::string& filename) { + std::ifstream file; + file.open(filename); + if (file.fail()) { + INJA_THROW(FileError("failed accessing file at '" + filename + "'")); + } + std::string text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + return text; + } +}; + +} // namespace inja + +#endif // INCLUDE_INJA_PARSER_HPP_ + +// #include "renderer.hpp" +#ifndef INCLUDE_INJA_RENDERER_HPP_ +#define INCLUDE_INJA_RENDERER_HPP_ + +#include +#include +#include +#include +#include + +// #include "config.hpp" + +// #include "exceptions.hpp" + +// #include "node.hpp" + +// #include "template.hpp" + +// #include "utils.hpp" + + +namespace inja { + +/*! + * \brief Class for rendering a Template with data. + */ +class Renderer : public NodeVisitor { + using Op = FunctionStorage::Operation; + + const RenderConfig config; + const TemplateStorage& template_storage; + const FunctionStorage& function_storage; + + const Template* current_template; + size_t current_level {0}; + std::vector template_stack; + std::vector block_statement_stack; + + const json* data_input; + std::ostream* output_stream; + + json additional_data; + json* current_loop_data = &additional_data["loop"]; + + std::vector> data_tmp_stack; + std::stack data_eval_stack; + std::stack not_found_stack; + + bool break_rendering {false}; + + static bool truthy(const json* data) { + if (data->is_boolean()) { + return data->get(); + } else if (data->is_number()) { + return (*data != 0); + } else if (data->is_null()) { + return false; + } + return !data->empty(); + } + + void print_data(const std::shared_ptr value) { + if (value->is_string()) { + *output_stream << value->get_ref(); + } else if (value->is_number_integer()) { + *output_stream << value->get(); + } else if (value->is_null()) { + } else { + *output_stream << value->dump(); + } + } + + const std::shared_ptr eval_expression_list(const ExpressionListNode& expression_list) { + if (!expression_list.root) { + throw_renderer_error("empty expression", expression_list); + } + + expression_list.root->accept(*this); + + if (data_eval_stack.empty()) { + throw_renderer_error("empty expression", expression_list); + } else if (data_eval_stack.size() != 1) { + throw_renderer_error("malformed expression", expression_list); + } + + const auto result = data_eval_stack.top(); + data_eval_stack.pop(); + + if (!result) { + if (not_found_stack.empty()) { + throw_renderer_error("expression could not be evaluated", expression_list); + } + + auto node = not_found_stack.top(); + not_found_stack.pop(); + + throw_renderer_error("variable '" + static_cast(node->name) + "' not found", *node); + } + return std::make_shared(*result); + } + + void throw_renderer_error(const std::string& message, const AstNode& node) { + SourceLocation loc = get_source_location(current_template->content, node.pos); + INJA_THROW(RenderError(message, loc)); + } + + void make_result(const json&& result) { + auto result_ptr = std::make_shared(result); + data_tmp_stack.push_back(result_ptr); + data_eval_stack.push(result_ptr.get()); + } + + template std::array get_arguments(const FunctionNode& node) { + if (node.arguments.size() < N_start + N) { + throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " + std::to_string(node.arguments.size()), node); + } + + for (size_t i = N_start; i < N_start + N; i += 1) { + node.arguments[i]->accept(*this); + } + + if (data_eval_stack.size() < N) { + throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node); + } + + std::array result; + for (size_t i = 0; i < N; i += 1) { + result[N - i - 1] = data_eval_stack.top(); + data_eval_stack.pop(); + + if (!result[N - i - 1]) { + const auto data_node = not_found_stack.top(); + not_found_stack.pop(); + + if (throw_not_found) { + throw_renderer_error("variable '" + static_cast(data_node->name) + "' not found", *data_node); + } + } + } + return result; + } + + template Arguments get_argument_vector(const FunctionNode& node) { + const size_t N = node.arguments.size(); + for (auto a : node.arguments) { + a->accept(*this); + } + + if (data_eval_stack.size() < N) { + throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node); + } + + Arguments result {N}; + for (size_t i = 0; i < N; i += 1) { + result[N - i - 1] = data_eval_stack.top(); + data_eval_stack.pop(); + + if (!result[N - i - 1]) { + const auto data_node = not_found_stack.top(); + not_found_stack.pop(); + + if (throw_not_found) { + throw_renderer_error("variable '" + static_cast(data_node->name) + "' not found", *data_node); + } + } + } + return result; + } + + void visit(const BlockNode& node) { + for (auto& n : node.nodes) { + n->accept(*this); + + if (break_rendering) { + break; + } + } + } + + void visit(const TextNode& node) { + output_stream->write(current_template->content.c_str() + node.pos, node.length); + } + + void visit(const ExpressionNode&) {} + + void visit(const LiteralNode& node) { + data_eval_stack.push(&node.value); + } + + void visit(const DataNode& node) { + if (additional_data.contains(node.ptr)) { + data_eval_stack.push(&(additional_data[node.ptr])); + } else if (data_input->contains(node.ptr)) { + data_eval_stack.push(&(*data_input)[node.ptr]); + } else { + // Try to evaluate as a no-argument callback + const auto function_data = function_storage.find_function(node.name, 0); + if (function_data.operation == FunctionStorage::Operation::Callback) { + Arguments empty_args {}; + const auto value = std::make_shared(function_data.callback(empty_args)); + data_tmp_stack.push_back(value); + data_eval_stack.push(value.get()); + } else { + data_eval_stack.push(nullptr); + not_found_stack.emplace(&node); + } + } + } + + void visit(const FunctionNode& node) { + switch (node.operation) { + case Op::Not: { + const auto args = get_arguments<1>(node); + make_result(!truthy(args[0])); + } break; + case Op::And: { + make_result(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0])); + } break; + case Op::Or: { + make_result(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0])); + } break; + case Op::In: { + const auto args = get_arguments<2>(node); + make_result(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end()); + } break; + case Op::Equal: { + const auto args = get_arguments<2>(node); + make_result(*args[0] == *args[1]); + } break; + case Op::NotEqual: { + const auto args = get_arguments<2>(node); + make_result(*args[0] != *args[1]); + } break; + case Op::Greater: { + const auto args = get_arguments<2>(node); + make_result(*args[0] > *args[1]); + } break; + case Op::GreaterEqual: { + const auto args = get_arguments<2>(node); + make_result(*args[0] >= *args[1]); + } break; + case Op::Less: { + const auto args = get_arguments<2>(node); + make_result(*args[0] < *args[1]); + } break; + case Op::LessEqual: { + const auto args = get_arguments<2>(node); + make_result(*args[0] <= *args[1]); + } break; + case Op::Add: { + const auto args = get_arguments<2>(node); + if (args[0]->is_string() && args[1]->is_string()) { + make_result(args[0]->get_ref() + args[1]->get_ref()); + } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) { + make_result(args[0]->get() + args[1]->get()); + } else { + make_result(args[0]->get() + args[1]->get()); + } + } break; + case Op::Subtract: { + const auto args = get_arguments<2>(node); + if (args[0]->is_number_integer() && args[1]->is_number_integer()) { + make_result(args[0]->get() - args[1]->get()); + } else { + make_result(args[0]->get() - args[1]->get()); + } + } break; + case Op::Multiplication: { + const auto args = get_arguments<2>(node); + if (args[0]->is_number_integer() && args[1]->is_number_integer()) { + make_result(args[0]->get() * args[1]->get()); + } else { + make_result(args[0]->get() * args[1]->get()); + } + } break; + case Op::Division: { + const auto args = get_arguments<2>(node); + if (args[1]->get() == 0) { + throw_renderer_error("division by zero", node); + } + make_result(args[0]->get() / args[1]->get()); + } break; + case Op::Power: { + const auto args = get_arguments<2>(node); + if (args[0]->is_number_integer() && args[1]->get() >= 0) { + const auto result = static_cast(std::pow(args[0]->get(), args[1]->get())); + make_result(result); + } else { + const auto result = std::pow(args[0]->get(), args[1]->get()); + make_result(result); + } + } break; + case Op::Modulo: { + const auto args = get_arguments<2>(node); + make_result(args[0]->get() % args[1]->get()); + } break; + case Op::AtId: { + const auto container = get_arguments<1, 0, false>(node)[0]; + node.arguments[1]->accept(*this); + if (not_found_stack.empty()) { + throw_renderer_error("could not find element with given name", node); + } + const auto id_node = not_found_stack.top(); + not_found_stack.pop(); + data_eval_stack.pop(); + data_eval_stack.push(&container->at(id_node->name)); + } break; + case Op::At: { + const auto args = get_arguments<2>(node); + if (args[0]->is_object()) { + data_eval_stack.push(&args[0]->at(args[1]->get())); + } else { + data_eval_stack.push(&args[0]->at(args[1]->get())); + } + } break; + case Op::Default: { + const auto test_arg = get_arguments<1, 0, false>(node)[0]; + data_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]); + } break; + case Op::DivisibleBy: { + const auto args = get_arguments<2>(node); + const auto divisor = args[1]->get(); + make_result((divisor != 0) && (args[0]->get() % divisor == 0)); + } break; + case Op::Even: { + make_result(get_arguments<1>(node)[0]->get() % 2 == 0); + } break; + case Op::Exists: { + auto&& name = get_arguments<1>(node)[0]->get_ref(); + make_result(data_input->contains(json::json_pointer(DataNode::convert_dot_to_ptr(name)))); + } break; + case Op::ExistsInObject: { + const auto args = get_arguments<2>(node); + auto&& name = args[1]->get_ref(); + make_result(args[0]->find(name) != args[0]->end()); + } break; + case Op::First: { + const auto result = &get_arguments<1>(node)[0]->front(); + data_eval_stack.push(result); + } break; + case Op::Float: { + make_result(std::stod(get_arguments<1>(node)[0]->get_ref())); + } break; + case Op::Int: { + make_result(std::stoi(get_arguments<1>(node)[0]->get_ref())); + } break; + case Op::Last: { + const auto result = &get_arguments<1>(node)[0]->back(); + data_eval_stack.push(result); + } break; + case Op::Length: { + const auto val = get_arguments<1>(node)[0]; + if (val->is_string()) { + make_result(val->get_ref().length()); + } else { + make_result(val->size()); + } + } break; + case Op::Lower: { + auto result = get_arguments<1>(node)[0]->get(); + std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast(::tolower(c)); }); + make_result(std::move(result)); + } break; + case Op::Max: { + const auto args = get_arguments<1>(node); + const auto result = std::max_element(args[0]->begin(), args[0]->end()); + data_eval_stack.push(&(*result)); + } break; + case Op::Min: { + const auto args = get_arguments<1>(node); + const auto result = std::min_element(args[0]->begin(), args[0]->end()); + data_eval_stack.push(&(*result)); + } break; + case Op::Odd: { + make_result(get_arguments<1>(node)[0]->get() % 2 != 0); + } break; + case Op::Range: { + std::vector result(get_arguments<1>(node)[0]->get()); + std::iota(result.begin(), result.end(), 0); + make_result(std::move(result)); + } break; + case Op::Round: { + const auto args = get_arguments<2>(node); + const int precision = args[1]->get(); + const double result = std::round(args[0]->get() * std::pow(10.0, precision)) / std::pow(10.0, precision); + if (precision == 0) { + make_result(int(result)); + } else { + make_result(result); + } + } break; + case Op::Sort: { + auto result_ptr = std::make_shared(get_arguments<1>(node)[0]->get>()); + std::sort(result_ptr->begin(), result_ptr->end()); + data_tmp_stack.push_back(result_ptr); + data_eval_stack.push(result_ptr.get()); + } break; + case Op::Upper: { + auto result = get_arguments<1>(node)[0]->get(); + std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast(::toupper(c)); }); + make_result(std::move(result)); + } break; + case Op::IsBoolean: { + make_result(get_arguments<1>(node)[0]->is_boolean()); + } break; + case Op::IsNumber: { + make_result(get_arguments<1>(node)[0]->is_number()); + } break; + case Op::IsInteger: { + make_result(get_arguments<1>(node)[0]->is_number_integer()); + } break; + case Op::IsFloat: { + make_result(get_arguments<1>(node)[0]->is_number_float()); + } break; + case Op::IsObject: { + make_result(get_arguments<1>(node)[0]->is_object()); + } break; + case Op::IsArray: { + make_result(get_arguments<1>(node)[0]->is_array()); + } break; + case Op::IsString: { + make_result(get_arguments<1>(node)[0]->is_string()); + } break; + case Op::Callback: { + auto args = get_argument_vector(node); + make_result(node.callback(args)); + } break; + case Op::Super: { + const auto args = get_argument_vector(node); + const size_t old_level = current_level; + const size_t level_diff = (args.size() == 1) ? args[0]->get() : 1; + const size_t level = current_level + level_diff; + + if (block_statement_stack.empty()) { + throw_renderer_error("super() call is not within a block", node); + } + + if (level < 1 || level > template_stack.size() - 1) { + throw_renderer_error("level of super() call does not match parent templates (between 1 and " + std::to_string(template_stack.size() - 1) + ")", node); + } + + const auto current_block_statement = block_statement_stack.back(); + const Template* new_template = template_stack.at(level); + const Template* old_template = current_template; + const auto block_it = new_template->block_storage.find(current_block_statement->name); + if (block_it != new_template->block_storage.end()) { + current_template = new_template; + current_level = level; + block_it->second->block.accept(*this); + current_level = old_level; + current_template = old_template; + } else { + throw_renderer_error("could not find block with name '" + current_block_statement->name + "'", node); + } + make_result(nullptr); + } break; + case Op::Join: { + const auto args = get_arguments<2>(node); + const auto separator = args[1]->get(); + std::ostringstream os; + std::string sep; + for (const auto& value : *args[0]) { + os << sep; + if (value.is_string()) { + os << value.get(); // otherwise the value is surrounded with "" + } else { + os << value.dump(); + } + sep = separator; + } + make_result(os.str()); + } break; + case Op::None: + break; + } + } + + void visit(const ExpressionListNode& node) { + print_data(eval_expression_list(node)); + } + + void visit(const StatementNode&) {} + + void visit(const ForStatementNode&) {} + + void visit(const ForArrayStatementNode& node) { + const auto result = eval_expression_list(node.condition); + if (!result->is_array()) { + throw_renderer_error("object must be an array", node); + } + + if (!current_loop_data->empty()) { + auto tmp = *current_loop_data; // Because of clang-3 + (*current_loop_data)["parent"] = std::move(tmp); + } + + size_t index = 0; + (*current_loop_data)["is_first"] = true; + (*current_loop_data)["is_last"] = (result->size() <= 1); + for (auto it = result->begin(); it != result->end(); ++it) { + additional_data[static_cast(node.value)] = *it; + + (*current_loop_data)["index"] = index; + (*current_loop_data)["index1"] = index + 1; + if (index == 1) { + (*current_loop_data)["is_first"] = false; + } + if (index == result->size() - 1) { + (*current_loop_data)["is_last"] = true; + } + + node.body.accept(*this); + ++index; + } + + additional_data[static_cast(node.value)].clear(); + if (!(*current_loop_data)["parent"].empty()) { + const auto tmp = (*current_loop_data)["parent"]; + *current_loop_data = std::move(tmp); + } else { + current_loop_data = &additional_data["loop"]; + } + } + + void visit(const ForObjectStatementNode& node) { + const auto result = eval_expression_list(node.condition); + if (!result->is_object()) { + throw_renderer_error("object must be an object", node); + } + + if (!current_loop_data->empty()) { + (*current_loop_data)["parent"] = std::move(*current_loop_data); + } + + size_t index = 0; + (*current_loop_data)["is_first"] = true; + (*current_loop_data)["is_last"] = (result->size() <= 1); + for (auto it = result->begin(); it != result->end(); ++it) { + additional_data[static_cast(node.key)] = it.key(); + additional_data[static_cast(node.value)] = it.value(); + + (*current_loop_data)["index"] = index; + (*current_loop_data)["index1"] = index + 1; + if (index == 1) { + (*current_loop_data)["is_first"] = false; + } + if (index == result->size() - 1) { + (*current_loop_data)["is_last"] = true; + } + + node.body.accept(*this); + ++index; + } + + additional_data[static_cast(node.key)].clear(); + additional_data[static_cast(node.value)].clear(); + if (!(*current_loop_data)["parent"].empty()) { + *current_loop_data = std::move((*current_loop_data)["parent"]); + } else { + current_loop_data = &additional_data["loop"]; + } + } + + void visit(const IfStatementNode& node) { + const auto result = eval_expression_list(node.condition); + if (truthy(result.get())) { + node.true_statement.accept(*this); + } else if (node.has_false_statement) { + node.false_statement.accept(*this); + } + } + + void visit(const IncludeStatementNode& node) { + auto sub_renderer = Renderer(config, template_storage, function_storage); + const auto included_template_it = template_storage.find(node.file); + if (included_template_it != template_storage.end()) { + sub_renderer.render_to(*output_stream, included_template_it->second, *data_input, &additional_data); + } else if (config.throw_at_missing_includes) { + throw_renderer_error("include '" + node.file + "' not found", node); + } + } + + void visit(const ExtendsStatementNode& node) { + const auto included_template_it = template_storage.find(node.file); + if (included_template_it != template_storage.end()) { + const Template* parent_template = &included_template_it->second; + render_to(*output_stream, *parent_template, *data_input, &additional_data); + break_rendering = true; + } else if (config.throw_at_missing_includes) { + throw_renderer_error("extends '" + node.file + "' not found", node); + } + } + + void visit(const BlockStatementNode& node) { + const size_t old_level = current_level; + current_level = 0; + current_template = template_stack.front(); + const auto block_it = current_template->block_storage.find(node.name); + if (block_it != current_template->block_storage.end()) { + block_statement_stack.emplace_back(&node); + block_it->second->block.accept(*this); + block_statement_stack.pop_back(); + } + current_level = old_level; + current_template = template_stack.back(); + } + + void visit(const SetStatementNode& node) { + std::string ptr = node.key; + replace_substring(ptr, ".", "/"); + ptr = "/" + ptr; + additional_data[json::json_pointer(ptr)] = *eval_expression_list(node.expression); + } + +public: + Renderer(const RenderConfig& config, const TemplateStorage& template_storage, const FunctionStorage& function_storage) + : config(config), template_storage(template_storage), function_storage(function_storage) {} + + void render_to(std::ostream& os, const Template& tmpl, const json& data, json* loop_data = nullptr) { + output_stream = &os; + current_template = &tmpl; + data_input = &data; + if (loop_data) { + additional_data = *loop_data; + current_loop_data = &additional_data["loop"]; + } + + template_stack.emplace_back(current_template); + current_template->root.accept(*this); + + data_tmp_stack.clear(); + } +}; + +} // namespace inja + +#endif // INCLUDE_INJA_RENDERER_HPP_ + +// #include "template.hpp" + +// #include "utils.hpp" + + +namespace inja { + +/*! + * \brief Class for changing the configuration. + */ +class Environment { + LexerConfig lexer_config; + ParserConfig parser_config; + RenderConfig render_config; + + FunctionStorage function_storage; + TemplateStorage template_storage; + +protected: + std::string input_path; + std::string output_path; + +public: + Environment(): Environment("") {} + + explicit Environment(const std::string& global_path): input_path(global_path), output_path(global_path) {} + + Environment(const std::string& input_path, const std::string& output_path): input_path(input_path), output_path(output_path) {} + + /// Sets the opener and closer for template statements + void set_statement(const std::string& open, const std::string& close) { + lexer_config.statement_open = open; + lexer_config.statement_open_no_lstrip = open + "+"; + lexer_config.statement_open_force_lstrip = open + "-"; + lexer_config.statement_close = close; + lexer_config.statement_close_force_rstrip = "-" + close; + lexer_config.update_open_chars(); + } + + /// Sets the opener for template line statements + void set_line_statement(const std::string& open) { + lexer_config.line_statement = open; + lexer_config.update_open_chars(); + } + + /// Sets the opener and closer for template expressions + void set_expression(const std::string& open, const std::string& close) { + lexer_config.expression_open = open; + lexer_config.expression_open_force_lstrip = open + "-"; + lexer_config.expression_close = close; + lexer_config.expression_close_force_rstrip = "-" + close; + lexer_config.update_open_chars(); + } + + /// Sets the opener and closer for template comments + void set_comment(const std::string& open, const std::string& close) { + lexer_config.comment_open = open; + lexer_config.comment_open_force_lstrip = open + "-"; + lexer_config.comment_close = close; + lexer_config.comment_close_force_rstrip = "-" + close; + lexer_config.update_open_chars(); + } + + /// Sets whether to remove the first newline after a block + void set_trim_blocks(bool trim_blocks) { + lexer_config.trim_blocks = trim_blocks; + } + + /// Sets whether to strip the spaces and tabs from the start of a line to a block + void set_lstrip_blocks(bool lstrip_blocks) { + lexer_config.lstrip_blocks = lstrip_blocks; + } + + /// Sets the element notation syntax + void set_search_included_templates_in_files(bool search_in_files) { + parser_config.search_included_templates_in_files = search_in_files; + } + + /// Sets whether a missing include will throw an error + void set_throw_at_missing_includes(bool will_throw) { + render_config.throw_at_missing_includes = will_throw; + } + + Template parse(std::string_view input) { + Parser parser(parser_config, lexer_config, template_storage, function_storage); + return parser.parse(input, input_path); + } + + Template parse_template(const std::string& filename) { + Parser parser(parser_config, lexer_config, template_storage, function_storage); + auto result = Template(parser.load_file(input_path + static_cast(filename))); + parser.parse_into_template(result, input_path + static_cast(filename)); + return result; + } + + Template parse_file(const std::string& filename) { + return parse_template(filename); + } + + std::string render(std::string_view input, const json& data) { + return render(parse(input), data); + } + + std::string render(const Template& tmpl, const json& data) { + std::stringstream os; + render_to(os, tmpl, data); + return os.str(); + } + + std::string render_file(const std::string& filename, const json& data) { + return render(parse_template(filename), data); + } + + std::string render_file_with_json_file(const std::string& filename, const std::string& filename_data) { + const json data = load_json(filename_data); + return render_file(filename, data); + } + + void write(const std::string& filename, const json& data, const std::string& filename_out) { + std::ofstream file(output_path + filename_out); + file << render_file(filename, data); + file.close(); + } + + void write(const Template& temp, const json& data, const std::string& filename_out) { + std::ofstream file(output_path + filename_out); + file << render(temp, data); + file.close(); + } + + void write_with_json_file(const std::string& filename, const std::string& filename_data, const std::string& filename_out) { + const json data = load_json(filename_data); + write(filename, data, filename_out); + } + + void write_with_json_file(const Template& temp, const std::string& filename_data, const std::string& filename_out) { + const json data = load_json(filename_data); + write(temp, data, filename_out); + } + + std::ostream& render_to(std::ostream& os, const Template& tmpl, const json& data) { + Renderer(render_config, template_storage, function_storage).render_to(os, tmpl, data); + return os; + } + + std::string load_file(const std::string& filename) { + Parser parser(parser_config, lexer_config, template_storage, function_storage); + return parser.load_file(input_path + filename); + } + + json load_json(const std::string& filename) { + std::ifstream file; + file.open(input_path + filename); + if (file.fail()) { + INJA_THROW(FileError("failed accessing file at '" + input_path + filename + "'")); + } + + return json::parse(std::istreambuf_iterator(file), std::istreambuf_iterator()); + } + + /*! + @brief Adds a variadic callback + */ + void add_callback(const std::string& name, const CallbackFunction& callback) { + add_callback(name, -1, callback); + } + + /*! + @brief Adds a variadic void callback + */ + void add_void_callback(const std::string& name, const VoidCallbackFunction& callback) { + add_void_callback(name, -1, callback); + } + + /*! + @brief Adds a callback with given number or arguments + */ + void add_callback(const std::string& name, int num_args, const CallbackFunction& callback) { + function_storage.add_callback(name, num_args, callback); + } + + /*! + @brief Adds a void callback with given number or arguments + */ + void add_void_callback(const std::string& name, int num_args, const VoidCallbackFunction& callback) { + function_storage.add_callback(name, num_args, [callback](Arguments& args) { + callback(args); + return json(); + }); + } + + /** Includes a template with a given name into the environment. + * Then, a template can be rendered in another template using the + * include "" syntax. + */ + void include_template(const std::string& name, const Template& tmpl) { + template_storage[name] = tmpl; + } + + /*! + @brief Sets a function that is called when an included file is not found + */ + void set_include_callback(const std::function& callback) { + parser_config.include_callback = callback; + } +}; + +/*! +@brief render with default settings to a string +*/ +inline std::string render(std::string_view input, const json& data) { + return Environment().render(input, data); +} + +/*! +@brief render with default settings to the given output stream +*/ +inline void render_to(std::ostream& os, std::string_view input, const json& data) { + Environment env; + env.render_to(os, env.parse(input), data); +} + +} // namespace inja + +#endif // INCLUDE_INJA_ENVIRONMENT_HPP_ + +// #include "exceptions.hpp" + +// #include "parser.hpp" + +// #include "renderer.hpp" + +// #include "template.hpp" + + +#endif // INCLUDE_INJA_INJA_HPP_ diff --git a/install_manifest.txt b/install_manifest.txt deleted file mode 100644 index 29cb52c..0000000 --- a/install_manifest.txt +++ /dev/null @@ -1,25 +0,0 @@ -/usr/local/bin/cmaker -/usr/local/lib/libcurl.so.4.8.0 -/usr/local/lib/libcurl.so.4 -/usr/local/lib/libcurl.so -/usr/local/bin/curl-config -/usr/local/lib/pkgconfig/libcurl.pc -/usr/local/include/curl/multi.h -/usr/local/include/curl/options.h -/usr/local/include/curl/urlapi.h -/usr/local/include/curl/header.h -/usr/local/include/curl/websockets.h -/usr/local/include/curl/curlver.h -/usr/local/include/curl/system.h -/usr/local/include/curl/typecheck-gcc.h -/usr/local/include/curl/easy.h -/usr/local/include/curl/stdcheaders.h -/usr/local/include/curl/curl.h -/usr/local/include/curl/mprintf.h -/usr/local/lib/cmake/CURL/CURLTargets.cmake -/usr/local/lib/cmake/CURL/CURLTargets-release.cmake -/usr/local/lib/cmake/CURL/CURLConfigVersion.cmake -/usr/local/lib/cmake/CURL/CURLConfig.cmake -/usr/local/lib/cmake/termcolor/termcolor-config.cmake -/usr/local/include/termcolor/termcolor.hpp -/usr/local/lib/cmake/termcolor/termcolor-targets.cmake \ No newline at end of file diff --git a/src/Command/Command.hpp b/src/Command/Command.hpp index 2eee2df..68083c1 100644 --- a/src/Command/Command.hpp +++ b/src/Command/Command.hpp @@ -2,7 +2,6 @@ #include "nlohmann/json_fwd.hpp" #include #include -#include #include #include #include @@ -10,12 +9,15 @@ #include #include #include +#include "../Utils/CLI.hpp" #define ENDL "\n" namespace Command { using nlohmann::json; + using Utils::CLI::Prompt; + using namespace Utils::CLI::Ansi; [[deprecated("I DON'T KNOW WHAT THIS FUCKING DOES")]] bool handleCppProject(); [[deprecated("WE ARE A BIG PROJECT MUST HAVE DEPRECATED STUFF")]] @@ -36,15 +38,15 @@ namespace Command { } // opening the file if (file) { - std::cout << "file config.json exists" << std::endl; - std::cout << "do you want to overwrite it?[y/n]:"; - std::string input; - std::getline(std::cin, input); - if (input != "y"){ + + Prompt *prompt = new Prompt("file config.json already exists\n Are you sure you would like to overwrite it?"); + prompt->Color(RED)->ExitOnFailure()->Run(); + if(prompt->Get()){ + file.close(); + return true; + }else{ exit(1); } - file.close(); - return true; } return false; } @@ -59,23 +61,26 @@ namespace Command { ParseResult removeOptions(int argc, char** argv); ParseResult updateOptions(int argc, char** argv); - typedef struct packageResult { + typedef struct Package_s { std::string name; std::string url; std::vector versions; std::string target_link; std::string description; int score; - } packageResult; + //TODO: implement this + json toJson(); + void fromJson(json j); + } Package;//Deez nuts - typedef struct dependency { + typedef struct Dependency_s { std::string name; std::string url; std::string version; std::string target_link; - } dependency; + } Dependency; - typedef struct BuildServer { + typedef struct BuildServer_s { std::string name; std::string ip; std::string username; @@ -85,10 +90,17 @@ namespace Command { int port; } BuildServer; - typedef struct Context { + namespace ProjectType { + const std::string EXECUTABLE = "executable"; + const std::string HEADER_ONLY = "header_only"; + const std::string STATIC_LIBRARY = "static_library"; + const std::string SHARED_LIBRARY = "shared_library"; + }; + typedef struct Project_s { std::string project_name; - std::string project_type; std::string project_description; + std::string project_type = ProjectType::EXECUTABLE; + BuildServer build_server; std::filesystem::path project_path; std::string git{"null"}; std::string lang{"cpp"}; @@ -98,16 +110,16 @@ namespace Command { std::vector authors; std::string src_dir{"src"}; std::string include_dir{"include"}; - std::vector dependencies; + std::vector dependencies; std::vector build_servers; std::string build_dir{"build"}; - dependency testing_lib; + Dependency testing_lib; std::string project_version{"0.0.1"}; std::vector flags; std::shared_ptr args; void fromJson(json j); nlohmann::json toJson(); - } Context; + } Project; class Interface{ private: //Commands; @@ -118,16 +130,20 @@ namespace Command { bool update(); bool run(); bool help(); + bool search(); bool addFlag(); bool server(); - + bool setBuildServer(std::vector servers); + bool getBuildServer(); bool addAuthors(); bool addDependency(); bool ftp(); bool watch(); bool clean(); + //TODO: setup register comamnd + bool registerCommand(std::string name, std::vector subcommands, std::function func); public: - std::shared_ptr ctx; + std::shared_ptr pro; bool parse(); std::shared_ptr options; std::shared_ptr args; @@ -141,6 +157,7 @@ namespace Command { }; namespace OptionsInit{ bool Init(Interface*); + bool Search(Interface*); bool Add(Interface*); bool Remove(Interface*); bool Server(Interface*); @@ -156,7 +173,7 @@ namespace Command { * @return bool -> finished successfully */ [[deprecated("Old function, Command::Interface ")]] - bool loadPackageJson(std::shared_ptr ctx); + bool loadPackageJson(std::shared_ptr pro); /* * Initializes the project * Prompts the user for information about the project @@ -164,14 +181,14 @@ namespace Command { * @return bool -> finished successfully */ [[deprecated("Old function, Command::Interface ")]] - bool init(std::shared_ptr, cxxopts::ParseResult &args); + bool init(std::shared_ptr, cxxopts::ParseResult &args); /* * Builds and runs the project * @param ctx the current project context * @return bool -> finished successfully */ [[deprecated("Old function, Command::Interface ")]] - bool run(std::shared_ptr); + bool run(std::shared_ptr); /* * Fuck this project function * Deletes the project entirely @@ -179,14 +196,14 @@ namespace Command { * @return bool -> finished successfully */ [[deprecated("Old function, Command::Interface ")]] - bool ftp(std::shared_ptr); + bool ftp(std::shared_ptr); /* * Adds flags to build script * @param ctx project context * @return bool -> finished successfully */ [[deprecated("Old function, Command::Interface ")]] - bool addFlag(std::shared_ptr, cxxopts::ParseResult &args); + bool addFlag(std::shared_ptr, cxxopts::ParseResult &args); /* * prints all commands and their function and parameters * @return bool -> finished successfully @@ -198,20 +215,20 @@ namespace Command { * @returns bool -> finished successfully */ [[deprecated("Old function, Command::Interface ")]] - bool addDependency(std::shared_ptr ctx, cxxopts::ParseResult &args); + bool addDependency(std::shared_ptr pro, cxxopts::ParseResult &args); [[deprecated("Old function, Command::Interface ")]] - bool addAuthor(std::shared_ptrctx, cxxopts::ParseResult &args); + bool addAuthor(std::shared_ptrctx, cxxopts::ParseResult &args); /* * Generates cmake file for project based on the current project context * @returns bool -> finished successfully */ [[deprecated("Old function, Command::Interface ")]] - bool createCMakelists(std::shared_ptr); + bool createCMakelists(std::shared_ptr); /* * What the fuck lucas */ - std::vector searchPackage(std::string query); + std::vector searchPackage(std::string query); std::string downloadIndex(); /* @@ -219,27 +236,27 @@ namespace Command { * @returns bool -> finished successfully */ [[deprecated("Old function, Command::Interface ")]] - bool add(std::shared_ptr ctx, cxxopts::ParseResult &args); + bool add(std::shared_ptr pro, cxxopts::ParseResult &args); json fetchIndex(); void updateIndex(); [[deprecated("Old function, Command::Interface ")]] - bool update(std::shared_ptr ctx, cxxopts::ParseResult &args); + bool update(std::shared_ptr pro, cxxopts::ParseResult &args); [[deprecated("Old function, Command::Interface ")]] - bool addLib(std::shared_ptr, cxxopts::ParseResult &args); + bool addLib(std::shared_ptr, cxxopts::ParseResult &args); [[deprecated("Old function, Command::Interface ")]] - bool update(std::shared_ptr, cxxopts::ParseResult &args); + bool update(std::shared_ptr, cxxopts::ParseResult &args); [[deprecated("Old function, Command::Interface ")]] - bool dev(std::shared_ptr ctx); + bool dev(std::shared_ptr); [[deprecated("Old function, Command::Interface ")]] - bool remove(std::shared_ptrctx, cxxopts::ParseResult &args); + bool remove(std::shared_ptr, cxxopts::ParseResult &args); [[deprecated("Old function, Command::Interface ")]] - bool removeDep(std::shared_ptr ctx, cxxopts::ParseResult &args); + bool removeDep(std::shared_ptr, cxxopts::ParseResult &args); diff --git a/src/Command/CommandAdd.cpp b/src/Command/CommandAdd.cpp index cec6132..806dc93 100644 --- a/src/Command/CommandAdd.cpp +++ b/src/Command/CommandAdd.cpp @@ -22,52 +22,66 @@ namespace Command { "\tlib: adds a library" << std::endl; return false; } + std::string subcommand = args->operator[]("subcommand").as(); if (subcommand == "dep") { this->addDependency(); } + if (subcommand == "flag") { this->addFlag(); } + return true; } + bool Interface::addFlag() { if (args->count("subcommand") == 0) { for (auto flag : args->operator[]("subcommand").as>()) { - ctx->flags.push_back(flag); + pro->flags.push_back(flag); } + } + return true; } + bool Interface::addAuthors(){ - if (ctx->args->count("args") == 0) { - for (auto author : ctx->args->operator[]("args").as>()) { - ctx->authors.push_back(author); + if (pro->args->count("args") == 0) { + for (auto author : pro->args->operator[]("args").as>()) { + pro->authors.push_back(author); } + } + return true; } - bool checkForOverlappingDependencies(std::shared_ptr ctx, std::string name){ - if(ctx->dependencies.size() == 0){ + + bool checkForOverlappingDependencies(std::shared_ptr pro, std::string name){ + if(pro->dependencies.size() == 0){ return false; } - for(dependency dep: ctx->dependencies){ + for(Dependency dep: pro->dependencies){ if(dep.name == name){ return true; } + } + return false; } + bool searchVersions(){ std::cout << "Getting versions" << std::endl; return true; } + bool Interface::addDependency() { if (args->count("args") == 0) { std::cout << @@ -76,23 +90,27 @@ namespace Command { "\tcmake add dep [args] " << std::endl; return false; } + std::string query = args->operator[]("args").as>()[0]; - std::vector searchResults = searchPackage(query); + std::vector searchResults = searchPackage(query); if(searchResults.size() == 0){ std::cout << "No results found" << std::endl; return false; } + std::cout << "Select a package to install: "; std::string input; std::cin >> input; int index; try{ index = std::stoi(input); - }catch(...){ + } +catch(...){ std::cout << "Invalid input" << std::endl; return false; } + std::cout << "Installing " << searchResults[index].name << std::endl; //json versionJson = Utils::fetchJson("https://raw.githubusercontent.com/cmaker-dev/index/main/index/" + searchResults[index].name + "/info.json"); @@ -105,13 +123,21 @@ namespace Command { if (searchResults[index].versions[i] == "master" || searchResults[index].versions[i] == "main" || searchResults[index].versions[i] == "stable"){ version = searchResults[index].versions[i]; } + } - json packageInfo = json{{"name", searchResults[index].name}, {"url", searchResults[index].url}, {"versions", searchResults[index].versions}, {"target_link", searchResults[index].target_link}}; + + json packageInfo = json{{"name", searchResults[index].name} +, {"url", searchResults[index].url} +, {"versions", searchResults[index].versions} +, {"target_link", searchResults[index].target_link} +} +; List* list = (new List())->Numbered()->ReverseIndexed(); for(size_t i = 0; i < searchResults[index].versions.size(); i++){ - list->pushBack(ListItem(searchResults[index].versions[i],"")); + list->pushBack(ListItem(searchResults[index].versions[i])); } + std::cout << list->Build() << std::endl; std::cout << "Select a version to install [" << termcolor::green << version << termcolor::white << "] : "; std::string versionInput; @@ -120,19 +146,22 @@ namespace Command { int versionIndex; try{ versionIndex = std::stoi(versionInput); - }catch(...){ + } +catch(...){ std::cout << "Invalid input" << std::endl; return false; } + version = searchResults[index].versions[versionIndex]; - if(checkForOverlappingDependencies(ctx, searchResults[index].name)){ + if(checkForOverlappingDependencies(pro, searchResults[index].name)){ std::cout << "Package already installed" << std::endl; return false; } + std::cout << "Adding dependency to config.json" << std::endl; - ctx->dependencies.push_back({ + pro->dependencies.push_back({ .name = searchResults[index].name, .url = searchResults[index].url, @@ -142,15 +171,20 @@ namespace Command { ? searchResults[index].name : searchResults[index].target_link - }); + } +); std::cout << "Writing config.json" << std::endl; - if(!Generators::ConfigJson::writeConfig(ctx)){ + if(!Generators::ConfigJson::writeConfig(pro)){ std::cout << "Failed to write config.json" << std::endl; } - if(!Generators::CMakeList::create(ctx)){ + + if(!Generators::CMakeList::createCMakeListsExecutable(pro)){ std::cout << "Failed to write CMakeLists.txt" << std::endl; } + return true; } -} // namespace Command + +} + // namespace Command diff --git a/src/Command/CommandClean.cpp b/src/Command/CommandClean.cpp index 1539177..1e3ce70 100644 --- a/src/Command/CommandClean.cpp +++ b/src/Command/CommandClean.cpp @@ -5,8 +5,8 @@ namespace Command{ bool Interface::clean(){ std::cout << "Cleaning: " << std::endl; - std::cout << ctx->project_path / "build/*" << std::endl; - for(auto& p: std::filesystem::directory_iterator(ctx->project_path / "build")){ + std::cout << pro->project_path / "build/*" << std::endl; + for(auto& p: std::filesystem::directory_iterator(pro->project_path / "build")){ std::cout << p.path() << std::endl; } return true; diff --git a/src/Command/CommandCompileModes.cpp b/src/Command/CommandCompileModes.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/Command/CommandFTP.cpp b/src/Command/CommandFTP.cpp index d6a300e..b2bf075 100644 --- a/src/Command/CommandFTP.cpp +++ b/src/Command/CommandFTP.cpp @@ -1,13 +1,28 @@ #include #include "Command.hpp" +#include "../Utils/CLI.hpp" namespace Command{ + using Utils::CLI::Prompt; + using namespace Utils::CLI::Ansi; bool Interface::ftp() { bool test = this->LoadPackageJson(); if(!test) { std::cout << "Error: Could not load config.json" << std::endl; return false; } + + Prompt *prompt = new Prompt("Are you sure you would like to delete the entire project?"); + prompt->Color(RED)->Run(); + if(!prompt->Get()) { + std::cout << "Aborting..." << std::endl; + return false; + } + + + + + #ifdef DEBUG system("rm -rf ./build/*"); #else diff --git a/src/Command/CommandGeneral.cpp b/src/Command/CommandGeneral.cpp index 79d5276..f45bfee 100644 --- a/src/Command/CommandGeneral.cpp +++ b/src/Command/CommandGeneral.cpp @@ -9,9 +9,9 @@ namespace Command { try { std::string file_name = "config.json"; std::fstream file; - file.open((ctx->project_path / file_name).string()); + file.open((pro->project_path / file_name).string()); json data = json::parse(file); - ctx->fromJson(data); + pro->fromJson(data); //Simplfied this fucking code } catch (json::exception &e) { std::cout << "Error: Could not load config.json" << std::endl; diff --git a/src/Command/CommandHelp.cpp b/src/Command/CommandHelp.cpp index 0e5558f..07452f6 100644 --- a/src/Command/CommandHelp.cpp +++ b/src/Command/CommandHelp.cpp @@ -27,10 +27,13 @@ namespace Command { "\t run: builds and runs your project" << ENDL "\t watch: watches builds and runs your project on changes" << ENDL "\t add [dep, lib, flags]: add library, dependency or flags to your project" << ENDL + "\t search : search for a package" << ENDL "\t server: manages remote build servers for your projects" << ENDL "\t ftp: deletes the entire project (F*ck this project)" << ENDL + // RYANS LAST CODE BLOCK IN ENTIRE REPO MUST BE SAVED DO NOT TOUCH "\t flags: adds a flag to your project" << ENDL "\t help: print help" << ENDL; + // SACRED CODE BLOCK ENDS HERE std::cout << termcolor::red << "This is a pre-alpha version of cmaker, please report any bugs here: " << termcolor::reset << " https://github.com/cmaker-dev/cmaker/issues" << ENDL; return 1; diff --git a/src/Command/CommandInit.cpp b/src/Command/CommandInit.cpp index f0b30d2..7b573f9 100644 --- a/src/Command/CommandInit.cpp +++ b/src/Command/CommandInit.cpp @@ -12,24 +12,24 @@ namespace Command { -bool createJson(std::shared_ptr ctx) { +bool createJson(std::shared_ptr pro) { //Lucas did it again std::shared_ptrconfig_json = std::make_shared(); - Generators::ConfigJson::readUserInput(ctx, config_json); - Generators::ConfigJson::writeConfig(ctx); + Generators::ConfigJson::readUserInput(pro, config_json); + Generators::ConfigJson::writeConfig(pro); return false; } -bool createHelloWorldCpp(std::shared_ptr ctx) { +bool createHelloWorldCpp(std::shared_ptr pro) { #ifdef DEBUG system("cd build;mkdir src"); #else system("mkdir src"); #endif std::ofstream file; - std::string file_name = ctx->project_path / "src/main.cpp"; + std::string file_name = pro->project_path / "src/main.cpp"; std::cout << file_name << std::endl; - file.open(ctx->project_path / file_name); + file.open(pro->project_path / file_name); file << "#include \n" "int main(){\n" "\tstd::cout << \"Hello World\" << std::endl;\n" @@ -37,7 +37,7 @@ bool createHelloWorldCpp(std::shared_ptr ctx) { "}\n"; return false; } -bool createHelloWorldC(std::shared_ptr ctx) { +bool createHelloWorldC(std::shared_ptr pro) { #ifdef DEBUG system("cd build;mkdir src"); #else @@ -45,7 +45,7 @@ bool createHelloWorldC(std::shared_ptr ctx) { #endif std::ofstream file; std::string file_name = "src/main.c"; - file.open(ctx->project_path / file_name); + file.open(pro->project_path / file_name); file << "#include \n" "int main(){\n" "\tprintf(\"Hello World\");\n" @@ -55,22 +55,30 @@ bool createHelloWorldC(std::shared_ptr ctx) { } bool createProject(Interface *inter){ - createJson(inter->ctx); + createJson(inter->pro); inter->LoadPackageJson(); - Generators::CMakeList::create(inter->ctx); - if(inter->ctx->lang == "cpp"){ - createHelloWorldCpp(inter->ctx); - }else if(inter->ctx->lang == "c"){ - createHelloWorldC(inter->ctx); + //Generators::CMakeList::create(inter->pro); + Generators::CMakeList::createCMakeListsExecutable(inter->pro); + if(inter->pro->lang == "cpp"){ + createHelloWorldCpp(inter->pro); + }else if(inter->pro->lang == "c"){ + createHelloWorldC(inter->pro); } +#ifndef DEBUG + Generators::GitIgnore::create(inter->pro); + int gitinit = std::system(("cd "+inter->pro->project_path.string()+";git init").c_str()); + if(gitinit != 0){ + std::cout << termcolor::red << "We had problems initializing your project with git" << termcolor::reset << ENDL; + } +#endif return false; } -bool defaultJsonCpp(std::shared_ptr ctx) { +bool defaultJson(std::shared_ptr pro) { using nlohmann::json; - json j = ctx->toJson(); + json j = pro->toJson(); std::cout << "📄 New json File: \n"; std::cout << j << '\n'; @@ -78,7 +86,7 @@ bool defaultJsonCpp(std::shared_ptr ctx) { std::string file_name = "config.json"; - file.open(ctx->project_path / file_name); + file.open(pro->project_path / file_name); file << j; file << '\n'; file.close(); @@ -98,36 +106,48 @@ bool Interface::init() { if(args->operator[]("name").count() > 0){ new_project_name = args->operator[]("name").as(); } + pro->project_name = new_project_name; - ctx->project_name = new_project_name; - + if(args->operator[]("type").count() > 0){ + pro->project_type = args->operator[]("type").as(); + std::cout << "type: " << pro->project_type << ENDL; + } + //TODO: Stop using this shit file_exists(file_name); - if(ctx->project_path.empty()){ - ctx->project_path = std::filesystem::current_path(); + if(pro->project_path.empty()){ + pro->project_path = std::filesystem::current_path(); } - std::cout << "project path" << ctx->project_path << ENDL; - std::cout << "config.json path" << ctx->project_path / file_name << ENDL; - std::string current_path = ctx->project_path.string(); + std::cout << "project path" << pro->project_path << ENDL; + std::cout << "config.json path" << pro->project_path / file_name << ENDL; + std::string current_path = pro->project_path.string(); if (args->operator[]("skip-init").as()) { std::string language = args->operator[]("language").as(); if (language == "cpp" || language == "c++"){ - defaultJsonCpp(ctx); + defaultJson(pro); } else if (language == "c") { - //defaultJsonC(ctx, args); + defaultJson(pro); + //TODO: c support is there no need to exitt failing std::cout << "C is not supported yet" << ENDL; exit(-1); } this->LoadPackageJson(); - Generators::CMakeList::create(ctx); + Generators::CMakeList::createCMakeListsExecutable(pro); +#ifndef DEBUG + Generators::GitIgnore::create(pro); +#endif + + int gitinit = std::system(("cd "+pro->project_path.string()+";git init").c_str()); + if(gitinit != 0){ + std::cout << termcolor::red << "We had problems initializing your project with git" << termcolor::reset << ENDL; + } } else { createProject(this); } - return true; } diff --git a/src/Command/CommandRemove.cpp b/src/Command/CommandRemove.cpp index 5f16d0c..4980f5b 100644 --- a/src/Command/CommandRemove.cpp +++ b/src/Command/CommandRemove.cpp @@ -35,7 +35,7 @@ Usage remove dep: std::cout << "args:" << name << std::endl; } std::cout << "removing dependencies" << std::endl; - std::erase_if(ctx->dependencies, [&name_to_remove](auto &dep) { + std::erase_if(pro->dependencies, [&name_to_remove](auto &dep) { for (std::string name : name_to_remove) { if (dep.name == name) { return true; @@ -44,8 +44,8 @@ Usage remove dep: return false; }); - Generators::ConfigJson::writeConfig(ctx); - Generators::CMakeList::create(ctx); + Generators::ConfigJson::writeConfig(pro); + Generators::CMakeList::createCMakeListsExecutable(pro); return true; } } // namespace Command diff --git a/src/Command/CommandRun.cpp b/src/Command/CommandRun.cpp index 83ce242..7c4c10a 100644 --- a/src/Command/CommandRun.cpp +++ b/src/Command/CommandRun.cpp @@ -36,7 +36,7 @@ namespace Command { #ifdef DEBUG file_name = "./build/build/"; #endif - std::string command = file_name + ctx->project_name; + std::string command = file_name + pro->project_name; success = system(command.c_str()); if (success != 0) { std::cout << "Error running project!" << std::endl; diff --git a/src/Command/CommandSearch.cpp b/src/Command/CommandSearch.cpp new file mode 100644 index 0000000..ee8500a --- /dev/null +++ b/src/Command/CommandSearch.cpp @@ -0,0 +1,17 @@ +#include "Command.hpp" + + +namespace Command { + bool Interface::search(){ + std::string query; + if(args->operator[]("query").count() > 0){ + query = args->operator[]("query").as(); + }else{ + std::cout << "No query provided" << ENDL; + std::cout << "Usage: cmaker search " << ENDL; + return false; + } + searchPackage(query); + return true; + } +} diff --git a/src/Command/CommandServer.cpp b/src/Command/CommandServer.cpp index 349774d..bd5aa4c 100644 --- a/src/Command/CommandServer.cpp +++ b/src/Command/CommandServer.cpp @@ -1,39 +1,63 @@ #include "Command.hpp" #include "../Utils/General.hpp" +#include "../Utils/CLI.hpp" + namespace Command{ + using namespace Utils::CLI; bool getServerName(std::string& name){ - std::cout << "Enter the name of the server: "; - std::getline(std::cin, name); + Prompt *name_promp = new Prompt("Enter the name of the server: "); + name_promp->Run(); + name = name_promp->Get(); return true; } bool getServerAddress(std::string& address){ - std::cout << "Enter the address of the server: "; - std::getline(std::cin, address); + Prompt *address_promp = new Prompt("Enter the address of the server: "); + address_promp->Run(); + address = address_promp->Get(); return true; } - bool getServerPort(std::string& port){ - std::cout << "Enter the port of the server: "; - std::getline(std::cin, port); + + bool getServerPort(int& port){ + Prompt *port_promp = new Prompt("Enter the port of the server: "); + port_promp->Run(); + port = port_promp->Get(); return true; } + bool getServerUsername(std::string& username){ - std::cout << "Enter the username of the server: "; - std::getline(std::cin, username); + Prompt *username_promp = new Prompt("Enter the username of the server: "); + username_promp->Validator([](std::string username){ + if (username == ""){ + return false; + } + else if (username.length() > 256){ + return false; + } + return true; + }); + username_promp->Run(); + username = username_promp->Get(); return true; } + bool getServerAuthMethod(std::string& authMethod){ - std::cout << "Enter the authentication method of the server[pem/password]: "; - std::getline(std::cin, authMethod); + Prompt *authMethod_promp = new Prompt("Enter the authentication method of the server[pem/password]: "); + authMethod_promp->Run(); + authMethod = authMethod_promp->Get(); return true; } + bool getServerPassword(std::string& password){ - std::cout << "Enter the password of the server: "; - std::getline(std::cin, password); + Prompt *password_promp = new Prompt("Enter the password of the server: "); + password_promp->Run(); + password = password_promp->Get(); return true; } + bool getServerKey(std::string& key){ - std::cout << "Enter path the ssh key for the server: "; - std::getline(std::cin, key); + Prompt *key_promp = new Prompt("Enter path the ssh key for the server: "); + key_promp->Run(); + key = key_promp->Get(); return true; } @@ -49,10 +73,51 @@ Usage server: )EOF" << std::endl; return true; } + bool serverList(std::vector servers){ + //TODO put this in the constructor + Utils::TableFormat table; + table.width = 20; + table << "Name" << "Address" << "Port" << "Username" << "AuthMethod" << ENDL; + for (auto& server: servers){ + table << server.name << server.ip << server.port << server.username << server.authMethod << ENDL; + } + + return true; + } + + bool Interface::setBuildServer( std::vector servers){ + std::string name; + getServerName(name); + for (auto& server: servers){ + std::cout << "server.name:" << server.name << std::endl; + std::cout << "name:" << name << std::endl; + if (server.name == name){ + + std::cout << "Found server" << std::endl; + std::ofstream file; + file.open(std::string(std::getenv("HOME")) + "/.config/cmaker/" + "current_build_server.json"); + json current_build_server = { + {"name", server.name}, + {"address", server.ip}, + {"port", server.port}, + {"username", server.username}, + {"authMethod", server.authMethod}, + {"password", server.password.value_or("")}, + {"key", server.key.value_or("")} + }; + file << current_build_server; + return true; + } + } + std::cout << "Could not find server" << std::endl; + return false; + } + bool serverAdd(std::vector servers){ std::string build_servers= std::string(std::getenv("HOME")) + "/.config/cmaker/" + "build_server.json"; - std::string name, address, port, username, authMethod, password, key; + std::string name, address, username, authMethod, password, key; + int port; getServerName(name); getServerAddress(address); getServerPort(port); @@ -71,11 +136,11 @@ Usage server: - BuildServer build_server = BuildServer(name, address, username, authMethod, password, key, std::stoi(port)); + BuildServer build_server = BuildServer(name, address, username, authMethod, password, key, port); servers.push_back(build_server); std::vector build_server_json; - for (auto& build_server: servers){ + for (BuildServer& build_server: servers){ json build_server_json_tmp = { {"name", build_server.name}, {"address", build_server.ip}, @@ -92,6 +157,15 @@ Usage server: file << build_server_json; return true; } + bool Interface::getBuildServer(){ + Utils::TableFormat table; + table.width = 20; + table << "Name" << "Address" << "Port" << "Username" << "AuthMethod" << ENDL; + table << pro->build_server.name << pro->build_server.ip << pro->build_server.port << pro->build_server.username << pro->build_server.authMethod << ENDL; + return true; + + } + bool Interface::server(){ std::fstream file; std::string build_servers_dir= std::string(std::getenv("HOME")) + "/.config/cmaker/"; @@ -99,10 +173,15 @@ Usage server: std::filesystem::create_directory(build_servers_dir); } std::string build_servers= std::string(std::getenv("HOME")) + "/.config/cmaker/" + "build_server.json"; + std::string current_build_server= std::string(std::getenv("HOME")) + "/.config/cmaker/" + "current_build_server.json"; if (!std::filesystem::exists(build_servers)){ std::ofstream file(build_servers); file << "[]"; } + if (!std::filesystem::exists(current_build_server)){ + std::ofstream file(current_build_server); + file << "{}"; + } file.open(build_servers); std::vector servers; @@ -114,10 +193,29 @@ Usage server: std::cout << "Error: Could not load build_server.json" << std::endl; return false; } + try{ + json current_build_server_json = json::parse(std::ifstream(current_build_server)); + if (!current_build_server_json["name"].is_null()) { + pro->build_server = BuildServer( + current_build_server_json["name"].get(), + current_build_server_json["address"].get(), + current_build_server_json["username"].get(), + current_build_server_json["authMethod"].get(), + current_build_server_json["password"].get(), + current_build_server_json["key"].get(), + current_build_server_json["port"].get() + ); + } + + } + catch(json::exception &e){ + std::cout << "Error: Could not load current_build_server.json" << std::endl; + return false; + } - for (auto& server : server_list){ + for (json& server : server_list){ BuildServer build_server = BuildServer( server["name"].get(), server["address"].get(), @@ -142,10 +240,13 @@ Usage server: else if (this->args->operator[]("subcommand").as() == "remove") { } else if (this->args->operator[]("subcommand").as() == "list") { + serverList(servers); } else if (this->args->operator[]("subcommand").as() == "set") { + setBuildServer(servers); } else if (this->args->operator[]("subcommand").as() == "get") { + getBuildServer(); } else{ serverHelp(); diff --git a/src/Command/CommandWatcher.cpp b/src/Command/CommandWatcher.cpp index 72d53be..403338b 100644 --- a/src/Command/CommandWatcher.cpp +++ b/src/Command/CommandWatcher.cpp @@ -1,6 +1,7 @@ #include "Command.hpp" #include #include +#ifdef __linux__ #include #include @@ -17,7 +18,7 @@ namespace Command { std::cout << "Error creating inotify instance" << std::endl; exit(1); } - int err = inotify_add_watch(temp_fd, filetoken.path().c_str(), IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); + int err = inotify_add_watch(temp_fd, filetoken.path().c_str(), IN_MODIFY | IN_CREATE | IN_DELETE ); if(err < 0){ std::cout << "Error adding watch" << std::endl; exit(1); @@ -48,7 +49,7 @@ namespace Command { ev.data.fd = inotify_fd; // TODO: Add recursive directory watching - size_t watch_desc = inotify_add_watch(inotify_fd, path.c_str(), IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); + size_t watch_desc = inotify_add_watch(inotify_fd, path.c_str(), IN_MODIFY | IN_CREATE | IN_DELETE ); if (watch_desc < 0) { std::cout << "Error adding watch" << std::endl; return; @@ -87,9 +88,9 @@ namespace Command { } std::cout << buffer << std::endl; - changeCallback(); } + changeCallback(); } @@ -112,20 +113,89 @@ namespace Command { #endif watcher([this]() { -#ifdef DEBUG - std::string command = "cmake ./build/ && ./build/make && ./build/" + ctx->build_dir + "/" + ctx->project_name; + #ifdef DEBUG + std::string command = "cmake ./build/ && ./build/make && ./build/" + pro->build_dir + "/" + pro->project_name; if (args->count("args") != 0) { std::vector args_vec = args->operator[]("args").as>(); std::string command_args = std::accumulate( args_vec.begin(), args_vec.end(), args_vec[0], [](std::string a, std::string b) { return a + " " + b; } ); + command = "rsync -avh --exclude-from='.gitignore' --update -e 'ssh -p " + std::to_string(pro->build_server.port) + "' --progress . " + + pro->build_server.username + "@" + pro->build_server.ip + + ":/tmp/cmaker && ssh -p " + std::to_string(pro->build_server.port) + " " + pro->build_server.username + "@" + pro->build_server.ip + + " 'cd /tmp/cmaker && cmake . && make && ./build/new'"; + //command = "cmake ./build/ && make && ./build/" + pro->build_dir + "/" + pro->project_name + " " + command_args; + + } + bool build_server=args->operator[]("remote-build").as(); + if (build_server == true) { + std::string current_build_server= std::string(std::getenv("HOME")) + "/.config/cmaker/" + "current_build_server.json"; + json current_build_server_json = json::parse(std::ifstream(current_build_server)); + if (!current_build_server_json["name"].is_null()) { + pro->build_server = BuildServer( + current_build_server_json["name"].get(), + current_build_server_json["address"].get(), + current_build_server_json["username"].get(), + current_build_server_json["authMethod"].get(), + current_build_server_json["password"].get(), + current_build_server_json["key"].get(), + current_build_server_json["port"].get() + ); + } + command = "rsync -avh --exclude-from='.gitignore' --update -e 'ssh -p " + std::to_string(pro->build_server.port) + "' --progress . " + + pro->build_server.username + "@" + pro->build_server.ip + + ":/tmp/cmaker && ssh -p " + std::to_string(pro->build_server.port) + " " + pro->build_server.username + "@" + pro->build_server.ip + + " 'cd /tmp/cmaker && cmake . && make && ./build/" + pro->project_name + "'"; - command = "cmake ./build/ && make && ./build/" + ctx->build_dir + "/" + ctx->project_name + " " + command_args; } -#else - std::string command = "cmake . && make && ./" + ctx->build_dir + "/" + ctx->project_name; + #else + std::string command = "cmake . && make && ./" + pro->build_dir + "/" + pro->project_name; + + bool build_server=args->operator[]("remote-build").as(); + if (build_server == true) { + std::string current_build_server= std::string(std::getenv("HOME")) + "/.config/cmaker/" + "current_build_server.json"; + json current_build_server_json = json::parse(std::ifstream(current_build_server)); + if (!current_build_server_json["name"].is_null()) { + pro->build_server = BuildServer( + current_build_server_json["name"].get(), + current_build_server_json["address"].get(), + current_build_server_json["username"].get(), + current_build_server_json["authMethod"].get(), + current_build_server_json["password"].get(), + current_build_server_json["key"].get(), + current_build_server_json["port"].get() + ); + } + command = "rsync -avh --exclude-from='.gitignore' --update -e 'ssh -p " + std::to_string(pro->build_server.port) + "' --progress . " + + pro->build_server.username + "@" + pro->build_server.ip + + ":/tmp/cmaker && ssh -p " + std::to_string(pro->build_server.port) + " " + pro->build_server.username + "@" + pro->build_server.ip + + " 'cd /tmp/cmaker && cmake . && make && ./build/" + pro->project_name + "'"; + + } +// else{ +// std::string build_servers = std::string(std::getenv("HOME")) + "/.config/cmaker/" + "build_servers.json"; +// json build_servers_json = json::parse(std::ifstream(build_servers)); +// std::string build_server = args->operator[]("server").as(); +// for (auto &bserver: build_servers_json){ +// if (bserver["name"].get() == build_server){ +// pro->build_server = BuildServer( +// bserver["name"].get(), +// bserver["authMethod"].get(), +// bserver["password"].get(), +// bserver["key"].get(), +// bserver["port"].get() +// +// ); +// command = "rsync -avh --exclude-from='.gitignore' --update -e 'ssh -p " + std::to_string(pro->build_server.port) + "' --progress . " +// + pro->build_server.username + "@" + pro->build_server.ip +// + ":/tmp/cmaker && ssh -p " + std::to_string(pro->build_server.port) + " " + pro->build_server.username + "@" + pro->build_server.ip +// + " 'cd /tmp/cmaker && cmake . && make && ./build/" + pro->project_name + "'"; +// break; +// } +// } +// } if (args->count("args") != 0) { std::cout << "estamos aqui" << std::endl; std::vector args_vec = args->operator[]("args").as>(); @@ -140,9 +210,10 @@ namespace Command { } std::cout << "command_args: " << command_args << std::endl; - command = "cmake . && make && ./" + ctx->build_dir + "/" + ctx->project_name + " " + command_args; + command = "cmake . && make && ./" + pro->build_dir + "/" + pro->project_name + " " + command_args; + } -#endif + #endif std::cout << "Running command: " << command << std::endl; int success = system(command.c_str()); if (success != 0) { @@ -154,4 +225,4 @@ namespace Command { return true; } } // namespace Command - +#endif diff --git a/src/Command/Context.cpp b/src/Command/Context.cpp index b42ec13..9747d57 100644 --- a/src/Command/Context.cpp +++ b/src/Command/Context.cpp @@ -5,7 +5,7 @@ namespace Command { /* * Welp reflection is a bitch aint it */ - void Context::fromJson(json j){ + void Project::fromJson(json j){ #ifdef DEBUG project_path = std::filesystem::current_path() / "build"; #else @@ -14,6 +14,7 @@ namespace Command { project_name = j["project_name"]; cmake_version = j["cmake_version"]; project_version = j["project_version"]; + project_type = j["project_type"]; lang = j["lang"]; lang_version = j["lang_version"]; compiler = j["compiler"]; @@ -24,7 +25,7 @@ namespace Command { project_type = j["project_type"]; project_description = j["project_description"]; for (auto &dep : j["dependencies"]) { - dependency d; + Dependency d; d.name = dep["name"]; d.url = dep["url"]; d.version = dep["version"]; @@ -33,7 +34,7 @@ namespace Command { } flags = j["flags"]; } - nlohmann::json Context::toJson(){ + nlohmann::json Project::toJson(){ using nlohmann::json; std::vector deps; for (auto &dep : dependencies) { @@ -46,6 +47,7 @@ namespace Command { } json j; j["project_name"] = project_name; + j["project_type"] = project_type; j["cmake_version"] = cmake_version; j["project_version"] = project_version; j["lang"] = lang; diff --git a/src/Command/Interface.cpp b/src/Command/Interface.cpp index c61b3b5..1d8eff5 100644 --- a/src/Command/Interface.cpp +++ b/src/Command/Interface.cpp @@ -27,23 +27,15 @@ namespace Command { this->argc = argc; this->argv = argv; - this->ctx = std::make_shared(); + this->pro = std::make_shared(); OptionsInit::Main(this); this->parse(); //After the parse we can set the context args - this->ctx->args = this->args; + this->pro->args = this->args; // if(!this->args->count("command")){ // this->help(); - // } - -#ifdef DEBUG - ctx->project_path = std::filesystem::current_path() / "build"; -#else - ctx->project_path = std::filesystem::current_path(); -#endif - - std::string command = this->args->operator[]("command").as(); + std::string command = this->args->operator[]("command").as(); #ifdef DEBUG std::cout << "DEBUG MODE ENABLED\n"; @@ -52,6 +44,13 @@ namespace Command { this->LoadPackageJson(); } +#ifdef DEBUG + pro->project_path = std::filesystem::current_path() / "build"; +#else + pro->project_path = std::filesystem::current_path(); +#endif + + using namespace cxxopts; if (command == "init"){ @@ -82,6 +81,11 @@ namespace Command { std::cout << "Error: Could not add project" << ENDL; } + }else if(command == "search"){ + OptionsInit::Search(this); + if(!this->search()){ + std::cout << "Error: Could not search project" << ENDL; + } } else if (command == "server"){ OptionsInit::Server(this); diff --git a/src/Command/Options.cpp b/src/Command/Options.cpp index 7e018ca..0d91894 100644 --- a/src/Command/Options.cpp +++ b/src/Command/Options.cpp @@ -20,6 +20,17 @@ namespace Command { return inter->parse(); } + bool OptionsInit::Search(Interface* inter){ + inter->InitHeader(); + inter->options->parse_positional({"command","query"}); + + inter->options->add_options() + ("command", "Command to run", cxxopts::value()->default_value("help")) + ("query", "Arguments to pass to the command", cxxopts::value()) + ("h,help", "Print usage"); + return inter->parse(); + } + bool OptionsInit::Init(Interface* inter) { inter->InitHeader(); @@ -30,7 +41,8 @@ namespace Command { ("y,skip-init", "skip init", cxxopts::value()->default_value("false")) ("v,verbose", "Verbose output", cxxopts::value()->default_value("false")) ("name", "Name of the project", cxxopts::value()->default_value("false")) - ("language", "Language of the project", cxxopts::value()->default_value("cpp")); + ("t,type", "Type of the project", cxxopts::value()->default_value(ProjectType::EXECUTABLE)) + ("l,language", "Language of the project", cxxopts::value()->default_value("cpp")); return inter->parse(); } bool OptionsInit::Add(Interface* inter) { @@ -74,6 +86,7 @@ namespace Command { ("o,ours", "watches cmaker source", cxxopts::value()) #endif ("command", "Command to run", cxxopts::value()->default_value("help")) + ("b,remote-build", "Build server to use", cxxopts::value()->default_value("false")) ("c,args", "command to pass to dev", cxxopts::value>()) ("y,skip-init", "skip init", cxxopts::value()->default_value("true")) ("v,verbose", "Verbose output", cxxopts::value()->default_value("false")); @@ -88,7 +101,6 @@ namespace Command { #endif ("command", "Command to run", cxxopts::value()->default_value("help")) ("subcommand", "Subcommand to run", cxxopts::value())("h,help", "Print usage") - ("c,args", "command to pass to dev", cxxopts::value>()) ("y,skip-init", "skip init", cxxopts::value()->default_value("true")) ("v,verbose", "Verbose output", cxxopts::value()->default_value("false")); return interface->parse(); diff --git a/src/Command/SearchPackage.cpp b/src/Command/SearchPackage.cpp index beffa28..40ef6e2 100644 --- a/src/Command/SearchPackage.cpp +++ b/src/Command/SearchPackage.cpp @@ -1,6 +1,4 @@ #include "Command.hpp" -#include -#include #include #include "../Utils/General.hpp" #include "../Utils/CLI.hpp" @@ -72,19 +70,19 @@ namespace Command { } return score; } - void getPackageScore(packageResult &package, std::string &query){ + void getPackageScore(Package &package, std::string &query){ int score = 0; score += getStringScore(package.name, query) * 10; score += getStringScore(package.description, query); score += getStringScore(package.target_link, query); package.score = score; } - std::vector calculatePackageScores(std::string query){ - std::vector results; + std::vector calculatePackageScores(std::string query){ + std::vector results; json rawIndex = fetchIndex(); Utils::toLower(query); for(json json: rawIndex){ - packageResult package{ + Package package{ .name = json["name"], .url = json["git"], .versions = json["versions"], @@ -104,22 +102,24 @@ namespace Command { results.push_back(package); } - std::sort(results.begin(), results.end(), [](packageResult a, packageResult b){ + std::sort(results.begin(), results.end(), [](Package a, Package b){ return a.score > b.score; }); return results; } - std::vector searchPackage(std::string query){ + std::vector searchPackage(std::string query){ std::cout << "Searching for package" << std::endl; - std::vector results = calculatePackageScores(query); + std::vector results = calculatePackageScores(query); std::cout << "Results: " << results.size() << std::endl; - Utils::CLI::List *list = (new Utils::CLI::List())->Numbered()->ReverseIndexed(); - for(packageResult result: results){ + List *list = (new Utils::CLI::List())-> + Numbered()-> + ReverseIndexed(); + for(Package result: results){ if(result.score > 10){ list->pushBack(ListItem(result.name + " (" + result.url + ")", result.description)); } diff --git a/src/Generators/CMakeGenerator.cpp b/src/Generators/CMakeGenerator.cpp index 7b1102e..634e5d0 100644 --- a/src/Generators/CMakeGenerator.cpp +++ b/src/Generators/CMakeGenerator.cpp @@ -1,5 +1,6 @@ #include "Generators.hpp" #include +#include #include #include #include "../Command/Command.hpp" @@ -12,8 +13,8 @@ namespace Generators::CMakeList{ * @param ctx: the context of the command * @return a vector of dependencies that will be later combined to build the cmake file */ - void generateDeps(std::shared_ptr ctx, std::shared_ptr cmake_context){ - for (auto dep : ctx->dependencies) { + void generateDeps(std::shared_ptr pro, std::shared_ptr cmake_context){ + for (auto dep : pro->dependencies) { std::string FetchContent_Declare = std::format( R"V0G0N( @@ -29,7 +30,7 @@ FetchContent_Declare( std::format("FetchContent_MakeAvailable({})", dep.name); std::string target_link_libraries = std::format( - "target_link_libraries({} {})", ctx->project_name, dep.target_link); + "target_link_libraries({} {})", pro->project_name, dep.target_link); Dep dependency = Dep{FetchContent_Declare, FetchContent_MakeAvailable, target_link_libraries}; @@ -39,13 +40,13 @@ FetchContent_Declare( cmake_context->dependencies.push_back(dependency); } } - bool create(std::shared_ptr ctx) { + bool create(std::shared_ptr pro) { std::shared_ptr cmake_context = std::make_shared(); cmake_context->cmake_minimum_required = - std::format("cmake_minimum_required(VERSION {})", ctx->cmake_version); + std::format("cmake_minimum_required(VERSION {})", pro->cmake_version); cmake_context->project_name = std::format("project ({} VERSION {} LANGUAGES CXX)", - ctx->project_name, ctx->project_version); + pro->project_name, pro->project_version); cmake_context->build_type = R"( if (CMAKE_BUILD_TYPE STREQUAL "Release") message("Release mode") @@ -63,11 +64,11 @@ else() endif() )"; cmake_context->cxx_version = - std::format("set(CMAKE_CXX_STANDARD {})", ctx->lang_version); + std::format("set(CMAKE_CXX_STANDARD {})", pro->lang_version); cmake_context->compiler = - std::format("set(CMAKE_CXX_COMPILER {})\n", ctx->compiler); - cmake_context->source_dir = std::format("set(SOURCE_DIR {})", ctx->src_dir); - cmake_context->build_dir = std::format("set(BUILD_DIR {})", ctx->build_dir); + std::format("set(CMAKE_CXX_COMPILER {})\n", pro->compiler); + cmake_context->source_dir = std::format("set(SOURCE_DIR {})", pro->src_dir); + cmake_context->build_dir = std::format("set(BUILD_DIR {})", pro->build_dir); cmake_context->fetch_content = "include(FetchContent)"; cmake_context->files = R"V0G0N( @@ -81,12 +82,12 @@ file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_SOURCE_DIR} cmake_context->include_fetch = "include(FetchContent)"; - generateDeps(ctx, cmake_context); + generateDeps(pro, cmake_context); cmake_context->include_dir = - std::format("include_directories({})", ctx->include_dir); + std::format("include_directories({})", pro->include_dir); cmake_context->add_executable = - std::format("add_executable({} ", ctx->project_name) + "${SOURCES})"; + std::format("add_executable({} ", pro->project_name) + "${SOURCES})"; cmake_context->testing = "ok"; cmake_context->mode = R"V0G0N( if(RELEASE EQUAL 1) @@ -101,20 +102,20 @@ endif() )V0G0N"; cmake_context->set_build_dir = std::format( "set_target_properties({} PROPERTIES RUNTIME_OUTPUT_DIRECTORY {})", - ctx->project_name, ctx->build_dir); + pro->project_name, pro->build_dir); std::ofstream file; std::string file_name = "CMakeLists.txt"; try{ - remove((ctx->project_path / file_name).c_str()); + remove((pro->project_path / file_name).c_str()); }catch(...){ std::cout << "Error while removing file: " << file_name << std::endl; return false; } try{ - file.open(ctx->project_path / file_name); + file.open(pro->project_path / file_name); }catch(...){ std::cout << "Error while opening file: " << file_name << std::endl; return false; diff --git a/src/Generators/CMakeTemplates.cpp b/src/Generators/CMakeTemplates.cpp new file mode 100644 index 0000000..f9e7641 --- /dev/null +++ b/src/Generators/CMakeTemplates.cpp @@ -0,0 +1,101 @@ +#include +#include +#include "../Command/Command.hpp" +#include + +namespace Generators::CMakeList { + bool createCMakeListsExecutable(std::shared_ptr pro){ + std::cout << "Creating CMakeLists.txt" << std::endl; + std::cout << pro->toJson() << std::endl; + std::string CMakeListsExecutable = inja::render(R"EOF( +cmake_minimum_required( VERSION {{ cmake_version }} ) +project( + {{ project_name }} + VERSION {{ cmake_version }} + {%if lang == "cpp"%} + LANGUAGES CXX +) + {%endif%} + {%if lang == "c"%} + LANGUAGES C +) + {%endif%} + +set(CMAKE_CXX_STANDARD {{ lang_version }}) +set(CMAKE_CXX_COMPILER {{ compiler }}) +include(FetchContent) +##for dep in dependencies +FetchContent_Declare( + {{ dep.name }} + GIT_REPOSITORY {{ dep.url }} + GIT_TAG {{ dep.version }} +) +FetchContent_MakeAvailable({{ dep.name }}) +##endfor + +file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_SOURCE_DIR} + "src/**.cpp" + "src/**.c" + "src/**/**.cpp" + "src/**/**.c" +) + +include_directories($CMAKE_SOURCE_DIR/{{ include_dir }}) +set(HEADER_DIR $CMAKE_SOURCE_DIR/{{ include_dir }}) + +if (NOT DEFINED RELEASE) + set(RELEASE 0) +endif() + +add_executable({{project_name}} ${SOURCES}) + +if (CMAKE_BUILD_TYPE STREQUAL "Release") + message("Release mode") + set(RELEASE 1) +elseif(CMAKE_BUILD_TYPE STREQUAL "Debug") + message("Debug mode") + set(RELEASE 0) +elseif(CMAKE_BUILD_TYPE STREQUAL "Test") + message("Test mode") + set(RELEASE 0) + set(TEST_MODE 1) +else() + message("Default mode") + set(RELEASE 0) +endif() + + +set(BUILD_DIR {{ build_dir }}) +set_target_properties({{project_name}} PROPERTIES RUNTIME_OUTPUT_DIRECTORY {{build_dir}}) +if (RELEASE EQUAL 1) + message("Release mode") +elseif (RELEASE EQUAL 0) + message("Debug mode") +endif() + +##for dep in dependencies +target_link_libraries({{project_name}} {{dep.target_link}}) +##endfor +install(TARGETS {{project_name}} DESTINATION bin) +)EOF", pro->toJson()); + std::ofstream file; + std::string file_name = "CMakeLists.txt"; + + try{ + remove((pro->project_path / file_name).c_str()); + }catch(...){ + std::cout << "Error while removing file: " << file_name << std::endl; + return false; + } + + try{ + file.open(pro->project_path / file_name); + }catch(...){ + std::cout << "Error while opening file: " << file_name << std::endl; + return false; + } + std::cout << CMakeListsExecutable << std::endl; + file << CMakeListsExecutable; + return true; + } +} diff --git a/src/Generators/ConfigJsonGenerator.cpp b/src/Generators/ConfigJsonGenerator.cpp index 93212a0..594b810 100644 --- a/src/Generators/ConfigJsonGenerator.cpp +++ b/src/Generators/ConfigJsonGenerator.cpp @@ -6,66 +6,61 @@ #include namespace Generators::ConfigJson{ - - - - - - - bool readUserInput(std::shared_ptr ctx, std::shared_ptr config_json) { - validateLang: - if(!validateLang("📚Language->" + ctx->lang + " : ", ctx, config_json)) { + bool readUserInput(std::shared_ptr pro, std::shared_ptr config_json) { + //TODO: Convert this to the new prompt system + + while(!validateLang("📚Language->" + pro->lang + " : ", pro, config_json)) { std::cout << termcolor::red << "Invalid language - retry" << termcolor::reset << std::endl; - goto validateLang; + } - validateProjectName: - if (!validateProjectName("📖Project name->" + ctx->project_name + " : ", ctx, config_json)) { + + while (!validateProjectName("📖Project name->" + pro->project_name + " : ", pro, config_json)) { std::cout << termcolor::red << "Invalid project name - retry" << termcolor::reset << std::endl; - goto validateProjectName; + } - validateCmakeVersion: - if (!validateCmakeVersion("🔨Cmake version->" + ctx->cmake_version + " : ", ctx, config_json)) { + + while (!validateCmakeVersion("🔨Cmake version->" + pro->cmake_version + " : ", pro, config_json)) { std::cout << termcolor::red << "Invalid cmake version - retry" << termcolor::reset << std::endl; - goto validateCmakeVersion; + } - validateProjectVersion: - if (!validateProjectVersion("🗃️Version->" + ctx->project_version + " : ", ctx, config_json)) { + + while (!validateProjectVersion("🗃️Version->" + pro->project_version + " : ", pro, config_json)) { std::cout << termcolor::red << "Invalid project version - retry" << termcolor::reset << std::endl; - goto validateProjectVersion; + } - validateLanguageVersion: - if (!validateLanguageVersion("📰Language Standard->" + ctx->lang_version + " : ", ctx, config_json)) { + + while (!validateLanguageVersion("📰Language Standard->" + pro->lang_version + " : ", pro, config_json)) { std::cout << termcolor::red << "Invalid language version - retry" << termcolor::reset << std::endl; - goto validateLanguageVersion; + } - validateCompiler: - if (!validateCompiler("💽Compiler->", ctx, config_json)) { + + while (!validateCompiler("💽Compiler->", pro, config_json)) { std::cout << termcolor::red << "Invalid compiler - retry" << termcolor::reset << std::endl; - goto validateCompiler; + } - validateSourceDir: - if (!validateSourceDir("⛲Source Dir->" + ctx->src_dir + " : ", ctx, config_json)) { + + while (!validateSourceDir("⛲Source Dir->" + pro->src_dir + " : ", pro, config_json)) { std::cout << termcolor::red << "Invalid source directory - retry" << termcolor::reset << std::endl; - goto validateSourceDir; + } - validateBuildDir: - if (!validateBuildDir("🛠️Build Dir->" + ctx->build_dir + " : ", ctx, config_json)) { + + while (!validateBuildDir("🛠️Build Dir->" + pro->build_dir + " : ", pro, config_json)) { std::cout << termcolor::red << "Invalid build directory - retry" << termcolor::reset << std::endl; - goto validateBuildDir; + } - validateIncludeDir: - if (!validateIncludeDir("🫃Include Dir->" + ctx->include_dir + " : ", ctx, config_json)) { + + while (!validateIncludeDir("🫃Include Dir->" + pro->include_dir + " : ", pro, config_json)) { std::cout << termcolor::red << "Invalid include directory - retry" << termcolor::reset << std::endl; - goto validateIncludeDir; + } return true; } - bool writeConfig(std::shared_ptr &ctx) { + bool writeConfig(std::shared_ptr &pro) { std::ofstream file; std::string file_name = "config.json"; - file.open(ctx->project_path / file_name); - file << ctx->toJson(); + file.open(pro->project_path / file_name); + file << pro->toJson(); file << '\n'; file.close(); return true; diff --git a/src/Generators/Generators.hpp b/src/Generators/Generators.hpp index 072333f..59a41b2 100644 --- a/src/Generators/Generators.hpp +++ b/src/Generators/Generators.hpp @@ -3,6 +3,9 @@ #include #include "../Command/Command.hpp" namespace Generators{ + namespace GitIgnore{ + bool create(std::shared_ptr pro); + } namespace CMakeList{ typedef struct Dep { std::string fetch_declare; @@ -33,9 +36,10 @@ namespace Generators{ * @param ctx: the context of the command * @return a vector of dependencies that will be later combined to build the cmake file */ - void generateDeps(std::shared_ptr ctx, std::shared_ptr cmake_context); + void generateDeps(std::shared_ptr pro, std::shared_ptr cmake_context); - bool create(std::shared_ptr ctx); + bool create(std::shared_ptr pro); + bool createCMakeListsExecutable(std::shared_ptr pro); } namespace ConfigJson{ @@ -54,8 +58,8 @@ namespace Generators{ std::string authors_str; } Config; - bool readUserInput(std::shared_ptr ctx, std::shared_ptr config_json); - bool writeConfig(std::shared_ptr& ctx); + bool readUserInput(std::shared_ptr pro, std::shared_ptr config_json); + bool writeConfig(std::shared_ptr& ctx); /* * Validate cmakeVersion @@ -64,7 +68,7 @@ namespace Generators{ * @param config_json: the config json context * @return: true if the cmake version is valid */ - bool validateCmakeVersion(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json); + bool validateCmakeVersion(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json); /* * Validates the project name @@ -73,7 +77,7 @@ namespace Generators{ * @param config_json: the config json context * @return: true if the project name is valid */ - bool validateProjectName(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json); + bool validateProjectName(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json); /* * Validates the project version @@ -82,7 +86,7 @@ namespace Generators{ * @param config_json: the config json context * @return: true if the project version is valid */ - bool validateProjectVersion(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json); + bool validateProjectVersion(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json); /* * Validates the language version @@ -91,7 +95,7 @@ namespace Generators{ * @param config_json: the config json context * @return: true if the language version is valid */ - bool validateLanguageVersion(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json); + bool validateLanguageVersion(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json); /* * Validates the compiler @@ -100,7 +104,7 @@ namespace Generators{ * @param config_json: the config json context * @return: true if the language is valid */ - bool validateCompiler(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json); + bool validateCompiler(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json); /* * Validates the source directory @@ -109,7 +113,7 @@ namespace Generators{ * @param config_json: the config json context * @return: true if the source directory is valid */ - bool validateSourceDir(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json); + bool validateSourceDir(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json); /* * Validates the build directory @@ -118,7 +122,7 @@ namespace Generators{ * @param config_json: the config json context * @return: true if the build directory is valid */ - bool validateBuildDir(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json); + bool validateBuildDir(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json); /* * Reads the data from the user @@ -126,7 +130,7 @@ namespace Generators{ * @param config_json: the config json context * @return: true if the data is valid */ - bool validateIncludeDir(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json); + bool validateIncludeDir(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json); /* @@ -136,7 +140,7 @@ namespace Generators{ * @param config_json: the config json context * @return: true if the language is valid */ - bool validateLang(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json); + bool validateLang(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json); } } diff --git a/src/Generators/GitIgnoreGenerator.cpp b/src/Generators/GitIgnoreGenerator.cpp new file mode 100644 index 0000000..3f2b1e3 --- /dev/null +++ b/src/Generators/GitIgnoreGenerator.cpp @@ -0,0 +1,72 @@ +#include "Generators.hpp" +#include +#include "../Utils/CLI.hpp" + + +namespace Generators::GitIgnore{ + +bool write_gitignore(std::string gitignore, std::filesystem::path gitignore_path) { + try{ + std::ofstream file; + file.open(gitignore_path); + file << gitignore; + file.close(); + }catch(std::exception &e){ + std::cout << "Failed to create gitignore" << std::endl; + return false; + } + return true; +} + + using namespace Utils::CLI; + bool create(std::shared_ptr pro){ + std::filesystem::path gitignore_path = pro->project_path / ".gitignore"; + std::string gitignore = std::format(R"VOG( +# CMake +CMakeLists.txt.user +CMakeFiles/ +CMakeCache.txt +Makefile +_deps/ +CMakeCache.txt +cmake_install.cmake +install_manifest.txt +compile_commands.json +# Build dir +{}/* +# VS Code +.vscode/ +# CLion +.idea/ +# XCode +*.xcodeproj +*.xcworkspace +# Visual Studio +*.sln +*.vcxproj +*.vcxproj.filters +*.vcxproj.user +*.vcproj +*.vcproj.* +# vim/nvim +*.swp +compile_commands.json + )VOG", pro->build_dir); + + if(std::filesystem::exists(gitignore_path)){ + std::cout << "Gitignore already exists" << std::endl; + Prompt *prompt = new Prompt("Do you want to overwrite it?"); + prompt->Color(Ansi::RED)->ExitOnFailure()->Run(); + if(prompt->Get()){ + write_gitignore(gitignore, gitignore_path); + return true; + }else{ + return false; + } + }else{ + write_gitignore(gitignore, gitignore_path); + } + + return true; + } +} diff --git a/src/Generators/Validators/BuildDir.cpp b/src/Generators/Validators/BuildDir.cpp index f2cad75..34376ff 100644 --- a/src/Generators/Validators/BuildDir.cpp +++ b/src/Generators/Validators/BuildDir.cpp @@ -14,7 +14,7 @@ namespace Generators::ConfigJson{ * @param config_json: the config json context * @return: true if the build directory is valid */ - bool validateBuildDir(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json) { + bool validateBuildDir(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json) { std::cout << prefix; #ifndef TEST std::getline(std::cin, config_json->build_dir); @@ -38,7 +38,7 @@ namespace Generators::ConfigJson{ } return false; end: - ctx->build_dir = config_json->build_dir == "" ? ctx->build_dir : config_json->build_dir; + pro->build_dir = config_json->build_dir == "" ? pro->build_dir : config_json->build_dir; return true; } diff --git a/src/Generators/Validators/CMakeVersion.cpp b/src/Generators/Validators/CMakeVersion.cpp index 7cf18d5..32c481e 100644 --- a/src/Generators/Validators/CMakeVersion.cpp +++ b/src/Generators/Validators/CMakeVersion.cpp @@ -14,7 +14,7 @@ namespace Generators::ConfigJson{ * @param config_json: the config json context * @return: true if the version is valid */ - bool validateCmakeVersion(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json) { + bool validateCmakeVersion(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json) { std::cout << prefix; #ifndef TEST std::getline(std::cin, config_json->cmake_version); @@ -33,7 +33,7 @@ namespace Generators::ConfigJson{ } return false; end: - ctx->cmake_version = config_json->cmake_version == "" ? ctx->cmake_version : config_json->cmake_version; + pro->cmake_version = config_json->cmake_version == "" ? pro->cmake_version : config_json->cmake_version; return true; } diff --git a/src/Generators/Validators/Compiler.cpp b/src/Generators/Validators/Compiler.cpp index 33b0891..2c63b7c 100644 --- a/src/Generators/Validators/Compiler.cpp +++ b/src/Generators/Validators/Compiler.cpp @@ -15,12 +15,12 @@ namespace Generators::ConfigJson{ * @param config_json: the config json context * @return: true if the language is valid */ - bool validateCompiler(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json) { + bool validateCompiler(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json) { std::vector supportedCppCompilers = {"g++", "clang++"}; std::vector supportedCCompilers = {"gcc", "clang", "msvc", "icc", "tcc", "emcc"}; std::cout << prefix << ENDL; - if(ctx->lang == "cpp" || ctx->lang == "c++"){ - ctx->compiler = supportedCppCompilers[0]; + if(pro->lang == "cpp" || pro->lang == "c++"){ + pro->compiler = supportedCppCompilers[0]; std::cout << " Supported Compilers: ( "; for(std::string comp : supportedCppCompilers){ if(comp == supportedCppCompilers[0]){ @@ -32,7 +32,7 @@ namespace Generators::ConfigJson{ } } if(config_json->lang == "c"){ - ctx->compiler = supportedCCompilers[0]; + pro->compiler = supportedCCompilers[0]; std::cout << " Supported Compilers: "; for(std::string comp : supportedCCompilers){ if(comp == supportedCCompilers[0]){ @@ -67,7 +67,7 @@ namespace Generators::ConfigJson{ return false; end: - ctx->compiler = config_json->compiler == "" ? ctx->compiler : config_json->compiler; + pro->compiler = config_json->compiler == "" ? pro->compiler : config_json->compiler; return true; } diff --git a/src/Generators/Validators/IncludeDir.cpp b/src/Generators/Validators/IncludeDir.cpp index 76fd818..ecbb18d 100644 --- a/src/Generators/Validators/IncludeDir.cpp +++ b/src/Generators/Validators/IncludeDir.cpp @@ -14,7 +14,7 @@ namespace Generators::ConfigJson{ * @param config_json: the config json context * @return: true if the data is valid */ - bool validateIncludeDir(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json) { + bool validateIncludeDir(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json) { std::cout << prefix; #ifndef TEST std::getline(std::cin, config_json->include_dir); @@ -34,7 +34,7 @@ namespace Generators::ConfigJson{ return false; end: - ctx->include_dir = config_json->include_dir == "" ? ctx->include_dir : config_json->include_dir; + pro->include_dir = config_json->include_dir == "" ? pro->include_dir : config_json->include_dir; return true; } diff --git a/src/Generators/Validators/Language.cpp b/src/Generators/Validators/Language.cpp index c2e02ac..88b5c5b 100644 --- a/src/Generators/Validators/Language.cpp +++ b/src/Generators/Validators/Language.cpp @@ -14,7 +14,7 @@ namespace Generators::ConfigJson{ * @param config_json: the config json context * @return: true if the language is valid */ - bool validateLang(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json) { + bool validateLang(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json) { std::vector supportedLangs = {"cpp", "c"}; std::cout << prefix << ENDL; std::cout << " Supported languages: ( "; @@ -36,7 +36,7 @@ namespace Generators::ConfigJson{ } return false; end: - ctx->lang = config_json->lang == "" ? ctx->lang : config_json->lang; + pro->lang = config_json->lang == "" ? pro->lang : config_json->lang; return true; } } diff --git a/src/Generators/Validators/LanguageVersion.cpp b/src/Generators/Validators/LanguageVersion.cpp index 5249b94..52879fa 100644 --- a/src/Generators/Validators/LanguageVersion.cpp +++ b/src/Generators/Validators/LanguageVersion.cpp @@ -14,7 +14,7 @@ namespace Generators::ConfigJson{ * @param config_json: the config json context * @return: true if the language version is valid */ - bool validateLanguageVersion(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json) { + bool validateLanguageVersion(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json) { std::cout << prefix; #ifndef TEST std::getline(std::cin, config_json->lang_version); @@ -40,7 +40,7 @@ namespace Generators::ConfigJson{ } return false; end: - ctx->lang_version = config_json->lang_version == "" ? ctx->lang_version : config_json->lang_version; + pro->lang_version = config_json->lang_version == "" ? pro->lang_version : config_json->lang_version; return true; } diff --git a/src/Generators/Validators/ProjectName.cpp b/src/Generators/Validators/ProjectName.cpp index a546296..ef0ee02 100644 --- a/src/Generators/Validators/ProjectName.cpp +++ b/src/Generators/Validators/ProjectName.cpp @@ -14,7 +14,7 @@ namespace Generators::ConfigJson{ * @param config_json: the config json context * @return: true if the project name is valid */ - bool validateProjectName(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json) { + bool validateProjectName(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json) { std::cout << prefix; #ifndef TEST std::getline(std::cin, config_json->project_name); @@ -33,7 +33,7 @@ namespace Generators::ConfigJson{ return false; end: - ctx->project_name = config_json->project_name == "" ? ctx->project_name : config_json->project_name; + pro->project_name = config_json->project_name == "" ? pro->project_name : config_json->project_name; return true; } diff --git a/src/Generators/Validators/ProjectVersion.cpp b/src/Generators/Validators/ProjectVersion.cpp index 5a33dd8..3cf5549 100644 --- a/src/Generators/Validators/ProjectVersion.cpp +++ b/src/Generators/Validators/ProjectVersion.cpp @@ -15,7 +15,7 @@ namespace Generators::ConfigJson{ * @param config_json: the config json context * @return: true if the project version is valid */ - bool validateProjectVersion(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json) { + bool validateProjectVersion(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json) { std::cout << prefix; #ifndef TEST std::getline(std::cin, config_json->project_version); @@ -34,7 +34,7 @@ namespace Generators::ConfigJson{ return false; //If the version is empty we're gonna set it end: - ctx->project_version = config_json->project_version == "" ? ctx->project_version : config_json->project_version; + pro->project_version = config_json->project_version == "" ? pro->project_version : config_json->project_version; return true; diff --git a/src/Generators/Validators/SourceDir.cpp b/src/Generators/Validators/SourceDir.cpp index 4e48780..44f3545 100644 --- a/src/Generators/Validators/SourceDir.cpp +++ b/src/Generators/Validators/SourceDir.cpp @@ -15,7 +15,7 @@ namespace Generators::ConfigJson{ * @param config_json: the config json context * @return: true if the source directory is valid */ - bool validateSourceDir(std::string prefix, std::shared_ptr ctx, std::shared_ptr config_json) { + bool validateSourceDir(std::string prefix, std::shared_ptr pro, std::shared_ptr config_json) { std::cout << prefix; #ifndef TEST std::getline(std::cin, config_json->src_dir); @@ -39,7 +39,7 @@ namespace Generators::ConfigJson{ } return false; end: - ctx->src_dir = config_json->src_dir == "" ? ctx->src_dir : config_json->src_dir; + pro->src_dir = config_json->src_dir == "" ? pro->src_dir : config_json->src_dir; return true; } diff --git a/src/Test/TestCommands.cpp b/src/Test/TestCommands.cpp index 1b7e458..c6c7aa6 100644 --- a/src/Test/TestCommands.cpp +++ b/src/Test/TestCommands.cpp @@ -26,6 +26,11 @@ namespace Tests{ namespace Tests::Command { + using ::Command::Project; + using ::Command::Interface; + + + // [[deprecated("This test is deprecated because it is not a unit test")]] diff --git a/src/Test/TestGenerators.cpp b/src/Test/TestGenerators.cpp index f1b66ea..ca77901 100644 --- a/src/Test/TestGenerators.cpp +++ b/src/Test/TestGenerators.cpp @@ -18,21 +18,21 @@ namespace Test::Generators { for(std::string version : versions){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->cmake_version = version; - if(!::Generators::ConfigJson::validateCmakeVersion("Testing version " + version + "\n",ctx, cmake_context)){ + if(!::Generators::ConfigJson::validateCmakeVersion("Testing version " + version + "\n",pro, cmake_context)){ return false; } } for(std::string version : failing_versions){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->cmake_version = version; - if(::Generators::ConfigJson::validateCmakeVersion("Testing failing version " + version + "\n",ctx, cmake_context)){ + if(::Generators::ConfigJson::validateCmakeVersion("Testing failing version " + version + "\n",pro, cmake_context)){ return false; } } @@ -63,21 +63,21 @@ namespace Test::Generators { }; for(std::string name : mock_names){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->project_name = name; - if(!::Generators::ConfigJson::validateProjectName("Test " + name + " \n",ctx, cmake_context)){ + if(!::Generators::ConfigJson::validateProjectName("Test " + name + " \n",pro, cmake_context)){ return false; } } for(std::string name : failing_names){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->project_name = name; - if(::Generators::ConfigJson::validateProjectName("Test failing name " + name + " \n",ctx, cmake_context)){ + if(::Generators::ConfigJson::validateProjectName("Test failing name " + name + " \n",pro, cmake_context)){ return false; } } @@ -88,20 +88,20 @@ namespace Test::Generators { std::vector failing_project_versions = {"q4.26.*","123902", "5.xx42069","420x69","42x69x420"}; for(std::string version : passing_project_versions){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->project_version = version; - if(!::Generators::ConfigJson::validateProjectVersion("Testing version " + version + "\n",ctx, cmake_context)){ + if(!::Generators::ConfigJson::validateProjectVersion("Testing version " + version + "\n",pro, cmake_context)){ return false; } } for(std::string version : failing_project_versions){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->project_version = version; - if(::Generators::ConfigJson::validateProjectVersion("Testing failing version " + version + "\n",ctx, cmake_context)){ + if(::Generators::ConfigJson::validateProjectVersion("Testing failing version " + version + "\n",pro, cmake_context)){ return false; } } @@ -116,42 +116,42 @@ namespace Test::Generators { std::vector failing_c_language_versions = {"3", "69", "420", "69.420", "69.420.69", "69.420.69.420"}; for(std::string version : passing_cpp_language_versions){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->lang_version = version; cmake_context->lang = "cpp"; - if(!::Generators::ConfigJson::validateLanguageVersion("Testing cpp language version " + version + "\n",ctx, cmake_context)){ + if(!::Generators::ConfigJson::validateLanguageVersion("Testing cpp language version " + version + "\n",pro, cmake_context)){ return false; } } for(std::string version : failing_cpp_language_versions){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->lang_version = version; cmake_context->lang = "cpp"; - if(::Generators::ConfigJson::validateLanguageVersion("Testing failing cpp language version " + version + "\n",ctx, cmake_context)){ + if(::Generators::ConfigJson::validateLanguageVersion("Testing failing cpp language version " + version + "\n",pro, cmake_context)){ return false; } } for(std::string version : passing_c_language_versions){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->lang_version = version; cmake_context->lang = "c"; - if(!::Generators::ConfigJson::validateLanguageVersion("Testing c language version " + version + "\n",ctx, cmake_context)){ + if(!::Generators::ConfigJson::validateLanguageVersion("Testing c language version " + version + "\n",pro, cmake_context)){ return false; } } for(std::string version : failing_c_language_versions){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->lang_version = version; cmake_context->lang = "c"; - if(::Generators::ConfigJson::validateLanguageVersion("Testing failing c language version " + version + "\n",ctx, cmake_context)){ + if(::Generators::ConfigJson::validateLanguageVersion("Testing failing c language version " + version + "\n",pro, cmake_context)){ return false; } } @@ -166,42 +166,42 @@ namespace Test::Generators { std::vector failing_cpp_compilers = {"fuckitidontknow","iforgot","msvc","icc","tcc","emcc","clang"}; for(std::string compiler : passing_c_compilers){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->compiler = compiler; cmake_context->lang = "c"; - if(!::Generators::ConfigJson::validateCompiler("Testing c compiler " + compiler + "\n",ctx, cmake_context)){ + if(!::Generators::ConfigJson::validateCompiler("Testing c compiler " + compiler + "\n",pro, cmake_context)){ return false; } } for(std::string compiler : failing_c_compilers){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->compiler = compiler; cmake_context->lang = "c"; - if(::Generators::ConfigJson::validateCompiler("Testing failing c compiler " + compiler + "\n",ctx, cmake_context)){ + if(::Generators::ConfigJson::validateCompiler("Testing failing c compiler " + compiler + "\n",pro, cmake_context)){ return false; } } for(std::string compiler : passing_cpp_compilers){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->compiler = compiler; cmake_context->lang = "cpp"; - if(!::Generators::ConfigJson::validateCompiler("Testing cpp compiler " + compiler + "\n",ctx, cmake_context)){ + if(!::Generators::ConfigJson::validateCompiler("Testing cpp compiler " + compiler + "\n",pro, cmake_context)){ return false; } } for(std::string compiler : failing_cpp_compilers){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->compiler = compiler; cmake_context->lang = "cpp"; - if(::Generators::ConfigJson::validateCompiler("Testing failing cpp compiler " + compiler + "\n",ctx, cmake_context)){ + if(::Generators::ConfigJson::validateCompiler("Testing failing cpp compiler " + compiler + "\n",pro, cmake_context)){ return false; } } @@ -214,22 +214,22 @@ namespace Test::Generators { std::vector failing_source_dirs = {"S*($#*(@","*(\&\$\^","srcs_dir//*$", long_string,"src_dir//348934","source_dir///84$#*","sources_dir*$(@#"}; for(std::string source_dir : passing_source_dirs){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->src_dir = source_dir; - if(!::Generators::ConfigJson::validateSourceDir("Testing source dir " + source_dir + "\n",ctx, cmake_context)){ + if(!::Generators::ConfigJson::validateSourceDir("Testing source dir " + source_dir + "\n",pro, cmake_context)){ return false; } } for(std::string source_dir : failing_source_dirs){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->src_dir = source_dir; - if(::Generators::ConfigJson::validateSourceDir("Testing failing source dir " + source_dir + "\n",ctx, cmake_context)){ + if(::Generators::ConfigJson::validateSourceDir("Testing failing source dir " + source_dir + "\n",pro, cmake_context)){ return false; } } @@ -240,22 +240,22 @@ namespace Test::Generators { std::vector failing_build_dirs = {"B*($#*(@","*(\&\$\^","builds_dir//*$","build_dir//348934", long_string,"builds_dir///84$#*","builds_dir*$(@#"}; for(std::string build_dir : passing_build_dirs){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->build_dir = build_dir; - if(!::Generators::ConfigJson::validateBuildDir("Testing build dir " + build_dir + "\n",ctx, cmake_context)){ + if(!::Generators::ConfigJson::validateBuildDir("Testing build dir " + build_dir + "\n",pro, cmake_context)){ return false; } } for(std::string build_dir : failing_build_dirs){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->build_dir = build_dir; - if(::Generators::ConfigJson::validateBuildDir("Testing failing build dir " + build_dir + "\n",ctx, cmake_context)){ + if(::Generators::ConfigJson::validateBuildDir("Testing failing build dir " + build_dir + "\n",pro, cmake_context)){ return false; } } @@ -266,22 +266,22 @@ namespace Test::Generators { std::vector failing_include_dirs = {"I*($#*(@","*(\&\$\^","includes_dir//*$", long_string,"include_dir//348934","includes_dir///84$#*"}; for(std::string include_dir : passing_include_dirs){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->include_dir = include_dir; - if(!::Generators::ConfigJson::validateIncludeDir("Testing include dir " + include_dir + "\n",ctx, cmake_context)){ + if(!::Generators::ConfigJson::validateIncludeDir("Testing include dir " + include_dir + "\n",pro, cmake_context)){ return false; } } for(std::string include_dir : failing_include_dirs){ - std::shared_ptr ctx = std::make_shared(); + std::shared_ptr pro = std::make_shared(); std::shared_ptr<::Generators::ConfigJson::Config> cmake_context = std::make_shared<::Generators::ConfigJson::Config>(); cmake_context->include_dir = include_dir; - if(::Generators::ConfigJson::validateIncludeDir("Testing failing include dir " + include_dir + "\n",ctx, cmake_context)){ + if(::Generators::ConfigJson::validateIncludeDir("Testing failing include dir " + include_dir + "\n",pro, cmake_context)){ return false; } } diff --git a/src/Utils/CLI.hpp b/src/Utils/CLI.hpp index 0d93a88..d238f09 100644 --- a/src/Utils/CLI.hpp +++ b/src/Utils/CLI.hpp @@ -1,3 +1,5 @@ +#pragma once +#include #include #include #include @@ -8,7 +10,6 @@ namespace Utils::CLI { - //ansicolors namespace Ansi{ const std::string RESET = "\033[0m"; const std::string RED = "\033[31m"; @@ -23,13 +24,14 @@ namespace Utils::CLI { typedef struct ListItem { std::string primary; std::string subtext; - ListItem(std::string primary, std::string subtext); + ListItem(std::string primary, std::string subtext=""); } ListItem; class List { private: std::string index_color; std::string primary_color; std::string subtext_color; + std::string title; bool reversed_index; bool numbered; std::vector items; @@ -38,6 +40,7 @@ namespace Utils::CLI { std::stringstream stream; List(); + List(std::string title); ~List(); /* * Sets the color of the index @@ -82,4 +85,83 @@ namespace Utils::CLI { */ void pushBack(ListItem item); }; + template + class Prompt{ + static_assert( + std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value, + "Prompt only supports std::string, int, float, and double, bool"); + private: + std::string prompt; + std::string color{Ansi::WHITE}; + std::string input; + T value; + int max_length{0}; + bool exit_on_failure{false}; + std::function validator; + std::vector options; + void get_input(); + bool is_in_options(T option); + bool yoink(); + virtual bool has_options(); + virtual bool has_max_length(); + virtual bool has_validator(); + public: + /* + * Create a prompt builder + * @param prompt the prompt to display + * @param color the color of the prompt + */ + Prompt(std::string prompt); + Prompt* Message(std::string prompt); + /* + * Adds a vector of options to the prompt + * @param options the options to add + * @return this + */ + Prompt* Options(std::vector options); + /* + * Adds an option to the prompt in the form of a type + * @param option the option to add + * @return this + */ + Prompt* AddOption(T option); + /* + * Sets the maximum length of the input + * @param max_length the maximum length of the input + * @return this + */ + Prompt* MaxLength(int max_length); + /* + * Sets the color of the prompt + * @param color the color to set + * @return this + */ + Prompt* Color(std::string color); + /* + * Sets a validator for the input + * @param validator a function that takes a T and returns a bool + * @return this + */ + Prompt* Validator(std::function validator); + /* + * Runs the prompt, asks for input and handles exceptions and validations + * @return true if the prompt was successful + */ + Prompt* ExitOnFailure(); + bool Run(); + /* + * Gets the realized value of the prompt + * @return the value of the prompt + */ + T Get(); + }; + template class Prompt; + template class Prompt; + template class Prompt; + template class Prompt; + template class Prompt; } diff --git a/src/Utils/CLI.cpp b/src/Utils/CLIList.cpp similarity index 96% rename from src/Utils/CLI.cpp rename to src/Utils/CLIList.cpp index 8baab4f..eadae33 100644 --- a/src/Utils/CLI.cpp +++ b/src/Utils/CLIList.cpp @@ -19,6 +19,10 @@ namespace Utils::CLI { primary_color = Ansi::WHITE; subtext_color = Ansi::BLUE; } + List::List(std::string _title){ + title = _title; + List(); + } List::~List(){ } List* List::IndexColor(std::string color){ @@ -75,5 +79,4 @@ namespace Utils::CLI { void List::pushBack(ListItem item){ items.push_back(item); } - } diff --git a/src/Utils/CLIPrompt.cpp b/src/Utils/CLIPrompt.cpp new file mode 100644 index 0000000..94b5170 --- /dev/null +++ b/src/Utils/CLIPrompt.cpp @@ -0,0 +1,164 @@ +#include "CLI.hpp" +#include + +namespace Utils::CLI { + template + Prompt::Prompt(std::string prompt){ + this->prompt = prompt; + this->color = Ansi::GREEN; + } + + template + Prompt* Prompt::AddOption(T option){ + this->options.push_back(option); + return this; + } + template + Prompt* Prompt::MaxLength(int max_length){ + this->max_length = max_length; + return this; + } + template + Prompt* Prompt::Options(std::vector options){ + this->options = options; + return this; + } + template + Prompt* Prompt::Color(std::string color){ + this->color = color; + return this; + } + template + Prompt* Prompt::Validator(std::function validator){ + this->validator = validator; + return this; + } + template + bool Prompt::has_options(){ + return options.size() > 0; + } + template + bool Prompt::has_max_length(){ + return max_length > 0; + } + template + bool Prompt::has_validator(){ + return validator != nullptr; + } + template <> + bool Prompt::yoink(){ + try{ + value = std::stoi(input); + }catch(std::invalid_argument e){ + return false; + } + return true; + } + template <> + bool Prompt::yoink(){ + try{ + value = std::stof(input); + }catch(std::invalid_argument e){ + return false; + } + return true; + } + template <> + bool Prompt::yoink(){ + try{ + value = std::stod(input); + }catch(std::invalid_argument e){ + return false; + } + return true; + } + template <> + bool Prompt::yoink(){ + value = input; + return true; + } + template <> + bool Prompt::yoink(){ + if(input == "y" || input == "Y"){ + value = true; + return true; + }else if(input == "n" || input == "N"){ + value = false; + return true; + } + return false; + } + template + void Prompt::get_input(){ + if(std::is_same::value){ + std::cout << color << prompt << Ansi::RESET; + std::cout << "[y/n]"; + + }else if(has_options()){ + std::cout << color << prompt << Ansi::RESET << "\n"; + for(size_t i = 0; i < options.size(); i++){ + std::cout << "[ " << options[i] << " ]" << "\n"; + } + std::cout << ">"; + }else{ + std::cout << color << prompt << Ansi::RESET; + } + std::getline(std::cin, input); + }; + template + bool Prompt::is_in_options(T option){ + for(size_t i = 0; i < options.size(); i++){ + if(options[i] == option){ + return true; + } + } + return false; + } + template + T Prompt::Get(){ + return value; + } + template + Prompt* Prompt::ExitOnFailure(){ + this->exit_on_failure = true; + return this; + } + template + bool Prompt::Run(){ + get_input(); + if(has_max_length()){ + while(input.length() > max_length){ + std::cout << "Input too long" << std::endl; + if(exit_on_failure){ + exit(1); + } + return false; + } + } + if(!Prompt::yoink()){ + std::cout << "Invalid input for type " << std::endl; + return false; + } + if(has_validator()){ + while(!validator(value)){ + std::cout << "Invalid input: " << std::endl; + if(exit_on_failure){ + exit(1); + } + return false; + } + } + + if(has_options() && !std::is_same::value){ + if(!is_in_options(value)){ + std::cout << "Invalid input: " << std::endl; + if(exit_on_failure){ + exit(1); + } + return false; + } + } + return true; + } +} + diff --git a/src/Utils/General.hpp b/src/Utils/General.hpp index db5a14f..b66c895 100644 --- a/src/Utils/General.hpp +++ b/src/Utils/General.hpp @@ -2,7 +2,8 @@ #include #include #include - +#include +#include namespace Utils { using nlohmann::json; std::string getFolderName(); @@ -10,4 +11,14 @@ namespace Utils { void toLower(std::string& str); json fetchJson(std::string url); std::string fetchText(std::string url); + struct TableFormat { + int width; + char fill; + TableFormat(): width(14), fill(' ') {} + template + TableFormat& operator<<(const T& data) { + std::cout<< std::setw(width) << data << std::setfill(fill); + return *this; + } + }; }