code: production,dbal,hpp (3 files)

This commit is contained in:
Richard Ward
2025-12-30 23:33:28 +00:00
parent 06f73d3996
commit afbeab8feb
3 changed files with 725 additions and 0 deletions
@@ -0,0 +1,121 @@
/**
* Command-line argument parser
*/
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
#include <sstream>
namespace tools {
class ArgParser {
public:
ArgParser(int argc, char* argv[]) {
parse(argc, argv);
}
/**
* Get positional argument by index (0-based, excludes flags)
*/
std::string get_positional(size_t index) const {
return index < positional_.size() ? positional_[index] : "";
}
/**
* Get option value by name (e.g., --name value)
*/
std::string get_option(const std::string& name, const std::string& default_value = "") const {
auto it = options_.find(name);
return it != options_.end() ? it->second : default_value;
}
/**
* Get integer option
*/
int get_int_option(const std::string& name, int default_value = 0) const {
auto value = get_option(name);
if (value.empty()) return default_value;
try {
return std::stoi(value);
} catch (...) {
return default_value;
}
}
/**
* Check if flag is present (e.g., --dry-run)
*/
bool has_flag(const std::string& name) const {
return flags_.count(name) > 0;
}
/**
* Get comma-separated list option
*/
std::vector<std::string> get_list_option(const std::string& name) const {
auto value = get_option(name);
if (value.empty()) return {};
std::vector<std::string> result;
std::stringstream ss(value);
std::string item;
while (std::getline(ss, item, ',')) {
// Trim whitespace
item.erase(0, item.find_first_not_of(" \t"));
item.erase(item.find_last_not_of(" \t") + 1);
if (!item.empty()) {
result.push_back(item);
}
}
return result;
}
/**
* Get all positional arguments
*/
const std::vector<std::string>& positional_args() const {
return positional_;
}
private:
std::vector<std::string> positional_;
std::unordered_map<std::string, std::string> options_;
std::unordered_set<std::string> flags_;
// Options that take values
const std::unordered_set<std::string> value_options_ = {
"name", "description", "category", "min-level",
"entities", "components", "deps", "output"
};
void parse(int argc, char* argv[]) {
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg.substr(0, 2) == "--") {
auto name = arg.substr(2);
// Check if this option takes a value
if (value_options_.count(name) && i + 1 < argc) {
options_[name] = argv[++i];
} else {
flags_.insert(name);
}
} else if (arg[0] == '-') {
// Short flag (e.g., -h, -v)
flags_.insert(arg.substr(1));
} else {
positional_.push_back(arg);
}
}
}
};
} // namespace tools
@@ -0,0 +1,324 @@
/**
* Lua Runner - sol2-based Lua script executor
*
* Provides a sandboxed Lua environment for running package template scripts.
*/
#pragma once
#include <string>
#include <vector>
#include <filesystem>
#include <memory>
#include <stdexcept>
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
#include <nlohmann/json.hpp>
namespace fs = std::filesystem;
using json = nlohmann::json;
namespace tools {
/**
* Configuration object to pass to Lua functions
*/
class LuaConfig {
public:
json data;
void set(const std::string& key, const std::string& value) {
if (!value.empty()) {
data[key] = value;
}
}
void set(const std::string& key, int value) {
data[key] = value;
}
void set(const std::string& key, bool value) {
data[key] = value;
}
void set_list(const std::string& key, const std::vector<std::string>& values) {
if (!values.empty()) {
data[key] = values;
} else {
data[key] = json::array();
}
}
static LuaConfig from_json(const std::string& json_str) {
LuaConfig config;
config.data = json::parse(json_str);
return config;
}
sol::table to_lua_table(sol::state& lua) const {
return json_to_lua(lua, data);
}
private:
sol::object json_to_lua(sol::state& lua, const json& j) const {
if (j.is_null()) {
return sol::nil;
} else if (j.is_boolean()) {
return sol::make_object(lua, j.get<bool>());
} else if (j.is_number_integer()) {
return sol::make_object(lua, j.get<int64_t>());
} else if (j.is_number_float()) {
return sol::make_object(lua, j.get<double>());
} else if (j.is_string()) {
return sol::make_object(lua, j.get<std::string>());
} else if (j.is_array()) {
sol::table arr = lua.create_table();
int idx = 1;
for (const auto& elem : j) {
arr[idx++] = json_to_lua(lua, elem);
}
return arr;
} else if (j.is_object()) {
sol::table obj = lua.create_table();
for (auto& [key, val] : j.items()) {
obj[key] = json_to_lua(lua, val);
}
return obj;
}
return sol::nil;
}
};
/**
* Generated file structure
*/
struct GeneratedFile {
std::string path;
std::string content;
};
using GeneratedFiles = std::vector<GeneratedFile>;
/**
* Validation result from Lua
*/
struct ValidationResult {
bool valid = false;
std::vector<std::string> errors;
};
/**
* Lua script runner with sandboxed environment
*/
class LuaRunner {
public:
explicit LuaRunner(const fs::path& scripts_path)
: scripts_path_(scripts_path) {
initialize();
}
/**
* Load a Lua module by name
*/
bool load_module(const std::string& module_name) {
auto module_path = scripts_path_ / module_name / "init.lua";
if (!fs::exists(module_path)) {
// Try direct file
module_path = scripts_path_ / (module_name + ".lua");
}
if (!fs::exists(module_path)) {
return false;
}
try {
auto result = lua_.safe_script_file(module_path.string());
if (!result.valid()) {
sol::error err = result;
last_error_ = err.what();
return false;
}
// Store the module
loaded_modules_[module_name] = result.get<sol::table>();
return true;
} catch (const std::exception& e) {
last_error_ = e.what();
return false;
}
}
/**
* Call a Lua function with config, returning typed result
*/
template<typename T>
T call(const std::string& func_path, const LuaConfig& config) {
return call_impl<T>(func_path, config.to_lua_table(lua_));
}
/**
* Call a Lua function without arguments
*/
template<typename T>
T call(const std::string& func_path) {
return call_impl<T>(func_path, sol::nil);
}
const std::string& last_error() const { return last_error_; }
private:
sol::state lua_;
fs::path scripts_path_;
std::string last_error_;
std::unordered_map<std::string, sol::table> loaded_modules_;
void initialize() {
// Open safe libraries only (no os, io, debug)
lua_.open_libraries(
sol::lib::base,
sol::lib::string,
sol::lib::table,
sol::lib::math,
sol::lib::utf8
);
// Set up package path
std::string package_path = lua_["package"]["path"];
package_path += ";" + scripts_path_.string() + "/?.lua";
package_path += ";" + scripts_path_.string() + "/?/init.lua";
lua_["package"]["path"] = package_path;
// Add custom require that respects sandbox
setup_sandboxed_require();
}
void setup_sandboxed_require() {
// Override require to prevent loading unsafe modules
lua_.set_function("safe_require", [this](const std::string& name) -> sol::object {
// Block dangerous modules
static const std::vector<std::string> blocked = {
"os", "io", "debug", "ffi", "package.loadlib"
};
for (const auto& b : blocked) {
if (name == b || name.find(b + ".") == 0) {
throw std::runtime_error("Module '" + name + "' is not allowed in sandbox");
}
}
// Check if already loaded
if (loaded_modules_.count(name)) {
return loaded_modules_[name];
}
// Try to load
if (load_module(name)) {
return loaded_modules_[name];
}
throw std::runtime_error("Module '" + name + "' not found");
});
// Replace require
lua_.script(R"(
local original_require = require
require = function(name)
local ok, result = pcall(safe_require, name)
if ok then return result end
return original_require(name)
end
)");
}
template<typename T>
T call_impl(const std::string& func_path, sol::object arg) {
// Parse "module.function" path
auto dot_pos = func_path.find('.');
if (dot_pos == std::string::npos) {
throw std::runtime_error("Invalid function path: " + func_path);
}
auto module_name = func_path.substr(0, dot_pos);
auto func_name = func_path.substr(dot_pos + 1);
if (!loaded_modules_.count(module_name)) {
throw std::runtime_error("Module not loaded: " + module_name);
}
auto& module = loaded_modules_[module_name];
sol::function func = module[func_name];
if (!func.valid()) {
throw std::runtime_error("Function not found: " + func_path);
}
sol::protected_function_result result;
if (arg == sol::nil) {
result = func();
} else {
result = func(arg);
}
if (!result.valid()) {
sol::error err = result;
throw std::runtime_error(err.what());
}
return convert_result<T>(result);
}
// Specializations for different return types
template<typename T>
T convert_result(sol::protected_function_result& result);
};
// Specialization for GeneratedFiles
template<>
inline GeneratedFiles LuaRunner::convert_result<GeneratedFiles>(sol::protected_function_result& result) {
GeneratedFiles files;
sol::table lua_files = result;
for (auto& pair : lua_files) {
sol::table file_table = pair.second;
GeneratedFile file;
file.path = file_table["path"].get<std::string>();
file.content = file_table["content"].get<std::string>();
files.push_back(std::move(file));
}
return files;
}
// Specialization for ValidationResult
template<>
inline ValidationResult LuaRunner::convert_result<ValidationResult>(sol::protected_function_result& result) {
ValidationResult validation;
sol::table lua_result = result;
validation.valid = lua_result["valid"].get<bool>();
if (lua_result["errors"].valid()) {
sol::table errors = lua_result["errors"];
for (auto& pair : errors) {
validation.errors.push_back(pair.second.as<std::string>());
}
}
return validation;
}
// Specialization for vector<string>
template<>
inline std::vector<std::string> LuaRunner::convert_result<std::vector<std::string>>(sol::protected_function_result& result) {
std::vector<std::string> vec;
sol::table lua_table = result;
for (auto& pair : lua_table) {
vec.push_back(pair.second.as<std::string>());
}
return vec;
}
} // namespace tools
@@ -0,0 +1,280 @@
/**
* Package Template Generator CLI
*
* C++ CLI that executes Lua scripts to generate MetaBuilder package scaffolding.
*
* Usage:
* package_generator new <package_id> [options]
* package_generator quick <package_id> [options]
* package_generator list-categories
* package_generator validate <config.json>
*
* Examples:
* package_generator new my_package --category tools --min-level 3
* package_generator quick my_widget --dependency
* package_generator new my_forum --with-schema --entities Thread,Post
*/
#include <iostream>
#include <string>
#include <vector>
#include <filesystem>
#include <fstream>
#include <sstream>
#include "lua_runner.hpp"
#include "arg_parser.hpp"
#include "file_writer.hpp"
namespace fs = std::filesystem;
constexpr const char* VERSION = "1.0.0";
constexpr const char* PROGRAM_NAME = "package_generator";
void print_help() {
std::cout << R"(
Package Template Generator v)" << VERSION << R"(
USAGE:
)" << PROGRAM_NAME << R"( <command> [options]
COMMANDS:
new <package_id> Create a new package with full scaffolding
quick <package_id> Create a minimal package quickly
list-categories List available package categories
validate <file> Validate a package config JSON file
help Show this help message
OPTIONS:
--name <name> Display name (default: derived from package_id)
--description <desc> Package description
--category <cat> Package category (default: ui)
--min-level <n> Minimum access level 0-6 (default: 2)
--primary Package can own routes (default)
--dependency Package is dependency-only
--with-schema Include database schema scaffolding
--entities <e1,e2> Entity names for schema (comma-separated)
--with-components Include component scaffolding
--components <c1,c2> Component names (comma-separated)
--deps <d1,d2> Package dependencies (comma-separated)
--output <dir> Output directory (default: ./packages)
--dry-run Preview files without writing
CATEGORIES:
ui, editors, tools, social, media, gaming,
admin, config, core, demo, development, managers
EXAMPLES:
)" << PROGRAM_NAME << R"( new my_package
)" << PROGRAM_NAME << R"( new my_forum --with-schema --entities Thread,Post,Reply
)" << PROGRAM_NAME << R"( quick my_widget --dependency --category ui
)" << PROGRAM_NAME << R"( new dashboard --with-components --components StatCard,Chart
)";
}
void print_version() {
std::cout << PROGRAM_NAME << " version " << VERSION << std::endl;
}
int cmd_new(const tools::ArgParser& args, tools::LuaRunner& lua) {
auto package_id = args.get_positional(1);
if (package_id.empty()) {
std::cerr << "Error: package_id is required\n";
std::cerr << "Usage: " << PROGRAM_NAME << " new <package_id> [options]\n";
return 1;
}
// Build config table for Lua
tools::LuaConfig config;
config.set("packageId", package_id);
config.set("name", args.get_option("name", ""));
config.set("description", args.get_option("description", ""));
config.set("category", args.get_option("category", "ui"));
config.set("minLevel", args.get_int_option("min-level", 2));
config.set("primary", !args.has_flag("dependency"));
config.set("withSchema", args.has_flag("with-schema"));
config.set("withTests", true);
config.set("withComponents", args.has_flag("with-components"));
// Parse comma-separated lists
config.set_list("entities", args.get_list_option("entities"));
config.set_list("components", args.get_list_option("components"));
config.set_list("dependencies", args.get_list_option("deps"));
// Validate config via Lua
auto validation = lua.call<tools::ValidationResult>("package_template.validate_config", config);
if (!validation.valid) {
std::cerr << "Validation failed:\n";
for (const auto& err : validation.errors) {
std::cerr << " - " << err << "\n";
}
return 1;
}
// Generate files
auto files = lua.call<tools::GeneratedFiles>("package_template.generate", config);
if (args.has_flag("dry-run")) {
std::cout << "Would generate " << files.size() << " files:\n";
for (const auto& file : files) {
std::cout << " " << file.path << " (" << file.content.size() << " bytes)\n";
}
return 0;
}
// Write files
auto output_dir = args.get_option("output", "packages");
auto package_path = fs::path(output_dir) / package_id;
if (fs::exists(package_path)) {
std::cerr << "Error: Package directory already exists: " << package_path << "\n";
return 1;
}
tools::FileWriter writer(package_path);
int written = writer.write_all(files);
std::cout << "\n✅ Package '" << package_id << "' created successfully!\n";
std::cout << " Location: " << package_path << "\n";
std::cout << " Files: " << written << "\n";
std::cout << "\nNext steps:\n";
std::cout << " 1. Review generated files in " << package_path << "\n";
std::cout << " 2. Add package-specific logic to seed/scripts/\n";
std::cout << " 3. Run tests: npm run test:package " << package_id << "\n";
return 0;
}
int cmd_quick(const tools::ArgParser& args, tools::LuaRunner& lua) {
auto package_id = args.get_positional(1);
if (package_id.empty()) {
std::cerr << "Error: package_id is required\n";
return 1;
}
// Minimal config
tools::LuaConfig config;
config.set("packageId", package_id);
config.set("category", args.get_option("category", "ui"));
config.set("minLevel", args.get_int_option("min-level", 2));
config.set("primary", !args.has_flag("dependency"));
config.set("withSchema", false);
config.set("withTests", false);
config.set("withComponents", false);
auto files = lua.call<tools::GeneratedFiles>("package_template.generate_quick", config);
if (args.has_flag("dry-run")) {
std::cout << "Would generate " << files.size() << " files:\n";
for (const auto& file : files) {
std::cout << " " << file.path << "\n";
}
return 0;
}
auto output_dir = args.get_option("output", "packages");
auto package_path = fs::path(output_dir) / package_id;
if (fs::exists(package_path)) {
std::cerr << "Error: Package directory already exists: " << package_path << "\n";
return 1;
}
tools::FileWriter writer(package_path);
writer.write_all(files);
std::cout << "✅ Package '" << package_id << "' created (quick mode)\n";
return 0;
}
int cmd_list_categories(tools::LuaRunner& lua) {
auto categories = lua.call<std::vector<std::string>>("package_template.get_categories");
std::cout << "Available package categories:\n\n";
for (const auto& cat : categories) {
std::cout << " - " << cat << "\n";
}
return 0;
}
int cmd_validate(const tools::ArgParser& args, tools::LuaRunner& lua) {
auto config_file = args.get_positional(1);
if (config_file.empty()) {
std::cerr << "Error: config file path is required\n";
return 1;
}
if (!fs::exists(config_file)) {
std::cerr << "Error: File not found: " << config_file << "\n";
return 1;
}
// Read JSON config
std::ifstream file(config_file);
std::stringstream buffer;
buffer << file.rdbuf();
auto config = tools::LuaConfig::from_json(buffer.str());
auto validation = lua.call<tools::ValidationResult>("package_template.validate_config", config);
if (validation.valid) {
std::cout << "✅ Configuration is valid\n";
return 0;
} else {
std::cout << "❌ Configuration has errors:\n";
for (const auto& err : validation.errors) {
std::cout << " - " << err << "\n";
}
return 1;
}
}
int main(int argc, char* argv[]) {
tools::ArgParser args(argc, argv);
if (args.has_flag("help") || args.has_flag("h") || argc < 2) {
print_help();
return 0;
}
if (args.has_flag("version") || args.has_flag("v")) {
print_version();
return 0;
}
auto command = args.get_positional(0);
// Initialize Lua runner with package scripts path
auto scripts_path = fs::current_path() / "packages" / "codegen_studio" / "seed" / "scripts";
if (!fs::exists(scripts_path)) {
// Try relative to executable
auto exe_path = fs::path(argv[0]).parent_path();
scripts_path = exe_path / ".." / ".." / "packages" / "codegen_studio" / "seed" / "scripts";
}
tools::LuaRunner lua(scripts_path);
if (!lua.load_module("package_template")) {
std::cerr << "Error: Failed to load package_template module\n";
std::cerr << "Looked in: " << scripts_path << "\n";
return 1;
}
if (command == "new") {
return cmd_new(args, lua);
} else if (command == "quick") {
return cmd_quick(args, lua);
} else if (command == "list-categories") {
return cmd_list_categories(lua);
} else if (command == "validate") {
return cmd_validate(args, lua);
} else if (command == "help") {
print_help();
return 0;
} else {
std::cerr << "Unknown command: " << command << "\n";
std::cerr << "Run '" << PROGRAM_NAME << " help' for usage\n";
return 1;
}
}