diff options
| author | hachem <im@hachem.wtf> | 2026-09-07 20:26:10 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-07 20:26:10 +0200 |
| commit | b8f982f6b9fc9d430c7132456706059218bc6fc2 (patch) | |
| tree | eeacfd744e38ee9c7818174f36de1730028ec5f5 | |
| parent | 6a0143e28f294dc5672404a8bbc1e6dbb0229ba8 (diff) | |
feat: setup -h -v and command line argument parsing
| -rw-r--r-- | mkproj.c | 372 |
1 files changed, 370 insertions, 2 deletions
@@ -1,6 +1,374 @@ +/* + * mkproj - dumb project generator. + * + * generates boilerplate for make, cmake, meson or premake5 so i don't + * have to keep writing build files for small dumb projects. + * + * Author: hachem <im@hachem.wtf> + * Version: 0.1.0 + */ + #include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define mkproj_program "mkproj" +#define mkproj_version "0.1.0" +#define mkproj_tagline "dumb project generator" + +enum Language +{ + language_c, + language_cpp, + language_objc, + language_count +}; + +enum Build_System +{ + build_make, + build_cmake, + build_meson, + build_premake, + build_system_count +}; + +struct Language_Info +{ + const char *key; + const char *name; + const char *compiler; + const char *extension; + const char *const *standards; + size_t standard_count; + const char *default_standard; +}; + +struct Build_Info +{ + const char *key; + const char *name; +}; + +struct Config +{ + const char *name; + enum Language language; + const char *standard; + enum Build_System build; + int use_git; + int use_intermediate; + int use_clangd_ignore; +}; + +static const char *const c_standards[] = +{ + "c89", + "c99", + "c11", + "c17", + "c23", + "c2y" +}; + +static const char *const cpp_standards[] = +{ + "c++98", + "c++03", + "c++11", + "c++14", + "c++17", + "c++20", + "c++23", + "c++26" +}; + +static const struct Language_Info languages[language_count] = +{ + [language_c] = + { + .key = "c", + .name = "C", + .compiler = "clang", + .extension = "c", + .standards = c_standards, + .standard_count = sizeof c_standards / sizeof c_standards[0], + .default_standard = "c17", + }, + [language_cpp] = + { + .key = "cpp", + .name = "C++", + .compiler = "clang++", + .extension = "cpp", + .standards = cpp_standards, + .standard_count = sizeof cpp_standards / sizeof cpp_standards[0], + .default_standard = "c++23", + }, + [language_objc] = + { + .key = "objc", + .name = "Objective-C", + .compiler = "clang", + .extension = "m", + .standards = c_standards, + .standard_count = sizeof c_standards / sizeof c_standards[0], + .default_standard = "c17", + }, +}; + +static const struct Build_Info build_systems[build_system_count] = +{ + [build_make] = { .key = "make", .name = "Make" }, + [build_cmake] = { .key = "cmake", .name = "CMake" }, + [build_meson] = { .key = "meson", .name = "Meson" }, + [build_premake] = { .key = "premake", .name = "premake5" }, +}; + +static void print_version(void) +{ + (void) printf("%s %s\n", mkproj_program, mkproj_version); +} + +static void print_usage(FILE *stream) +{ + (void) fprintf( + stream, + "%s %s - %s\n" + "\n" + "generates boilerplate for make, cmake, meson or premake5 so i don't\n" + "have to keep writing build files for small dumb projects.\n" + "\n" + "usage: %s [-l lang] [-s std] [-b system] [--no-git] [--intermediate]\n" + " [--no-clangd] <name>\n" + " %s [-v | -h]\n" + "\n" + "options:\n" + " -l, --lang LANG language: c, cpp, objc (default c)\n" + " -s, --std STD language standard (default per language)\n" + " -b, --build SYSTEM build system: make, cmake, meson, premake (default make)\n" + " --no-git don't initialize a git repository\n" + " --intermediate use a separate intermediate object directory\n" + " --no-clangd don't ignore clangd files in .gitignore\n" + " -v, --version print version and bail\n" + " -h, --help print this and bail\n" + "\n" + "by hachem <im@hachem.wtf>\n", + mkproj_program, mkproj_version, mkproj_tagline, + mkproj_program, mkproj_program); +} + +static int find_language(const char *key, enum Language *out) +{ + for (enum Language language = 0; language < language_count; language++) + if (strcmp(languages[language].key, key) == 0) + { + *out = language; + return 1; + } + + return 0; +} + +static int find_build_system(const char *key, enum Build_System *out) +{ + for (enum Build_System build = 0; build < build_system_count; build++) + if (strcmp(build_systems[build].key, key) == 0) + { + *out = build; + return 1; + } + + return 0; +} + +static int valid_standard(enum Language language, const char *standard) +{ + const struct Language_Info *info = &languages[language]; -int main(void) + for (size_t i = 0; i < info->standard_count; i++) + if (strcmp(info->standards[i], standard) == 0) + return 1; + + return 0; +} + +static void error(const char *message, const char *detail) +{ + if (detail != NULL) + (void) fprintf(stderr, "%s: error: %s: %s\n", + mkproj_program, message, detail); + else + (void) fprintf(stderr, "%s: error: %s\n", mkproj_program, message); +} + +/* + * parse argv into config. returns 0 on success, a negative value when the + * program should exit successfully (help/version), or a positive value on a + * usage error. + */ +static int parse_args(int argc, char **argv, struct Config *config) +{ + const char *lang_arg = NULL; + const char *std_arg = NULL; + const char *build_arg = NULL; + const char *name = NULL; + + for (int i = 1; i < argc; i++) + { + const char *arg = argv[i]; + + if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) + { + print_usage(stdout); + return -1; + } + + if (strcmp(arg, "-v") == 0 || strcmp(arg, "--version") == 0) + { + print_version(); + return -1; + } + + if (strcmp(arg, "--no-git") == 0) + { + config->use_git = 0; + continue; + } + + if (strcmp(arg, "--intermediate") == 0) + { + config->use_intermediate = 1; + continue; + } + + if (strcmp(arg, "--no-clangd") == 0) + { + config->use_clangd_ignore = 0; + continue; + } + + if (strcmp(arg, "-l") == 0 || strcmp(arg, "--lang") == 0) + { + if (++i >= argc) + { + error("missing argument for", arg); + return 1; + } + + lang_arg = argv[i]; + continue; + } + + if (strcmp(arg, "-s") == 0 || strcmp(arg, "--std") == 0) + { + if (++i >= argc) + { + error("missing argument for", arg); + return 1; + } + + std_arg = argv[i]; + continue; + } + + if (strcmp(arg, "-b") == 0 || strcmp(arg, "--build") == 0) + { + if (++i >= argc) + { + error("missing argument for", arg); + return 1; + } + + build_arg = argv[i]; + continue; + } + + if (arg[0] == '-' && arg[1] != '\0') + { + error("unknown option", arg); + return 1; + } + + if (name != NULL) + { + error("unexpected extra argument", arg); + return 1; + } + + name = arg; + } + + if (name == NULL) + { + error("missing project name", NULL); + return 1; + } + + config->name = name; + + if (lang_arg != NULL && !find_language(lang_arg, &config->language)) + { + error("unknown language", lang_arg); + return 1; + } + + if (build_arg != NULL && !find_build_system(build_arg, &config->build)) + { + error("unknown build system", build_arg); + return 1; + } + + if (std_arg != NULL) + { + if (!valid_standard(config->language, std_arg)) + { + error("invalid standard for language", std_arg); + return 1; + } + + config->standard = std_arg; + } + else + { + config->standard = languages[config->language].default_standard; + } + + return 0; +} + +int main(int argc, char **argv) { - (void) puts("type shi"); + struct Config config = + { + .name = NULL, + .language = language_c, + .standard = NULL, + .build = build_make, + .use_git = 1, + .use_intermediate = 0, + .use_clangd_ignore = 1, + }; + + int status = parse_args(argc, argv, &config); + + if (status < 0) + return EXIT_SUCCESS; + + if (status > 0) + { + print_usage(stderr); + return EXIT_FAILURE; + } + + /* TODO: generation. for now, echo the resolved config. */ + (void) printf("name: %s\n", config.name); + (void) printf("language: %s\n", languages[config.language].name); + (void) printf("standard: %s\n", config.standard); + (void) printf("build: %s\n", build_systems[config.build].name); + (void) printf("git: %s\n", config.use_git ? "yes" : "no"); + (void) printf("interm.: %s\n", config.use_intermediate ? "yes" : "no"); + (void) printf("clangd: %s\n", config.use_clangd_ignore ? "yes" : "no"); + + return EXIT_SUCCESS; } |
