hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-07 20:26:10 +0200
committerhachem <im@hachem.wtf>2026-09-07 20:26:10 +0200
commitb8f982f6b9fc9d430c7132456706059218bc6fc2 (patch)
treeeeacfd744e38ee9c7818174f36de1730028ec5f5
parent6a0143e28f294dc5672404a8bbc1e6dbb0229ba8 (diff)
feat: setup -h -v and command line argument parsing
-rw-r--r--mkproj.c372
1 files changed, 370 insertions, 2 deletions
diff --git a/mkproj.c b/mkproj.c
index 45f759c..903a16c 100644
--- a/mkproj.c
+++ b/mkproj.c
@@ -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;
}