This commit is contained in:
2025-08-10 14:17:24 -04:00
parent 0cecfa4c97
commit 13cb187d53
15 changed files with 489 additions and 488 deletions

263
core/contents.h Normal file
View File

@@ -0,0 +1,263 @@
/* Copyright (C) vx_clutch
*
* This file is part of yait
*
* This project and file is licenced under the BSD-3-Clause licence.
* <https://opensource.org/license/bsd-3-clause>
*/
// clang-format off
#ifndef CONTENTS_H
#define CONTENTS_H
#define line(l) l "\n"
/* README template */
char *readme_template =
line ("%s ( concise description )")
line ()
line ("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor")
line ("incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis")
line ("nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.")
line ("Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu")
line ("fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in")
line ("culpa qui officia deserunt mollit anim id est laborum.");
/* configure script template */
char *configure_template =
line ("#!/bin/sh")
line ()
line ("usage() {")
line ("cat <<EOF")
line ("Usage: $0 [OPTION]... [VAR=VALUE]...")
line ()
line ("To assign environment variables (e.g., CC, CFLAGS...), specify them as")
line ("VAR=VALUE.")
line ()
line (" CC C compiler command [detected]")
line (" CFLAGS C compiler flags [-g, ...]")
line ()
line ("EOF")
line ("exit 0")
line ("}")
line ()
line ("echo () { printf \"%s\\n\" \"$*\" ; }")
line ("cmdexists () { type \"$1\" >/dev/null 2>&1 ; }")
line ("trycc () { test -z \"$CC\" && cmdexists \"$1\" && CC=$1 ; }")
line ()
line ("prefix=/usr/bin/")
line ("CFLAGS=\"-Wall -Wextra -O2\"")
line ("LDFLAGS=")
line ("CC=")
line ()
line ("for arg ; do")
line ("case \"$arg\" in")
line ("--help|h) usage ;;")
line ("CFLAGS=*) CFLAGS=${arg#*=} ;;")
line ("LDFLAGS=*) LDFLAGS=${arg#*=} ;;")
line ("esac")
line ("done")
line ()
line ("printf \"checking for C compiler... \"")
line ("%s")
line ("printf \"%s\\n\" \"$CC\"")
line ()
line ("printf \"checking weather C compiler works... \"")
line ("status=\"fail\"")
line ("tmpc=\"$(mktemp -d)/test.c\"")
line ("echo \"typedef int x;\" > \"$tmpc\"")
line ("if output=$($CC $CFLAGS -c -o /dev/null \"$tmpc\" 2>&1) ; then")
line ("printf \"yes\\n\"")
line ("else")
line ("printf \"no; %s\\n\" \"$output\"")
line ("exit 1")
line ("fi")
line ()
line ("printf \"creating config.mak... \"")
line ("printf \"PREFIX=%s\\n\" \"$prefix\" > config.mak")
line ("printf \"CFLAGS=%s\\n\" \"$CFLAGS\" >> config.mak")
line ("printf \"LDFLAGS=%s\\n\" \"$LDFLAGS\" >> config.mak")
line ("printf \"CC=%s\\n\" \"$CC\" >> config.mak")
line ("printf \"done\\n\"");
/* Makefile template */
char *makefile_template =
line ("prefix = /usr/bin")
line ()
line ("%s_SRCS := $(shell find . -name '%s/*.c')")
line ("%s_OBJS := $(patsubst ./%%.c,c-out/obj/%%.o,$(%s_SRCS))")
line ()
line ("%s := c-out/bin/%s")
line ()
line ("-include config.mak")
line ()
line ("ifeq ($(wildcard config.mak),)")
line ("all:")
line ("\t@echo \"File config.mak not found, run configure\"")
line ("\t@exit 1")
line ("else")
line ()
line ("all: build $(%s)")
line ()
line ("build:")
line ("\tmkdir -p c-out/bin")
line ("\tmkdir -p c-out/obj")
line ()
line ("c-out/obj/%%.o: %%.c")
line ("\t$(CC) $(CFLAGS) -c $< -o $@")
line ()
line ("$(%s): $(%s_OBJS)")
line ("\t$(CC) $(CFLAGS) -DCOMMIT=$(shell git rev-list --count --all 2>/dev/null || echo 0) $^ -o $@")
line ()
line ("endif")
line ()
line ("install:")
line ("\t@echo \"NOT IMPL\"")
line ("\texit 1")
line ()
line ("uninstall:")
line ("\t@echo \"NOT IMPL\"")
line ("\texit 1")
line ()
line ("clean:")
line ("\trm -rf c-out")
line ()
line ("dist-clean: clean")
line ("\trm -f config.mak")
line ()
line (".PHONY: all clean dist-clean install uninstall");
/* .clang-format template */
char *clang_format_template =
line ("BasedOnStyle: GNU");
/* BSD 3-Clause License template */
char *bsd3_license_template =
line ("BSD 3-Clause License")
line ()
line ("Copyright (c) %d, %s")
line ()
line ("Redistribution and use in source and binary forms, with or without")
line ("modification, are permitted provided that the following conditions are met:")
line ()
line ("1. Redistributions of source code must retain the above copyright notice, this")
line (" list of conditions and the following disclaimer.")
line ()
line ("2. Redistributions in binary form must reproduce the above copyright notice,")
line (" this list of conditions and the following disclaimer in the documentation")
line (" and/or other materials provided with the distribution.")
line ()
line ("3. Neither the name of the copyright holder nor the names of its")
line (" contributors may be used to endorse or promote products derived from")
line (" this software without specific prior written permission.")
line ()
line ("THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"")
line ("AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE")
line ("IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE")
line ("DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE")
line ("FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL")
line ("DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR")
line ("SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER")
line ("CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,")
line ("OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE")
line ("OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.");
/* MIT License template */
char *mit_license_template =
line ("THIS IS THE MIT LICENSE");
/* GPLv3 License template */
char *gplv3_license_template =
line ("THIS IS THE GPLv3 LICENSE");
/* config.h template */
char *config_h_template =
line ("#ifndef CONFIG_H")
line ("#define CONFIG_H")
line ()
line ("/* Program information */")
line ("#define PROGRAM \"%s\"")
line ("#define LICENSE_LINE \"%s\"")
line ("#define AUTHORS \"%s\"")
line ("#define VERSION \"pre-alpha\"")
line ("#define YEAR 2025")
line ()
line ("#define HELP_REQUESTED 2")
line ("#define ERROR_MEMORY_ALLOCATION 3")
line ("#define ERROR_DIRECTORY_CREATION 4")
line ()
line ("#endif");
/* main.c (non-GNU) template */
char *main_c_template =
line ("#include <stdio.h>")
line ()
line ("int main(void) {")
line (" printf(\"%s: Hello %s!\\n\");")
line (" return 0;")
line ("}");
/* main.c (GNU) template */
char *main_c_gnu_template =
line ("#include <stdio.h>")
line ("#include \"standard.h\"")
line ()
line ("void usage(int status) {")
line (" fprintf(stderr, \"Usage: %s [OPTION...]\\n\");")
line (" fprintf(stderr, \" --help\\tdisplay the help text and exit\\n\");")
line (" fprintf(stderr, \" --version\\toutput version information and exit\\n\");")
line ("}")
line ()
line ("int main(int argc, char **argv) {")
line (" parse_standard_options(usage, argc, argv);")
line (" printf(\"%s: Hello %s!\\n\");")
line (" return 0;")
line ("}");
/* standard.c template */
char *standard_c_template =
line ("#include \"standard.h\"")
line ("#include \"../config.h\"")
line ("#include <stdio.h>")
line ("#include <stdlib.h>")
line ("#include <string.h>")
line ()
line ("int")
line ("parse_standard_options (void (*usage) (int), int argc, char **argv)")
line ("{")
line (" for (int i = 1; i < argc; ++i)")
line (" {")
line (" if (strcmp (argv[i], \"--help\") == 0)")
line (" {")
line (" usage (0);")
line (" exit (EXIT_SUCCESS);")
line (" }")
line (" else if (strcmp (argv[i], \"--version\") == 0)")
line (" {")
line (" printf (\"%s %s %d\\nCopyright (C) %d %s.\\n%s\\nThis is free software: you are free to change and redistribute it.\\nThere is NO WARRNTY, to the extent permitted by law.\\n\", PROGRAM, VERSION, COMMIT, YEAR, AUTHORS, LICENSE_line );")
line (" exit (EXIT_SUCCESS);")
line (" }")
line (" }")
line (" return HELP_REQUESTED;")
line ("}");
/* standard.h template */
char *standard_h_template =
line ("#ifndef STANDARD_H")
line ("#define STANDARD_H")
line ()
line ("int parse_standard_options(void (*usage_func)(), int argc, char **argv);")
line ()
line ("#endif");
/* WHATNEXT.md template */
char *what_next_template =
line ("# What next?")
line ("")
line ("## Steps")
line (" - Write a description in the README")
line (" - Write the usage function ( write to stderr )")
line (" - Start writing your program");
#endif
// clang-format on

346
core/create_project.c Normal file
View File

@@ -0,0 +1,346 @@
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include "manifest.h"
#include "contents.h"
#include "yait.h"
#define DEFAULT_USER_NAME "unknown"
#define DEFAULT_PROJECT_NAME "Project"
#define DEFAULT_LICENCE BSD3
#define DEFAULT_GIT_INIT true
#define DEFAULT_CLANG_FORMAT true
/* This is to keep track of how deep we are within
the project tree. This is used in reset_path_ () */
int depth;
/* This macro exist purely because I like how it looks. This should be called
in every function that creates file to ensure they are being created in
right place. */
#define reset_path reset_path_()
int reset_path_()
{
while (depth != 0) {
if (chdir("..") != 0)
return errno;
else
--depth;
}
return 0;
}
int program_exists(const char *prog)
{
char *path = getenv("PATH");
if (!path)
return 1;
char *copy = strdup(path);
if (!copy)
return 1;
char *dir = strtok(copy, ":");
while (dir) {
char buf[4096];
snprintf(buf, sizeof(buf), "%s/%s", dir, prog);
if (access(buf, X_OK) == 0) {
free(copy);
return 0;
}
dir = strtok(NULL, ":");
}
free(copy);
return 1;
}
// TODO(vx-clutch): sanitize the alpha-numeric range
// clang-format off
int sanitize(manifest_t *m)
{
if (!m->project) m->project = DEFAULT_PROJECT_NAME;
if (!m->name) m->name = DEFAULT_USER_NAME;
if (!(m->licence == UNLICENCE)) m->licence = DEFAULT_LICENCE;
m->flag.git = m->flag.git ? true : DEFAULT_GIT_INIT;
m->flag.clang_format = m->flag.clang_format ? true : DEFAULT_CLANG_FORMAT;
m->flag.GNU = m->flag.GNU ? true : DEFAULT_GNU;
if (!m->name) {
struct passwd *pw = getpwuid(getuid());
m->name = (pw && pw->pw_name) ? pw->pw_name : DEFAULT_USER_NAME;
}
return 0;
}
// clang-format on
int create_libraries(manifest_t manifest)
{
int status = 0;
if (!manifest.libraries) {
return status;
}
/* reset_path; */
while (depth != 0) {
if (chdir("..") != 0)
return errno;
else
--depth;
}
for (int i = 0; i < LIB_COUNT_; ++i) {
if (HAS_LIBRARY(manifest.libraries, LIB_RAYLIB)) {
REMOVE_LIBRARY(manifest.libraries, LIB_RAYLIB);
status = system(
"git submodule add -q https://github.com/raysan5/raylib");
} else if (HAS_LIBRARY(manifest.libraries, LIB_NCURSES)) {
REMOVE_LIBRARY(manifest.libraries, LIB_NCURSES);
status = system(
"git submodule add -q https://github.com/mirror/ncurses");
} else if (HAS_LIBRARY(manifest.libraries, LIB_CURL)) {
REMOVE_LIBRARY(manifest.libraries, LIB_CURL);
status = system(
"git submodule add -q https://github.com/raysan5/raylib");
}
}
return status;
}
int create_licence(manifest_t manifest, char **licence_line_buffer)
{
if (manifest.licence == UNLICENCE)
return 0;
/* reset_path; */
while (depth != 0) {
if (chdir("..") != 0)
return errno;
else
--depth;
}
assert(licence_line_buffer != NULL);
switch (manifest.licence) {
case BSD3:
*licence_line_buffer = "Bsd";
printfn("Not impl");
assert(1 == 2);
break;
case GPLv3:
printfn("Not impl");
assert(1 == 2);
break;
case MIT:
printfn("Not impl");
assert(1 == 2);
break;
case UNLICENCE:
default:
printfn("bad logic in create_licence ()");
return 1;
}
return 0;
}
int maybe_create_clang_format(manifest_t manifest)
{
int status;
if (!manifest.flag.clang_format)
return 0;
/* reset_path; */
while (depth != 0) {
if (chdir("..") != 0)
return errno;
else
--depth;
}
status = create_file_with_content(".clang-format",
clang_format_template);
return status;
}
int setup_git(manifest_t manifest)
{
if (!manifest.flag.git) {
return 0;
}
/* reset_path; */
while (depth != 0) {
if (chdir("..") != 0)
return errno;
else
--depth;
}
int status = system("git init --quiet");
if (status) {
printfn("failed on git initialize: %s", strerror(status));
}
return status;
}
int create_makefile(manifest_t manifest)
{
char *makefile_name = strdup(manifest.project);
if (!makefile_name) {
printfn("fatal: out of memory");
return 1;
}
for (char *p = makefile_name; *p; ++p)
if (*p >= 'a' && *p <= 'z')
*p -= 32;
reset_path;
create_file_with_content("Makefile", makefile_template, makefile_name,
makefile_name, makefile_name, makefile_name,
makefile_name, makefile_name, manifest.project,
makefile_name, makefile_name);
free(makefile_name);
return 0;
}
int create_configure(manifest_t manifest)
{
int status = 0;
reset_path;
char *cc;
if (manifest.flag.use_cpp) {
cc = "trycc g++\ntrycc CC\ntrycc clang++\n";
} else {
cc = "trycc gcc\ntrycc cc\ntrycc clang\n";
}
create_file_with_content("configure", configure_template, cc);
status = system("chmod +x configure");
if (status)
printfn("error: %s", strerror(status));
return status;
}
int generate_source_code(manifest_t manifest, char *licence_line)
{
int status, year;
time_t t = time(NULL);
struct tm tm = *localtime(&t);
year = tm.tm_year + 1900;
// XXX(vx-clutch): this segfaults, but why?
// status = create_file_with_content("config.h", manifest.project, licence_line, year);
if (status) {
printfn("failed to create config.h: %s", strerror(status));
return status;
}
status = create_and_enter_directory(manifest.project);
++depth;
if (status) {
printfn("failed to create or enter directory: %s",
strerror(status));
return status;
}
if (manifest.flag.GNU) {
status = create_file_with_content("main.c", main_c_gnu_template,
manifest.project,
manifest.name);
} else {
status = create_file_with_content("main.c", main_c_template,
manifest.project,
manifest.name);
}
return status;
}
int create_project(manifest_t manifest)
{
int status;
status = sanitize(&manifest);
if (status) {
printfn("failed to sanitize format: %s", strerror(status));
return status;
}
depth = 0;
if (strcmp(manifest.path, ".") != 0) {
status = create_and_enter_directory(manifest.project);
if (status) {
printfn("failed to create or enter directory: %s",
strerror(status));
return status;
}
}
status = create_makefile(manifest);
if (status) {
printfn("failed to create Makefile: %s", strerror(status));
return status;
}
status = create_configure(manifest);
if (status) {
printfn("failed to create configure: %s", strerror(status));
return status;
}
status = setup_git(manifest);
if (status) {
printfn("warning: git initialization failed: %s",
strerror(status));
}
status = maybe_create_clang_format(manifest);
if (status) {
printfn("warning: clang-format setup failed: %s",
strerror(status));
}
// TODO(vx-clutch): make this smarter--or not ( macro ).
char *licence_line = malloc(sizeof(char) * 1024);
if (!licence_line) {
printfn("failed to create memory for licence line: %s",
strerror(status));
return status;
}
status = create_licence(manifest, &licence_line);
if (status) {
printfn("failed to get libraries: %s", strerror(status));
return status;
}
// TODO(vx-clutch): Take in licence line and put it into standard.c
status = generate_source_code(manifest, licence_line);
if (status) {
printfn("failed to generate source code: %s", strerror(status));
return status;
}
free(licence_line);
status = create_libraries(manifest);
if (status) {
printfn("failed to get libraries: %s", strerror(status));
return status;
}
return 0;
}

View File

@@ -6,13 +6,14 @@
* <https://opensource.org/license/bsd-3-clause>
*/
#include "file.h"
#include "../config.h"
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "yait.h"
int create_file_with_content(char *path, char *format, ...)
{

View File

@@ -1,24 +0,0 @@
/* Copyright (C) vx_clutch
*
* This file is part of yait
*
* This project and file is licenced under the BSD-3-Clause licence.
* <https://opensource.org/license/bsd-3-clause>
*/
#ifndef FILE_H
#define FILE_H
#include <unistd.h>
/* Constants for file operations */
#define DEFAULT_DIR_PERMISSIONS 0755
#define MAX_PATH_LENGTH 1024
int create_and_enter_directory (const char *dirname);
int create_file_with_content (char *path, char *format, ...);
int create_directory (char *format, ...);
#endif

78
core/manifest.h Normal file
View File

@@ -0,0 +1,78 @@
/* Copyright (C) vx_clutch
*
* This file is part of yait
*
* This project and file is licenced under the BSD-3-Clause licence.
* <https://opensource.org/licence/bsd-3-clause>
*/
#ifndef FORMAT_H
#define FORMAT_H
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
typedef enum {
BSD3, /* BSD 3-Clause Licence */
GPLv3, /* GNU General Public Licence v3 */
MIT, /* MIT Licence */
UNLICENCE, /* Unlicence */
LICENCE_HELP, /* Help case */
} licence_t;
/* A bit field is used so that we can accomplish two things. (a) store lots of
libraries without taxing memory; and (b) a dynamic array is not neccescary.
*/
typedef enum {
LIB_NONE = 0, /* No libraries selected */
LIB_RAYLIB = 1 << 0, /* Raylib game library */
LIB_NCURSES = 1 << 1, /* Windows API */
LIB_CURL = 1 << 2, /* cURL library */
LIB_COUNT_, /* Number of Libraries */
LIB_HELP, /* Help case */
} lib_flags_t;
typedef struct {
bool GNU;
bool git;
bool clang_format;
bool use_cpp;
} flags_t;
typedef struct {
licence_t licence; /* Licence type for the project */
char *project; /* Project name */
char *path; /* Path */
char *name; /* Author/creator name ( if not provided infered on sanitize ) */
lib_flags_t libraries; /* Selected libraries (bit field) */
flags_t flag; /* Flags */
} manifest_t;
#define DEFAULT_CLANG_FORMAT true
#define DEFAULT_GIT_INIT true
#define DEFAULT_GNU false
#define DEFAULT_LIBRARIES LIB_NONE
#define DEFAULT_LICENCE BSD3
#define HAS_LIBRARY(libs, lib) ((libs) & (lib))
#define ADD_LIBRARY(libs, lib) ((libs) |= (lib))
#define REMOVE_LIBRARY(libs, lib) ((libs) &= ~(lib))
static lib_flags_t TOLibrary(char *src)
{
if (strcmp(src, "raylib"))
return LIB_RAYLIB;
if (strcmp(src, "ncurse"))
return LIB_NCURSES;
if (strcmp(src, "ncurses"))
return LIB_NCURSES;
if (strcmp(src, "curl"))
return LIB_CURL;
if (strcmp(src, "help"))
return LIB_HELP;
fprintf(stderr, "could not find library %s", src);
return LIB_COUNT_; /* bad case */
}
#endif

View File

@@ -6,7 +6,7 @@
* <https://opensource.org/license/bsd-3-clause>
*/
#include "print.h"
#include "yait.h"
#include <stdarg.h>
#include <stdio.h>

View File

@@ -1,17 +0,0 @@
/* Copyright (C) vx_clutch
*
* This file is part of yait
*
* This project and file is licenced under the BSD-3-Clause licence.
* <https://opensource.org/license/bsd-3-clause>
*/
#ifndef PRINT_H
#define PRINT_H
#include <stdarg.h>
#include <stdio.h>
int printfn (char *format, ...);
#endif

View File

@@ -9,7 +9,8 @@
#ifndef COMMIT
#define COMMIT 0
#endif
#include "standard.h"
#include "yait.h"
#include "../config.h"
#include <stdio.h>
#include <stdlib.h>

View File

@@ -1,14 +0,0 @@
/* Copyright (C) vx_clutch
*
* This file is part of yait
*
* This project and file is licenced under the BSD-3-Clause licence.
* <https://opensource.org/license/bsd-3-clause>
*/
#ifndef STANDARD_H
#define STANDARD_H
int parse_standard_options (void (*usage_func) (), int argc, char **argv);
#endif

31
core/yait.h Normal file
View File

@@ -0,0 +1,31 @@
/* Copyright (C) vx_clutch
*
* This file is part of yait
*
* This project and file is licenced under the BSD-3-Clause licence.
* <https://opensource.org/license/bsd-3-clause>
*/
#ifndef YAIT_H
#define YAIT_H
/* Constants for file operations */
#define DEFAULT_DIR_PERMISSIONS 0755
#define MAX_PATH_LENGTH 1024
int printfn(char *format, ...);
int create_and_enter_directory(const char *dirname);
int create_file_with_content(char *path, char *format, ...);
int create_directory(char *format, ...);
int parse_standard_options(void (*usage_func)(), int argc, char **argv);
int program_exists(const char *prog);
#include "manifest.h"
int create_project(manifest_t manifest);
#endif // YAIT_H