| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- using GLib;
- namespace Spry.Cli {
- /**
- * `spry` — the Spry application CLI.
- *
- * Scaffolds Statum + Spry applications (`spry new`, including their USM
- * packaging), grows them with app-owned page/action/resource/auth
- * templates (`spry add …`), maintains the static key material in
- * `web-config.json` (`spry keys`), and ships them as container images
- * through USM (`spry deploy`). Edits to generated projects happen only
- * within `// spry:*-begin/end` markers in `src/main.vala` and
- * `# spry:*-begin/end` markers in `meson.build`, so `add` commands are
- * idempotent and never reformat surrounding user code.
- */
- public class SpryCli : Object {
- private static string? tools_dir = null;
- private static string? libdir = null;
- private static bool force = false;
- private static string? route = null;
- private static string? out_path = null;
- private static string? usm = null;
- private static string? deploy_exec = null;
- private static string? deploy_base = null;
- [CCode (array_length = false, null_terminated = true)]
- private static string[] deploy_repositories = null;
- private static string? deploy_installer_url = null;
- private static bool deploy_no_build = false;
- private static bool deploy_verbose = false;
- private static int dev_port = 8080;
- private static bool dev_fresh = false;
- private static bool dev_no_run = false;
- private const OptionEntry[] global_options = {
- { "tools-dir", '\0', 0, OptionArg.STRING, ref tools_dir, "Directory containing the statum-* tools (default: search PATH, then ~/.local/bin)", "DIR" },
- { "libdir", '\0', 0, OptionArg.STRING, ref libdir, "Library directory prepended to spawned tools' LD_LIBRARY_PATH/PKG_CONFIG_PATH (default: pkg-config spry-0.2 libdir)", "DIR" },
- { null }
- };
- private const OptionEntry[] new_options = {
- { "force", 'f', 0, OptionArg.NONE, ref force, "Overwrite generated files when the target directory already exists", null },
- { null }
- };
- private const OptionEntry[] page_options = {
- { "route", 'r', 0, OptionArg.STRING, ref route, "Page route (default: /<name>)", "PATH" },
- { null }
- };
- private const OptionEntry[] keys_options = {
- { "out", 'o', 0, OptionArg.FILENAME, ref out_path, "Configuration file to merge into (default: web-config.json)", "FILE" },
- { null }
- };
- private const OptionEntry[] deploy_options = {
- { "usm", '\0', 0, OptionArg.FILENAME, ref usm, "usm binary to delegate to (default: search PATH)", "FILE" },
- { "exec", '\0', 0, OptionArg.STRING, ref deploy_exec, "Container command (default: \"<app> 8080\")", "CMD" },
- { "base", '\0', 0, OptionArg.STRING, ref deploy_base, "Base image (default: usm's fedora:43)", "IMAGE" },
- { "repository", '\0', 0, OptionArg.STRING_ARRAY, ref deploy_repositories, "USM repository (.usmr) to resolve from; repeatable (default: the machine's configured repositories)", "FILE" },
- { "installer-url", '\0', 0, OptionArg.STRING, ref deploy_installer_url, "USM installer source override (file://… for local testing)", "URL" },
- { "no-build", '\0', 0, OptionArg.NONE, ref deploy_no_build, "Only generate the deploy context; do not build the image", null },
- { "verbose", 'v', 0, OptionArg.NONE, ref deploy_verbose, "Stream package script output to the terminal during the image build", null },
- { null }
- };
- private const OptionEntry[] dev_options = {
- { "port", 'p', 0, OptionArg.INT, ref dev_port, "Port to run the application on (default: 8080)", "N" },
- { "fresh", '\0', 0, OptionArg.NONE, ref dev_fresh, "Delete the SQLite database before starting", null },
- { "no-run", '\0', 0, OptionArg.NONE, ref dev_no_run, "Build once and exit; do not run or watch", null },
- { null }
- };
- private const string MESON_PAGES_BEGIN = "# spry:pages-begin";
- private const string MESON_PAGES_END = "# spry:pages-end";
- private const string MESON_RESOURCES_BEGIN = "# spry:resources-begin";
- private const string MESON_RESOURCES_END = "# spry:resources-end";
- private const string MESON_SOURCES_BEGIN = "# spry:sources-begin";
- private const string MESON_SOURCES_END = "# spry:sources-end";
- private const string MESON_GENERATED_BEGIN = "# spry:generated-begin";
- private const string MESON_GENERATED_END = "# spry:generated-end";
- private const string VALA_PAGES_BEGIN = "// spry:pages-begin";
- private const string VALA_PAGES_END = "// spry:pages-end";
- private const string VALA_ACTIONS_BEGIN = "// spry:actions-begin";
- private const string VALA_ACTIONS_END = "// spry:actions-end";
- private const string VALA_RESOURCES_BEGIN = "// spry:resources-begin";
- private const string VALA_RESOURCES_END = "// spry:resources-end";
- private const string VALA_AUTH_BEGIN = "// spry:auth-begin";
- private const string VALA_AUTH_END = "// spry:auth-end";
- private const string NAV_BEGIN = "<!-- spry:nav-begin -->";
- private const string NAV_END = "<!-- spry:nav-end -->";
- private const string NAV_AUTH_BEGIN = "<!-- spry:nav-auth-begin -->";
- private const string NAV_AUTH_END = "<!-- spry:nav-auth-end -->";
- public static int main(string[] args) {
- if (args.length < 2) {
- print_usage();
- return 1;
- }
- var command = args[1];
- if (command == "--help" || command == "-h" || command == "help") {
- print_usage();
- return 0;
- }
- try {
- switch (command) {
- case "new":
- return cmd_new(slice(args, 1));
- case "add":
- return cmd_add(slice(args, 1));
- case "keys":
- return cmd_keys(slice(args, 1));
- case "deploy":
- return cmd_deploy(slice(args, 1));
- case "dev":
- return cmd_dev(slice(args, 1));
- default:
- stderr.printf("Error: Unknown command '%s'.\n", command);
- print_usage();
- return 1;
- }
- } catch (OptionError e) {
- stderr.printf("Error: %s\n", e.message);
- return 1;
- } catch (Error e) {
- stderr.printf("Error: %s\n", e.message);
- return 1;
- }
- }
- private static void print_usage() {
- stdout.printf(
- "Usage:\n" +
- " spry <command> [options]\n" +
- "\n" +
- "Commands:\n" +
- " new <name> Scaffold a new Statum + Spry application\n" +
- " add page <name> [--route PATH] Add a page (HTML + entrypoint + wiring)\n" +
- " add action <name> Add a StatumAction skeleton\n" +
- " add resource <file> Embed a static resource (statum-mkres)\n" +
- " add login Add the login page (Spry.Actions.LoginAction)\n" +
- " add register Add the registration page\n" +
- " add user-management Add the admin user-management page\n" +
- " keys [--out FILE] Generate/merge static keys into web-config.json\n" +
- " deploy [--exec CMD] [--base I] Build the USM container image (usm manifest\n" +
- " deploy; --repository/--installer-url/\n" +
- " --no-build pass through)\n" +
- " dev [--port N] [--fresh] Build, run and watch: rebuild + restart on save\n" +
- "\n" +
- "Options:\n" +
- " --tools-dir DIR Directory containing the statum-* tools (default: PATH)\n" +
- " --libdir DIR Library dir for spawned tools' LD_LIBRARY_PATH\n" +
- " (default: pkg-config spry-0.2 libdir)\n" +
- " --help Show this help\n");
- }
- private static void print_add_usage() {
- stdout.printf(
- "Usage:\n" +
- " spry add page <name> [--route /path]\n" +
- " spry add action <name>\n" +
- " spry add resource <file>\n" +
- " spry add login\n" +
- " spry add register\n" +
- " spry add user-management\n");
- }
- // ----------------------------------------------------------------------
- // spry new
- // ----------------------------------------------------------------------
- private static int cmd_new(string[] args) throws Error, OptionError {
- parse(args, "<name> - scaffold a new Statum + Spry application", new_options);
- if (args.length < 2) {
- stderr.printf("Error: No application name specified.\n");
- return 1;
- }
- var name = args[1].down();
- if (!Generator.valid_name(name)) {
- stderr.printf("Error: '%s' is not a valid application name (letters, digits, '-' and '_', starting with a letter).\n", name);
- return 1;
- }
- var app_dir = Path.build_filename(Environment.get_current_dir(), name);
- if (FileUtils.test(app_dir, FileTest.EXISTS) && !force) {
- stderr.printf("Error: %s already exists (use --force to overwrite its generated files).\n", app_dir);
- return 1;
- }
- var vars = base_vars(name);
- write_app_file(app_dir, "meson.build", Generator.fill(Templates.PROJECT_MESON, vars));
- write_app_file(app_dir, "src/main.vala", Generator.fill(Templates.APP_MAIN, vars));
- write_app_file(app_dir, "src/pages/main.html", Generator.fill(Templates.LAYOUT, vars));
- write_app_file(app_dir, "src/pages/home.html", Generator.fill(Templates.HOME, vars));
- write_app_file(app_dir, "src/entrypoints/HomeEntrypoint.vala", Generator.fill(Templates.HOME_ENTRYPOINT, vars));
- write_app_file(app_dir, ".gitignore", Generator.fill(Templates.GITIGNORE, vars));
- write_app_file(app_dir, "README.md", Generator.fill(Templates.README, vars));
- // USM packaging: the manifest + scripts that let `spry deploy`
- // (usm manifest deploy) build and install the app in-container
- write_app_file(app_dir, "MANIFEST.usm", Generator.fill(Templates.MANIFEST_USM, vars));
- write_app_file(app_dir, "usm-scripts/build.sh", Templates.USM_BUILD_SH, 0755);
- write_app_file(app_dir, "usm-scripts/install.sh", Templates.USM_INSTALL_SH, 0755);
- write_app_file(app_dir, ".usmignore", Templates.USMIGNORE);
- var config_path = Path.build_filename(app_dir, "web-config.json");
- try {
- Keys.run(config_path, tools_dir, libdir);
- } catch (Error e) {
- stderr.printf("Warning: %s — wrote an empty config; run `spry keys` later.\n", e.message);
- Generator.write_file(config_path, "{\n \"statum\": {\n }\n}\n");
- }
- stdout.printf("Created %s\n", app_dir);
- stdout.printf("Next:\n cd %s\n spry dev\n", name);
- return 0;
- }
- // ----------------------------------------------------------------------
- // spry add …
- // ----------------------------------------------------------------------
- private static int cmd_add(string[] args) throws Error, OptionError {
- if (args.length < 2) {
- print_add_usage();
- return 1;
- }
- var what = args[1];
- var rest = slice(args, 1);
- switch (what) {
- case "page":
- return cmd_add_page(rest);
- case "action":
- return cmd_add_action(rest);
- case "resource":
- return cmd_add_resource(rest);
- case "login":
- return cmd_add_login(rest);
- case "register":
- return cmd_add_register(rest);
- case "user-management":
- return cmd_add_user_management(rest);
- default:
- stderr.printf("Error: Unknown add command '%s'.\n", what);
- print_add_usage();
- return 1;
- }
- }
- private static int cmd_add_page(string[] args) throws Error, OptionError {
- parse(args, "<name> - add a page (HTML + entrypoint + wiring)", page_options);
- if (args.length < 2) {
- stderr.printf("Error: No page name specified.\n");
- return 1;
- }
- var name = normalise(args[1]);
- if (!Generator.valid_name(name)) {
- stderr.printf("Error: '%s' is not a valid page name.\n", name);
- return 1;
- }
- var route_path = route != null ? (!)route : "/" + name;
- if (!route_path.has_prefix("/")) {
- route_path = "/" + route_path;
- }
- var app = require_app();
- add_page(app, name, Generator.pascal_case(name), route_path,
- Generator.fill(Templates.PAGE, page_vars(app, name, route_path)),
- Generator.fill(Templates.PAGE_ENTRYPOINT, page_vars(app, name, route_path)));
- stdout.printf("Added page %s at %s\n", name, route_path);
- return 0;
- }
- private static int cmd_add_action(string[] args) throws Error, OptionError {
- parse(args, "<name> - add a StatumAction skeleton", null);
- if (args.length < 2) {
- stderr.printf("Error: No action name specified.\n");
- return 1;
- }
- var name = normalise(args[1]);
- if (!Generator.valid_name(name)) {
- stderr.printf("Error: '%s' is not a valid action name.\n", name);
- return 1;
- }
- var cls = Generator.pascal_case(name);
- var app = require_app();
- var vars = app.vars;
- vars.set("CLASS", cls);
- write_new_app_file(app.dir, "src/actions/%sAction.vala".printf(cls),
- Generator.fill(Templates.ACTION, vars));
- edit_meson(app, "'src/actions/%sAction.vala',".printf(cls), MESON_SOURCES_BEGIN, MESON_SOURCES_END);
- edit_main(app, "statum.action<%sAction>();".printf(cls), VALA_ACTIONS_BEGIN, VALA_ACTIONS_END);
- stdout.printf("Added action %s\n", cls);
- return 0;
- }
- private static int cmd_add_resource(string[] args) throws Error, OptionError {
- parse(args, "<file> - embed a static resource", null);
- if (args.length < 2) {
- stderr.printf("Error: No resource file specified.\n");
- return 1;
- }
- var source = args[1];
- if (!FileUtils.test(source, FileTest.EXISTS)) {
- stderr.printf("Error: %s does not exist.\n", source);
- return 1;
- }
- var file_name = Path.get_basename(source);
- var stem = file_name.contains(".") ? file_name.substring(0, file_name.last_index_of(".")) : file_name;
- var cls = Generator.pascal_case(stem) + "Resource";
- var var_name = Generator.snake_case(stem) + "_resource";
- var content_type = content_type_for(file_name);
- var app = require_app();
- var resource_path = Path.build_filename(app.dir, "src", "resources", file_name);
- if (!FileUtils.test(resource_path, FileTest.EXISTS)) {
- Generator.write_file(resource_path, Generator.read_file(source));
- }
- var command = "[statum_mkres, '-o', '@OUTPUT@', '-n', '%s', '--class-name', '%s'".printf(file_name, cls);
- if (content_type != null) {
- command += ", '-c', '%s'".printf((!)content_type);
- }
- command += ", '@INPUT@']";
- var target = "%s = custom_target('%s-resource',\n".printf(var_name, Generator.snake_case(stem))
- + " input: 'src/resources/%s',\n".printf(file_name)
- + " output: '%s.vala',\n".printf(cls)
- + " command: %s\n".printf(command)
- + ")";
- edit_meson(app, target, MESON_RESOURCES_BEGIN, MESON_RESOURCES_END);
- edit_meson(app, "%s,".printf(var_name), MESON_GENERATED_BEGIN, MESON_GENERATED_END);
- edit_main(app, "statum.add_resource<%s>();".printf(cls), VALA_RESOURCES_BEGIN, VALA_RESOURCES_END);
- stdout.printf("Added resource %s as %s\n", source, cls);
- return 0;
- }
- private static int cmd_add_login(string[] args) throws Error, OptionError {
- parse(args, "- add the login page", null);
- var app = require_app();
- add_page(app, "login", "Login", "/login",
- Generator.fill(Templates.LOGIN, app.vars),
- Generator.fill(Templates.LOGIN_ENTRYPOINT, app.vars));
- // Keep Spry.Actions registrations in sync with
- // SpryAuth.register_actions (Spry/src/Auth.vala), the canonical
- // list of shipped actions.
- edit_main(app, "statum.action<Spry.Actions.LoginAction>();", VALA_AUTH_BEGIN, VALA_AUTH_END);
- edit_main(app, "statum.action<Spry.Actions.LogoutAction>();", VALA_AUTH_BEGIN, VALA_AUTH_END);
- edit_layout(app, "<a href=\"/login\" stm-if=\"!auth\">Login</a>", NAV_BEGIN, NAV_END);
- edit_layout(app, "<button type=\"button\" stm-action=\"auth.logout\">Log out</button>", NAV_AUTH_BEGIN, NAV_AUTH_END);
- Generator.append_section(Path.build_filename(app.dir, "README.md"),
- "## Guarding pages", Generator.fill(Templates.LOGIN_README, app.vars));
- stdout.printf("Added login page at /login\n");
- return 0;
- }
- private static int cmd_add_register(string[] args) throws Error, OptionError {
- parse(args, "- add the registration page", null);
- var app = require_app();
- add_page(app, "register", "Register", "/register",
- Generator.fill(Templates.REGISTER, app.vars),
- Generator.fill(Templates.REGISTER_ENTRYPOINT, app.vars));
- edit_main(app, "statum.action<Spry.Actions.RegisterAction>();", VALA_AUTH_BEGIN, VALA_AUTH_END);
- edit_layout(app, "<a href=\"/register\" stm-if=\"!auth\">Register</a>", NAV_BEGIN, NAV_END);
- stdout.printf("Added register page at /register\n");
- return 0;
- }
- private static int cmd_add_user_management(string[] args) throws Error, OptionError {
- parse(args, "- add the admin user-management page", null);
- var app = require_app();
- add_page(app, "user-management", "UserManagement", "/users",
- Generator.fill(Templates.USER_MANAGEMENT, app.vars),
- Generator.fill(Templates.USER_MANAGEMENT_ENTRYPOINT, app.vars));
- edit_main(app, "statum.action<Spry.Actions.SetUserEnabledAction>();", VALA_AUTH_BEGIN, VALA_AUTH_END);
- edit_main(app, "statum.action<Spry.Actions.GrantPermissionAction>();", VALA_AUTH_BEGIN, VALA_AUTH_END);
- edit_main(app, "statum.action<Spry.Actions.RevokePermissionAction>();", VALA_AUTH_BEGIN, VALA_AUTH_END);
- edit_main(app, "statum.action<Spry.Actions.AlterUserAction>();", VALA_AUTH_BEGIN, VALA_AUTH_END);
- edit_main(app, "statum.action<Spry.Actions.DeleteUserAction>();", VALA_AUTH_BEGIN, VALA_AUTH_END);
- edit_layout(app, "<a href=\"/users\" stm-if=\"auth && (auth.permissions.indexOf('admin') !== -1 || auth.permissions.indexOf('*') !== -1)\">Users</a>", NAV_BEGIN, NAV_END);
- stdout.printf("Added user-management page at /users\n");
- return 0;
- }
- // ----------------------------------------------------------------------
- // spry dev
- // ----------------------------------------------------------------------
- private static int cmd_dev(string[] args) throws Error, OptionError {
- parse(args, "- build, run and watch the application", dev_options);
- var app = require_app();
- return Dev.run(app.dir, app.name, dev_port, dev_fresh, dev_no_run, libdir);
- }
- // ----------------------------------------------------------------------
- // spry keys / spry deploy
- // ----------------------------------------------------------------------
- private static int cmd_keys(string[] args) throws Error, OptionError {
- parse(args, "- generate and merge static keys into web-config.json", keys_options);
- Keys.run(out_path ?? "web-config.json", tools_dir, libdir);
- return 0;
- }
- private static int cmd_deploy(string[] args) throws Error, OptionError {
- string[] repositories;
- string[] remaining;
- extract_repositories(args, out repositories, out remaining);
- parse(remaining, "- build the USM container image via usm manifest deploy", deploy_options);
- var app = require_app();
- return Deploy.run(app.dir, app.name, usm, deploy_exec, deploy_base,
- repositories, deploy_installer_url, deploy_no_build, deploy_verbose);
- }
- /**
- * Collects repeatable `--repository FILE` / `--repository=FILE`
- * arguments into {@link repositories}, leaving the rest in
- * {@link remaining} so the GLib option context (whose string-array
- * capture is unreliable here) never sees them.
- */
- private static void extract_repositories(string[] args, out string[] repositories, out string[] remaining) {
- var found = new string[0];
- var rest = new string[args.length];
- rest[0] = args[0];
- int out_index = 1;
- for (int i = 1; i < args.length; i++) {
- if (args[i] == "--repository" && i + 1 < args.length) {
- found += args[++i];
- }
- else if (args[i].has_prefix("--repository=")) {
- found += args[i].substring("--repository=".length);
- }
- else {
- rest[out_index++] = args[i];
- }
- }
- rest.length = out_index;
- repositories = found;
- remaining = rest;
- }
- // ----------------------------------------------------------------------
- // Shared helpers
- // ----------------------------------------------------------------------
- /**
- * The surrounding spry application: the current directory, the app
- * name (directory basename) and the namespace its generated pages
- * use (read back from meson.build, falling back to the directory
- * name).
- */
- private class AppContext {
- public string dir;
- public string name;
- public string ns;
- public HashTable<string, string> vars;
- public AppContext(string dir, string name, string ns) {
- this.dir = dir;
- this.name = name;
- this.ns = ns;
- this.vars = new HashTable<string, string>(str_hash, str_equal);
- this.vars.insert("APP_NAME", name);
- this.vars.insert("NS", ns);
- }
- }
- private static AppContext require_app() throws Error {
- var cwd = Environment.get_current_dir();
- if (!FileUtils.test(Path.build_filename(cwd, "meson.build"), FileTest.EXISTS)
- || !FileUtils.test(Path.build_filename(cwd, "src", "main.vala"), FileTest.EXISTS)) {
- throw Generator.cli_error("not a spry application directory (run from the project root)");
- }
- var name = Path.get_basename(cwd);
- var ns = detect_ns(Generator.read_file(Path.build_filename(cwd, "meson.build")));
- if (ns.length == 0) {
- ns = Generator.pascal_case(name);
- }
- return new AppContext(cwd, name, ns);
- }
- private static string detect_ns(string meson) {
- var marker = "'--ns', '";
- var index = meson.index_of(marker);
- if (index < 0) {
- return "";
- }
- var start = index + marker.length;
- var stop = meson.index_of("'", start);
- return stop > start ? meson.substring(start, stop - start) : "";
- }
- private static HashTable<string, string> base_vars(string name) {
- var vars = new HashTable<string, string>(str_hash, str_equal);
- vars.insert("APP_NAME", name);
- vars.insert("NS", Generator.pascal_case(name));
- return vars;
- }
- private static HashTable<string, string> page_vars(AppContext app, string name, string route_path) {
- app.vars.set("CLASS", Generator.pascal_case(name));
- app.vars.set("ROUTE", route_path);
- return app.vars;
- }
- private static void add_page(AppContext app, string name, string cls, string route_path,
- string html, string entrypoint) throws Error {
- write_new_app_file(app.dir, "src/pages/%s.html".printf(name), html);
- write_new_app_file(app.dir, "src/entrypoints/%sEntrypoint.vala".printf(cls), entrypoint);
- var var_name = Generator.snake_case(name) + "_page";
- var target = "%s = custom_target('%s-page',\n".printf(var_name, Generator.snake_case(name))
- + " input: 'src/pages/%s.html',\n".printf(name)
- + " output: '%sPage.vala',\n".printf(cls)
- + " command: [statum_mkpstm, '-o', '@OUTPUT@', '-n', '%sPage', '--ns', '%s', '@INPUT@'],\n".printf(cls, app.ns)
- + " depend_files: files('src/pages/main.html')\n"
- + ")";
- edit_meson(app, target, MESON_PAGES_BEGIN, MESON_PAGES_END);
- edit_meson(app, "'src/entrypoints/%sEntrypoint.vala',".printf(cls), MESON_SOURCES_BEGIN, MESON_SOURCES_END);
- edit_meson(app, "%s,".printf(var_name), MESON_GENERATED_BEGIN, MESON_GENERATED_END);
- edit_main(app, "statum.add_page<%sPage, %sEntrypoint>();".printf(cls, cls), VALA_PAGES_BEGIN, VALA_PAGES_END);
- }
- private static void edit_meson(AppContext app, string snippet, string begin, string end) throws Error {
- var path = Path.build_filename(app.dir, "meson.build");
- Generator.write_file(path, Generator.insert_marked(Generator.read_file(path), begin, end, snippet));
- }
- private static void edit_main(AppContext app, string snippet, string begin, string end) throws Error {
- var path = Path.build_filename(app.dir, "src", "main.vala");
- Generator.write_file(path, Generator.insert_marked(Generator.read_file(path), begin, end, snippet));
- }
- private static void edit_layout(AppContext app, string snippet, string begin, string end) throws Error {
- var path = Path.build_filename(app.dir, "src", "pages", "main.html");
- Generator.write_file(path, Generator.insert_marked(Generator.read_file(path), begin, end, snippet));
- }
- private static void write_app_file(string app_dir, string relative, string contents, uint32 mode = 0644) throws Error {
- Generator.write_file(Path.build_filename(app_dir, relative), contents, mode);
- }
- /**
- * Writes a scaffolded file only when it does not exist yet, so
- * re-running `add` commands never clobbers user edits.
- */
- private static void write_new_app_file(string app_dir, string relative, string contents) throws Error {
- var path = Path.build_filename(app_dir, relative);
- if (FileUtils.test(path, FileTest.EXISTS)) {
- stdout.printf("%s already exists — left untouched\n", path);
- return;
- }
- Generator.write_file(path, contents);
- }
- private static string normalise(string name) {
- return name.down();
- }
- private static string? content_type_for(string filename) {
- var extension = filename.contains(".") ? filename.substring(filename.last_index_of(".") + 1).down() : "";
- switch (extension) {
- case "css":
- return "text/css";
- case "js":
- case "mjs":
- return "application/javascript";
- case "png":
- return "image/png";
- case "svg":
- return "image/svg+xml";
- case "jpg":
- case "jpeg":
- return "image/jpeg";
- case "ico":
- return "image/x-icon";
- case "woff2":
- return "font/woff2";
- case "woff":
- return "font/woff";
- default:
- return null;
- }
- }
- private static void parse(string[] args, string summary, OptionEntry[]? extra) throws OptionError {
- var context = new OptionContext(summary);
- context.add_main_entries(global_options, null);
- if (extra != null) {
- context.add_main_entries((!)extra, null);
- }
- context.parse(ref args);
- }
- /** Drops `skip` positional words after `args[0]`, keeping `args[0]`. */
- private static string[] slice(string[] args, int skip) {
- var rest = new string[int.max(1, args.length - skip)];
- rest[0] = args[0];
- for (int i = skip + 1; i < args.length; i++) {
- rest[i - skip] = args[i];
- }
- return rest;
- }
- }
- }
|