/* * 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 * Version: 0.1.0 */ #include #include #include #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] \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 \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]; 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) { 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; }