diff --git a/source/src/commands/add-template.cpp b/source/src/commands/add-template.cpp new file mode 100644 index 0000000..5b09992 --- /dev/null +++ b/source/src/commands/add-template.cpp @@ -0,0 +1,200 @@ +#include "command_registry.hpp" +#include "config.hpp" +#include "templates.hpp" +#include "utils/directories.hpp" +#include "utils/output.hpp" +#include "utils/utils.hpp" + +#include +#include +#include + +#define JSON_INLINE_ALL +#include + +namespace dropshell { + +int add_template_handler(const CommandContext& ctx); + +static std::vector add_template_name_list = {"add-template"}; + +struct AddTemplateCommandRegister { + AddTemplateCommandRegister() { + CommandRegistry::instance().register_command({ + add_template_name_list, + add_template_handler, + nullptr, + false, // hidden + true, // requires_config + false, // requires_install + 0, // min_args + 1, // max_args (optional path, defaults to cwd) + "add-template [PATH]", + "Register the current directory (or PATH) as a template source.", + R"( +Adds a directory to template_paths.json as a template source. + + add-template Register current directory (or its git repo root) + add-template PATH Register the given directory (or its git repo root) + +The command will: + 1. Walk up to the git repo root (if inside a git repo) + 2. Verify dropshell templates exist in the tree + 3. Auto-detect the git remote URL + 4. Check the path (or a parent) isn't already registered + 5. Add the entry to template_paths.json + 6. Create dropshell-templates.list if it doesn't already exist + )" + }); + } +} add_template_command_register; + +// Find the git repo root containing dir_path, or return empty if not in a git repo. +static std::filesystem::path find_git_root(const std::filesystem::path& dir_path) { + std::string cmd = "git -C " + quote(dir_path.string()) + " rev-parse --show-toplevel 2>/dev/null"; + FILE* pipe = popen(cmd.c_str(), "r"); + if (!pipe) return {}; + std::string result; + char buffer[512]; + while (fgets(buffer, sizeof(buffer), pipe)) + result += buffer; + int ret = pclose(pipe); + if (ret != 0) return {}; + // Trim + while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) + result.pop_back(); + if (result.empty()) return {}; + return std::filesystem::canonical(result); +} + +// Extract the LOCAL_PATH from a template_paths.json entry string +static std::string entry_local_path(const std::string& entry) { + size_t colon = entry.find(':'); + return (colon != std::string::npos) ? entry.substr(0, colon) : entry; +} + +// Check if candidate is equal to or a child of parent +static bool is_same_or_child(const std::string& candidate, const std::string& parent) { + if (candidate == parent) return true; + // Check if candidate starts with parent + "/" + return candidate.size() > parent.size() && + candidate.compare(0, parent.size(), parent) == 0 && + candidate[parent.size()] == '/'; +} + +int add_template_handler(const CommandContext& ctx) { + // 1. Determine the starting directory + std::filesystem::path start_path; + if (ctx.args.empty()) + start_path = std::filesystem::current_path(); + else + start_path = std::filesystem::path(ctx.args[0]); + + if (start_path.is_relative()) + start_path = std::filesystem::current_path() / start_path; + + std::error_code ec; + start_path = std::filesystem::canonical(start_path, ec); + if (ec || !std::filesystem::is_directory(start_path)) { + error << "Not a valid directory: " << start_path << std::endl; + return 1; + } + + // 2. Walk up to git repo root if we're inside a git repo + std::filesystem::path dir_path = start_path; + std::filesystem::path git_root = find_git_root(start_path); + if (!git_root.empty()) { + if (git_root != start_path) + info << "Using git repo root: " << git_root.string() << std::endl; + dir_path = git_root; + } + std::string local_path = dir_path.string(); + + // 3. Check for templates before doing anything else + auto templates = gTemplateManager().discover_templates(local_path); + if (templates.empty()) { + error << "No dropshell templates found in " << local_path << std::endl; + info << " (looked for directories containing " << filenames::template_info_env << ")" << std::endl; + return 1; + } + + // 4. Find the template_paths.json file location + auto server_def_paths = gConfig().get_local_server_definition_paths(); + if (server_def_paths.empty()) { + error << "No server definition paths configured" << std::endl; + return 1; + } + std::filesystem::path json_path = std::filesystem::path(server_def_paths[0]) / filenames::template_paths_json; + + // 5. Load existing template_paths.json (or start fresh) + nlohmann::json j = nlohmann::json::array(); + if (std::filesystem::exists(json_path)) { + std::ifstream f(json_path); + if (f.is_open()) { + try { + j = nlohmann::json::parse(f); + } catch (...) { + error << "Failed to parse " << json_path << std::endl; + return 1; + } + } + } + + // 6. Check if this path or a parent is already registered + for (const auto& entry : j) { + if (!entry.is_string()) continue; + std::string existing = entry_local_path(entry.get()); + if (is_same_or_child(local_path, existing)) { + info << "Already covered by existing entry: " << existing << std::endl; + return 0; + } + } + + // 7. Auto-detect git remote URL + std::string git_url; + if (!git_root.empty()) { + std::string cmd = "git -C " + quote(local_path) + " remote get-url origin 2>/dev/null"; + FILE* pipe = popen(cmd.c_str(), "r"); + if (pipe) { + char buffer[512]; + while (fgets(buffer, sizeof(buffer), pipe)) + git_url += buffer; + pclose(pipe); + while (!git_url.empty() && (git_url.back() == '\n' || git_url.back() == '\r')) + git_url.pop_back(); + } + } + + // 8. Build entry and add + std::string new_entry = local_path; + if (!git_url.empty()) + new_entry += ":" + git_url; + j.push_back(new_entry); + + // 9. Write template_paths.json + std::filesystem::create_directories(json_path.parent_path()); + std::ofstream out(json_path); + if (!out.is_open()) { + error << "Failed to write " << json_path << std::endl; + return 1; + } + out << j.dump(4) << std::endl; + out.close(); + + info << "Added to " << json_path << ":" << std::endl; + info << " " << new_entry << std::endl; + + // 10. Create dropshell-templates.list if it doesn't exist + std::filesystem::path list_file = dir_path / filenames::dropshell_templates_list; + if (!std::filesystem::exists(list_file)) { + gTemplateManager().write_list_file(local_path, templates); + } else { + info << "Using existing " << list_file << std::endl; + } + + info << "Found " << templates.size() << " template(s)" << std::endl; + + return 0; +} + +} // namespace dropshell diff --git a/source/src/commands/help.cpp b/source/src/commands/help.cpp index 084378e..a4c4389 100644 --- a/source/src/commands/help.cpp +++ b/source/src/commands/help.cpp @@ -158,6 +158,7 @@ int help_handler(const CommandContext& ctx) { show_command("create-template"); info << std::endl; show_command("validate-template"); + show_command("add-template"); info << std::endl; show_command("pull"); } diff --git a/source/src/templates.cpp b/source/src/templates.cpp index 7b978c1..84f6d0c 100644 --- a/source/src/templates.cpp +++ b/source/src/templates.cpp @@ -169,7 +169,8 @@ } // Check if this directory contains template_info.env - if (std::filesystem::exists(it->path() / filenames::template_info_env)) { + std::error_code ec; + if (std::filesystem::exists(it->path() / filenames::template_info_env, ec) && !ec) { // Get relative path from root std::string rel = std::filesystem::relative(it->path(), root).string(); result.push_back(rel); diff --git a/source/src/templates.hpp b/source/src/templates.hpp index 9556307..b9e474f 100644 --- a/source/src/templates.hpp +++ b/source/src/templates.hpp @@ -70,13 +70,15 @@ class template_manager { bool pull_all(); bool pull_for_template(const std::string& template_name); + // Template discovery and list file management + std::vector discover_templates(const std::string& local_path) const; + void write_list_file(const std::string& local_path, const std::vector& template_dirs) const; + private: static bool required_file(std::string path, std::string template_name); void resolve_templates(); void resolve_source(size_t source_index); std::vector parse_list_file(const std::string& local_path) const; - std::vector discover_templates(const std::string& local_path) const; - void write_list_file(const std::string& local_path, const std::vector& template_dirs) const; bool git_pull_source(const TemplateSource& source); bool git_clone_source(const TemplateSource& source);