Files
yait/src/yait.c

1074 lines
27 KiB
C

/*
* yait vbeta - An opionated C project creator
*
* FEATURES:
* - Generate complete build systems: Autotools-like Makefile
* - Generate complete base system, ready to compile
* - Generate preconfigured gcklib, ready to import
*
* LIMITATIONS:
* - Platform: Only builds for POSIX systems
*
* COMPILATION (Linux - POSIX):
* ./configure && make
*
*
* LICENSE: BSD-3-Clause
*
* Copyright (c) 2025 GCK
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <config.h>
#include <errno.h>
#include <getopt.h>
#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "../lib/err.h"
#include "../lib/fs.h"
#include "../lib/proginfo.h"
#include "../lib/say.h"
#include "../lib/textc.h"
#include "../lib/xmem.h"
#include "licence.h"
typedef enum { MIT, GPL, BSD, UNL } licence_t;
static const struct option longopts[] = {
{ "author", required_argument, 0, 'a' },
{ "licence", required_argument, 0, 'l' },
{ "quiet", no_argument, 0, 'q' },
{ "force", no_argument, 0, 'f' },
{ 0, 0, 0, 0 }
};
static int exit_status;
static void print_help();
static void print_version();
static char *source_replace(const char *restrict template,
const char *restrict package,
const char *restrict author)
{
/*
* XXX(vx-clutch):
* - package token : {{PACKAGE}}
* - author token : {{AUTHOR}}
*
* - allocate buffer
* - replace all packages with package and authors with author
* - return
*/
char *buffer = xmalloc(99999999999);
return NULL;
}
static char *get_name()
{
int fds[2];
if (pipe(fds) == -1)
goto sysuser;
pid_t pid = fork();
if (pid == -1) {
close(fds[0]);
close(fds[1]);
goto sysuser;
}
if (pid == 0) {
dup2(fds[1], STDOUT_FILENO);
close(fds[0]);
close(fds[1]);
execlp("git", "git", "config", "--get", "user.name",
(char *)NULL);
_exit(127);
}
close(fds[1]);
char buf[256];
ssize_t n = read(fds[0], buf, sizeof buf - 1);
close(fds[0]);
int status;
waitpid(pid, &status, 0);
if (n > 0 && WIFEXITED(status) && WEXITSTATUS(status) == 0) {
buf[n] = 0;
buf[strcspn(buf, "\n")] = 0;
return str_dup(buf);
}
sysuser: {
char *name = getlogin();
if (name)
return str_dup(name);
struct passwd *pw = getpwuid(getuid());
if (pw && pw->pw_name)
return str_dup(pw->pw_name);
}
return str_dup("author");
}
static int get_year()
{
time_t now = time(NULL);
struct tm *t = localtime(&now);
return t->tm_year + 1900;
}
int main(int argc, char **argv)
{
int optc;
int lose = 0;
char *package;
bool quiet = false;
bool force = false;
bool editor = false;
bool shell = false;
char *author = get_name();
exit_status = EXIT_SUCCESS;
int year = get_year();
licence_t licence = BSD;
set_prog_name(argv[0]);
parse_standard_options(argc, argv, print_help, print_version);
while ((optc = getopt_long(argc, argv, "a:l:EqfS", longopts, NULL)) !=
-1)
switch (optc) {
case 'a':
if (optarg) {
if (author)
free(author);
author = str_dup(optarg);
}
break;
case 'l':
if (!strcmp(optarg, "list")) {
puts("BSD\nGPL\nMIT\nUNL");
exit(EXIT_SUCCESS);
}
if (!strcmp(optarg, "GPL"))
licence = GPL;
else if (!strcmp(optarg, "MIT"))
licence = MIT;
else if (!strcmp(optarg, "BSD"))
licence = BSD;
else if (!strcmp(optarg, "UNL"))
licence = UNL;
else {
puts("BSD\nGPL\nMIT\nUNL");
exit(EXIT_FAILURE);
}
break;
case 'E':
editor = true;
break;
case 'q':
quiet = true;
break;
case 'f':
force = true;
break;
case 'S':
shell = true;
break;
default:
lose = 1;
}
if (lose) {
emit_try_help();
}
if (optind >= argc) {
fatalf("no input name");
}
if (optind + 1 < argc) {
errorf("extra operand: %s", argv[optind + 1]);
emit_try_help();
}
package = str_dup(argv[optind]);
if (shell) {
fs_write(package, "\
#!/bin/sh\n\
\n\
# Usage: $0 [options]...\n\
\n\
prog_name=$(basename $0)\n\
tool_version=\"beta\"\n\
year=%d\n\
\n\
fatal() {\n\
echo \"fatal: $*\" >&2\n\
exit 1\n\
}\n\
\n\
run() {\n\
\"$@\" || fatal \"could not run: $*\"\n\
}\n\
\n\
print_help() {\n\
cat <<EOF\n\
Usage: $prog_name [options]...\n\
\n\
--help print this help and exit.\n\
--version print version information.\n\
EOF\n\
}\n\
\n\
print_version() {\n\
cat <<EOF\n\
$prog_name $tool_version $(git rev-list --count --all 2>/dev/null || echo 0)\n\
Copyright (C) $year %s.\n\
This is free software: you are free to change and redistribute it.\n\
There is NO WARRANTY, to the extent permitted by law.\n\
EOF\n\
}\n\
\n\
while [ $# -gt 0 ]; do\n\
case \"$1\" in\n\
--help) print_help; exit 0 ;;\n\
--version) print_version; exit 0 ;;\n\
*) fatal \"Not implemented yet\" ;;\n\
esac\n\
shift\n\
done\n\
",
year, author);
struct stat st;
if (stat(package, &st) != 0) {
fatalfa(errno);
}
mode_t mode = st.st_mode | S_IXUSR;
if (chmod(package, mode) != 0) {
fatalfa(errno);
}
return exit_status;
}
char *pdir;
asprintf(&pdir, "%s/", package);
fs_new(pdir);
if (chdir(pdir))
fatalfa(errno);
fs_new("doc/");
fs_new("src/");
fs_new("tools/");
fs_new("lib/");
fs_write("doc/version.texi", "\
@set UPDATED %s\
@set UPDATED-MONTH %s\
@set EDITION 1\
@set VERSION alpha\
",
"1 January 1970", "January 2025");
char *texi_buffer;
asprintf(&texi_buffer, "doc/%s.texi", package);
fs_write(texi_buffer, "\
\\input texinfo @c -*-texinfo-*-\n\
@c %**start of header\n\
@setfilename foo.info\n\
@include version.texi\n\
@settitle %s foo @value{VERSION}\n\
\n\
@defcodeindex op\n\
@syncodeindex op cp\n\
@c %**end of header\n\
\n\
@copying\n\
This manual is for %s foo (version @value{VERSION}, @value{UPDATED}),\n\
a simple program for demonstrating Texinfo documentation.\n\
\n\
Copyright @copyright{} 2025 %s.\n\
\n\
@quotation\n\
Copying and distribution of this file, with or without modification,\n\
are permitted in any medium without royalty provided the copyright\n\
notice and this notice are preserved.\n\
@end quotation\n\
@end copying\n\
\n\
@titlepage\n\
@title %s foo\n\
@subtitle for version @value{VERSION}, @value{UPDATED}\n\
@page\n\
@vskip 0pt plus 1filll\n\
@insertcopying\n\
@end titlepage\n\
\n\
@contents\n\
\n\
@ifnottex\n\
@node Top\n\
@top %s foo\n\
\n\
This manual is for %s foo (version @value{VERSION}, @value{UPDATED}),\n\
a simple program for demonstrating Texinfo documentation.\n\
@end ifnottex\n\
\n\
@menu\n\
* Overview:: General overview and purpose.\n\
* Sample output:: Example usage and output.\n\
* Invoking foo:: How to run @command{foo}.\n\
* Reporting bugs:: Sending bug reports and suggestions.\n\
* Concept index:: Index of concepts.\n\
@end menu\n\
\n\
\n\
@node Overview\n\
@chapter Overview\n\
\n\
@cindex overview\n\
@cindex purpose\n\
\n\
The %s @command{foo} program serves as a minimal example of a %s utility.\n\
Its purpose is to show how to build and document small command-line tools\n\
that follow GNU-style conventions.\n\
\n\
@itemize @bullet\n\
@item\n\
Implements clean command-line option parsing using GNU-style long options.\n\
@item\n\
Provides simple, predictable behavior for testing build systems.\n\
@item\n\
Demonstrates how to write Texinfo manuals for %s programs.\n\
@end itemize\n\
\n\
@cindex implementation\n\
%s foo is implemented in C and follows the GNU coding and maintainer standards.\n\
It uses Autotools for configuration and build setup, and Texinfo for documentation.\n\
\n\
\n\
@node Sample output\n\
@chapter Sample output\n\
\n\
@cindex examples\n\
@cindex sample output\n\
\n\
Here are some examples of running %s foo:\n\
\n\
@example\n\
$ foo\n\
foo: hello, world!\n\
@end example\n\
\n\
@example\n\
$ foo --message=\"%s rules!\"\n\
foo: %s rules!\n\
@end example\n\
\n\
@example\n\
$ foo --version\n\
%s foo @value{VERSION}\n\
@end example\n\
\n\
\n\
@node Invoking foo\n\
@chapter Invoking @command{foo}\n\
\n\
@cindex invoking\n\
@cindex usage\n\
@cindex options\n\
\n\
The general form for running @command{foo} is:\n\
\n\
@example\n\
foo @var{option} @dots{}\n\
@end example\n\
\n\
With no options, @command{foo} prints a default message.\n\
\n\
@command{foo} supports the following options:\n\
\n\
@table @option\n\
@item --message=@var{text}\n\
@itemx -m @var{text}\n\
@opindex --message\n\
@opindex -m\n\
Print @var{text} instead of the default greeting.\n\
\n\
@item --help\n\
@itemx -h\n\
@opindex --help\n\
@opindex -h\n\
Display help text and exit successfully.\n\
\n\
@item --version\n\
@itemx -v\n\
@opindex --version\n\
@opindex -v\n\
Print the version number and licensing information, then exit.\n\
@end table\n\
\n\
\n\
@node Reporting bugs\n\
@chapter Reporting bugs\n\
\n\
@cindex bugs\n\
@cindex reporting\n\
@cindex contact\n\
\n\
When reporting bugs, include:\n\
@itemize @bullet\n\
@item The output of @samp{foo --version}.\n\
@item Your operating system and compiler version.\n\
@item The exact command line used.\n\
@item Any relevant output or error messages.\n\
@end itemize\n\
\n\
Patches are welcome, preferably made with @samp{diff -u} and including\n\
a @file{ChangeLog} entry.\n\
\n\
\n\
@node GNU Free Documentation License\n\
@appendix GNU Free Documentation License\n\
\n\
@include fdl.texi\n\
\n\
\n\
@node Concept index\n\
@unnumbered Concept index\n\
\n\
@printindex cp\n\
\n\
@bye\
",
author, author, author, author, author, author, author, author,
author, author, author, author, author, author);
free(texi_buffer);
char *src_path;
asprintf(&src_path, "src/%s.c", package);
fs_write(src_path, "typedef int x;");
// fs_write(src_path, "\
// /* Copyright (C) %s\n\
// *\n\
// * This file is part of %s\n\
// *\n\
// * This project and file is licenced under the BSD-3-Clause licence.\n\
// * <https://opensource.org/licence/bsd-3-clause>\n\
// */\n\
// \n\
// // Usage: %s [OPTION]...\n\
// \n\
// #include <stdlib.h>\n\
// #include <stdio.h>\n\
// \n\
// #include <config.h>\n\
// \n\
// #include \"../lib/proginfo.h\"\n\
// \n\
// static int exit_status;\n\
// \n\
// static void print_help();\n\
// static void print_version();\n\
// \n\
// int main(int argc, char **argv)\n\
// {\n\
// set_prog_name(argv[0]);\n\
// \n\
// exit_status = EXIT_SUCCESS;\n\
// \n\
// return exit_status;\n\
// }\n\
// \n\
// void print_help()\n\
// {\n\
// printf(\"Usage: %%s [OPTION]...\\n\", PROGRAM);\n\
// fputs(\"\\
// %s does a thing.\\n\",\n\
// stdout);\n\
// \n puts(\"\");\n\
// fputs(\"\\
// --help display this help and exit\\n\\
// --version display version information and exit\\n\",\n\
// stdout);\n\
// puts(\"\");\n\
// fputs(\"\\
// --option Does an awesome thing\\n\\\",\n\
// stdout);\n\
// exit(exit_status);\n\
// }\n\
// \n\
// void print_version()\n\
// {\n\
// printf(\"%%s %%s %%d\\n\", prog_name, VERSION, COMMIT);\n\
// \n\
// printf(\"Copyright (C) %%d %s.\\n\", YEAR);\n\
// \n\
// puts(\"This is free software: you are free to change and redistribute it.\");\n\
// puts(\"There is NO WARRANTY, to the extent permitted by law.\");\n\
// exit(exit_status);\n\
// }\
// ",
// author, package, package, package, author);
free(src_path);
fs_write("tools/Cleanup", "\
#!/bin/sh\n\
# Usage: ./Cleanup\n\
\n\
fatal() {\n\
echo \"fatal: $*\" >&2\n\
exit 1\n\
}\n\
\n\
run() {\n\
\"$@\" || fatal \"could not run: $*\"\n\
}\n\
\n\
[ -d \"./git\" ] && fatal \"must be run from parent directory\"\n\
\n\
run sh ./tools/format\n\
run rm -rf .cache\n\
run rm -f compile_commands.json\n\
run make distclean\n\
\n\
echo \"done.\"\
");
fs_write("tools/format", "\
#!/bin/sh\n\
\n\
# Usage ./format\n\
\n\
find . -name \"*.c\" -exec clang-format -i --verbose {} \\;\n\
find . -name \"*.h\" -exec clang-format -i --verbose {} \\;\
");
fs_write(".clang-format", "\
---\n\
AccessModifierOffset: -4\n\
AlignAfterOpenBracket: Align\n\
AlignConsecutiveAssignments: false\n\
AlignConsecutiveDeclarations: false\n\
AlignEscapedNewlines: Left\n\
AlignOperands: true\n\
AlignTrailingComments: false\n\
AllowAllParametersOfDeclarationOnNextLine: false\n\
AllowShortBlocksOnASingleLine: false\n\
AllowShortCaseLabelsOnASingleLine: false\n\
AllowShortFunctionsOnASingleLine: None\n\
AllowShortIfStatementsOnASingleLine: false\n\
AllowShortLoopsOnASingleLine: false\n\
AlwaysBreakAfterDefinitionReturnType: None\n\
AlwaysBreakAfterReturnType: None\n\
AlwaysBreakBeforeMultilineStrings: false\n\
AlwaysBreakTemplateDeclarations: false\n\
BinPackArguments: true\n\
BinPackParameters: true\n\
BraceWrapping:\n\
AfterClass: false\n\
AfterControlStatement: false\n\
AfterEnum: false\n\
AfterFunction: true\n\
AfterNamespace: true\n\
AfterObjCDeclaration: false\n\
AfterStruct: false\n\
AfterUnion: false\n\
AfterExternBlock: false\n\
BeforeCatch: false\n\
BeforeElse: false\n\
IndentBraces: false\n\
SplitEmptyFunction: true\n\
SplitEmptyRecord: true\n\
SplitEmptyNamespace: true\n\
BreakBeforeBinaryOperators: None\n\
BreakBeforeBraces: Custom\n\
BreakBeforeInheritanceComma: false\n\
BreakBeforeTernaryOperators: false\n\
BreakConstructorInitializersBeforeComma: false\n\
BreakConstructorInitializers: BeforeComma\n\
BreakAfterJavaFieldAnnotations: false\n\
BreakStringLiterals: false\n\
ColumnLimit: 80\n\
CommentPragmas: '^ IWYU pragma:'\n\
CompactNamespaces: false\n\
ConstructorInitializerAllOnOneLineOrOnePerLine: false\n\
ConstructorInitializerIndentWidth: 8\n\
ContinuationIndentWidth: 8\n\
Cpp11BracedListStyle: false\n\
DerivePointerAlignment: false\n\
DisableFormat: false\n\
ExperimentalAutoDetectBinPacking: false\n\
FixNamespaceComments: false\n\
\n\
IncludeBlocks: Preserve\n\
IncludeCategories:\n\
- Regex: '.*'\n\
Priority: 1\n\
IncludeIsMainRegex: '(Test)?$'\n\
IndentCaseLabels: false\n\
IndentGotoLabels: false\n\
IndentPPDirectives: None\n\
IndentWidth: 8\n\
IndentWrappedFunctionNames: false\n\
JavaScriptQuotes: Leave\n\
JavaScriptWrapImports: true\n\
KeepEmptyLinesAtTheStartOfBlocks: false\n\
MacroBlockBegin: ''\n\
MacroBlockEnd: ''\n\
MaxEmptyLinesToKeep: 1\n\
NamespaceIndentation: None\n\
ObjCBinPackProtocolList: Auto\n\
ObjCBlockIndentWidth: 8\n\
ObjCSpaceAfterProperty: true\n\
ObjCSpaceBeforeProtocolList: true\n\
\n\
PenaltyBreakAssignment: 10\n\
PenaltyBreakBeforeFirstCallParameter: 30\n\
PenaltyBreakComment: 10\n\
PenaltyBreakFirstLessLess: 0\n\
PenaltyBreakString: 10\n\
PenaltyExcessCharacter: 100\n\
PenaltyReturnTypeOnItsOwnLine: 60\n\
\n\
PointerAlignment: Right\n\
ReflowComments: false\n\
SortIncludes: false\n\
SortUsingDeclarations: false\n\
SpaceAfterCStyleCast: false\n\
SpaceAfterTemplateKeyword: true\n\
SpaceBeforeAssignmentOperators: true\n\
SpaceBeforeCtorInitializerColon: true\n\
SpaceBeforeInheritanceColon: true\n\
SpaceBeforeParens: ControlStatementsExceptForEachMacros\n\
SpaceBeforeRangeBasedForLoopColon: true\n\
SpaceInEmptyParentheses: false\n\
SpacesBeforeTrailingComments: 1\n\
SpacesInAngles: false\n\
SpacesInContainerLiterals: false\n\
SpacesInCStyleCastParentheses: false\n\
SpacesInParentheses: false\n\
SpacesInSquareBrackets: false\n\
Standard: Cpp03\n\
TabWidth: 8\n\
UseTab: Always\n\
...\
");
fs_write(".clangd", "\
CompileFlags:\n\
Add: [-x, c, -std=c23, -Ilib, -I.]\n\
\n\
Diagnostics:\n\
ClangTidy:\n\
Add: [clang-diagnostic-*]\n\
Remove: []\
");
fs_write("README", "\
This is the README for the GCK %s distribution.\n\
%s does a thing.\n\
\n\
Copyright (C) %d GCK.\n\
\n\
Copying and distribution of this file, with or without modifications\n\
are permitted in any medium without royalty provided the copyright\n\
notice and this notice are preserved.\n\
\n\
See the files ./INSTALL* for building and installation instructions.\n\
\n\
Bug reports:\n\
Please include enough information for the maintainers to reproduce the\n\
problem. Generally speaking, that means:\n\
- the contents of any input files necessary to reproduce the bug\n\
and command line invocations of the program(s) involved (crucial!).\n\
- a description of the problem and any samples of the erroneous output.\n\
- the version number of the program(s) involved (use --version).\n\
- hardware, operating system, and compiler versions (uname -a).\n\
- unusual options you gave to configure, if any (see config.mak).\n\
- anything else that you think would be helpful.\n\
\n\
See README-dev for information on the development environment -- any\n\
interested parties are welcome. If you're a programmer and wish to\n\
contribute, this should get you started. If you're not a programmer,\n\
your help in writing test cases, checking documentation against the\n\
implementation, etc., would still be very much appreciated.\n\
\n\
GCK %s is free software. See the file COPYING for copying conditions.\n\
\n",
package, package, year, package);
fs_write("INSTALL", "\
Installation Instructions\n\
*************************\n\
\n\
Copyright (C) %d GCK.\n\
\n\
Copying and distribution of this file, with or without modification,\n\
are permitted in any medium without royalty provided the copyright\n\
notice and this notice are preserved. This file is offered as-is,\n\
without warranty of any kind.\n\
\n\
Basic Installation\n\
==================\n\
\n\
Briefly, the shell command `./configure && make && make install` should\n\
configure, build, and install this package. The following more-detailed\n\
instruction are generic; see the `README` file for instructions specific to\n\
this package.\n\
\n\
The `configure` shell script attempts to guess correct values for\n\
various system-dependent variables used during compilation. It uses\n\
those values within a `Makefile` to build for that POSIX system as\n\
defined by `config.mak` which was generated by `configure`.\n\
\n\
Compilers and Options\n\
=====================\n\
\n\
Some systems require unusal options for compilation or linking that\n\
the `configure` script does not know about. If you run into an issue\n\
run `./configure --help` to figure out what you can do to fix the\n\
behavoir.\n\
\n\
Installation Names\n\
==================\n\
\n\
By default, `make install` installs the package's command under\n\
`/usr/local/bin`. You can specify an installation prefix other than `/usr/local/`\n\
by giving `configure` the option `--prefix=PREFIX` to `configure`, the package uses\n\
PREFIX as the prefix for installation programs and libraries.\n\
Documentation and other data files still use the regular prefix.\n\
\n\
`configure` Invokation\n\
======================\n\
\n\
`configure` recongizes the following options to control its operations.\n\
\n\
`--help`\n\
Prints a summary of all the options to `configure`, and exits.\n\
`--prefix=PREFIX`\n\
Sets the installation prefix.\n\
`CFLAGS`\n\
Sets the flags used during compilation.\n\
\n\
`configure` also accepts some other options. Run `configure --help` for more\n\
details\n\
",
year);
fs_write("AUTHORS", "\
Authors of %s %s.\n\
\n\
Copyright (C) %d %s.\n\
\n\
Copying and distribution of this file, with or without modification,\n\
are permitted in any medium without royalty provided the copyright\n\
notice and this notice are preserved.\n\
\n\
Also see the THANKS files.\n\
\n\
%s\n\
",
author, package, year, package, author);
fs_write("THANKS", "\
Additional contributors to %s %s.\n\
\n\
Copyright (C) %d %s.\n\
\n\
Copying and distribution of this file, with or without modification,\n\
are permitted in any medium without royalty provided the copyright\n\
notice and this notice are preserved.\n\
\n\
Thanks to:\n\
\n\
GCK yait for project initialization.\n\
\n\
See also the AUTHORS file.\n\
",
author, package, year, author);
fs_write("config.h", "\
#ifndef CONFIG_H\n\
#define CONFIG_H\n\
\n\
/* Program information */\n\
#define PROGRAM \"%s\"\n\
#define AUTHORS \"%s\"\n\
#define VERSION \"beta\"\n\
#define YEAR %d\n\
\n\
#endif\
",
package, author, year);
fs_write("configure", "\
#!/bin/sh\n\
\n\
usage() {\n\
cat <<EOF\n\
Usage: $0 [OPTION]... [VAR=VALUE]...\n\
\n\
To assign environment variables (e.g., CC, CFLAGS...), specify them as\n\
VAR=VALUE.\n\
\n\
CC C compiler command [detected]\n\
CFLAGS C compiler flags [-g, ...]\n\
LDFLAGS C linker flags\n\
\n\
--prefix=<path> Set the install path\n\
--debug Flags for debug build, overrides CFLAGS\n\
\n\
EOF\n\
exit 0\n\
}\n\
\n\
cmdexists() { type \"$1\" >/dev/null 2>&1 ; }\n\
trycc() { [ -z \"$CC\" ] && cmdexists \"$1\" && CC=$1 ; }\n\
\n\
prefix=/usr/local\n\
CFLAGS=\"-std=c23\"\n\
LDFLAGS=\n\
CC=\n\
\n\
printf \"checking for C compiler... \"\n\
trycc gcc\n\
trycc clang\n\
trycc cc\n\
trycc icx\n\
printf \"%%s\n\" \"$CC\"\n\
\n\
DEBUG=false\n\
for arg; do\n\
case \"$arg\" in\n\
--help|-h) usage ;;\n\
--prefix=*) prefix=${arg#*=} ;;\n\
--debug) DEBUG=true ;;\n\
CFLAGS=*) CFLAGS=${arg#*=} ;;\n\
LDFLAGS=*) LDFLAGS=${arg#*=} ;;\n\
CC=*) CC=${arg#*=} ;;\n\
*) printf \"Unrecognized option %%s\n\" \"$arg\" ;;\n\
esac\n\
done\n\
\n\
printf \"checking whether C compiler works... \"\n\
tmpc=\"$(mktemp -d)/test.c\"\n\
echo \"typedef int x;\" > \"$tmpc\"\n\
if output=$($CC $CFLAGS -c -o /dev/null \"$tmpc\" 2>&1); then\n\
printf \"yes\n\"\n\
else\n\
printf \"no; %%s\n\" \"$output\"\n\
exit 1\n\
fi\n\
\n\
GDEBUGCFLAGS=\"-std=c23 -O0 -g3 -Wall -Wextra -Wpedantic -Werror -Wshadow -Wdouble-promotion -Wformat=2 -Wnull-dereference -Wconversion -Wsign-conversion -Wcast-qual -Wcast-align=strict -Wpointer-arith -Wstrict-overflow=5 -Wstrict-aliasing=2 -Wundef -Wunreachable-code -Wswitch-enum -fanalyzer -fsanitize=undefined,address -fstack-protector-strong -D_FORTIFY_SOURCE=3\"\n\
CDEBUGCFLAGS=\"-std=gnu2x -O0 -g3 -Wall -Wextra -Wpedantic -Werror -Wshadow -Wdouble-promotion -Wformat=2 -Wnull-dereference -Wconversion -Wsign-conversion -Wcast-qual -Wcast-align=strict -Wpointer-arith -Wstrict-overflow=5 -Wstrict-aliasing=2 -Wundef -Wunreachable-code -Wswitch-enum -fanalyzer -fsanitize=undefined,address -fstack-protector-strong -D_FORTIFY_SOURCE=3\"\n\
\n\
if [ \"$DEBUG\" = \"false\" ]; then\n\
case \"$CC\" in\n\
gcc) CFLAGS=\"$GDEBUGCFLAGS\";;\n\
clang) CFLAGS=\"$CDEBUGCFLAGS\";;\n\
*) ;;\n\
esac\n\
else\n\
case \"$CC\" in\n\
gcc) ;;\n\
clang) ;;\n\
*) ;;\n\
esac\n\
fi\n\
\n\
case \"$OSTYPE\" in\n\
cygwin|msys) \n\
echo \"enabling windows specific flags\"\n\
CFLAGS=\"-v $CFLAGS\"\n\
;;\n\
esac\n\
\n\
printf \"creating config.mak... \"\n\
{\n\
printf \"PREFIX=%%s\n\" \"$prefix\"\n\
printf \"CFLAGS=%%s\n\" \"$CFLAGS\"\n\
printf \"LDFLAGS=%%s\n\" \"$LDFLAGS\"\n\
printf \"CC=%%s\n\" \"$CC\"\n\
} > config.mak\n\
printf \"done\n\"\
");
fs_write("Makefile", "\
PACKAGE := %s\n\
\n\
SRCS := $(wildcard src/*.c) $(wildcard lib/*.c)\n\
OBJS := $(patsubst src/%%.c,build/obj/%%.o,$(SRCS))\n\
\n\
BIN := bin/$(PACKAGE)\n\
\n\
COMMIT := $(shell git rev-list --count --all)\n\
FLAGS := -I. -DCOMMIT=$(COMMIT)\n\
\n\
VERSION := $(shell git describe --tags --always --dirty)\n\
TARBALL := $(PACKAGE)-$(VERSION).tar.gz\n\
RELEASE_FILES := doc src lib COPYING AUTHORS README yait.1 INSTALL Makefile configure config.h\n\
\n\
-include config.mak\n\
\n\
ifeq ($(wildcard config.mak),)\n\
all:\n\
@echo \"File config.mak not found, run configure \"\n\
@exit 1\n\
else\n\
\n\
all: build $(BIN)\n\
\n\
build:\n\
mkdir -p bin\n\
mkdir -p build/obj\n\
\n\
build/obj/%%.o: $(SRCS) config.mak\n\
$(CC) $(FLAGS) $(CFLAGS) -c $< -o $@\n\
\n\
$(BIN): $(OBJS) \n\
$(CC) $(FLAGS) $(CFLAGS) $^ -o $@\n\
\n\
endif\n\
\n\
install: $(BIN)\n\
cp $(BIN) $(PREFIX)\n\
\n\
uninstall:\n\
$(RM) $(PREFIX)$(PACKAGE)\n\
\n\
clean:\n\
$(RM) $(BIN)\n\
$(RM) -r build\n\
\n\
distclean: clean\n\
$(RM) config.mak\n\
$(RM) $(TARBALL)\n\
\n\
release: clean all\n\
tar -czf $(TARBALL) $(RELEASE_FILES)\n\
\n\
.PHONY: all clean distclean install uninstall build release\
",
package);
fs_write("TODO", "\
%s %s- TODO\n\
\n\
Todo:\n\
\n\
* Generate %s.1 with help2man.\n\
\n\
end of file TODO\
",
author, package, package);
switch (licence) {
case BSD:
fs_write("COPYING", fBSD, year, author);
break;
case MIT:
fs_write("COPYING", fMIT, year, author);
break;
case GPL:
fs_write("COPYING", fGPL);
break;
case UNL:
fs_write("COPYING", fUNL);
break;
default:
fatalf("illegal state");
}
struct stat st;
if (stat("configure", &st) != 0) {
fatalfa(errno);
}
mode_t mode = st.st_mode | S_IXUSR;
if (chmod("configure", mode) != 0) {
fatalfa(errno);
}
if (chmod("tools/format", mode) != 0) {
fatalfa(errno);
}
if (chmod("tools/Cleanup", mode) != 0) {
fatalfa(errno);
}
return exit_status;
}
static void print_help()
{
printf("Usage: %s [OPTION]... [project-name]...\n", PROGRAM);
fputs("\
Generates an opinionated C project.\n",
stdout);
puts("");
fputs("\
--help display this help and exit\n\
--version display version information and exit\n",
stdout);
puts("");
fputs("\
-E Open $EDITOR after project creation\n\
-q, --quiet Only print required messages\n\
-f, --force Overwrite existing files\n\
--author=NAME Set the program author (default git username|system username)\n\
--licence=LICENCE Set the program licence (default BSD)\n",
stdout);
exit(exit_status);
}
static void print_version()
{
printf("%s %s %d\n", prog_name, VERSION, COMMIT);
printf("Copyright (C) %d GCK.\n", YEAR);
puts("This is free software: you are free to change and redistribute it.");
puts("There is NO WARRANTY, to the extent permitted by law.");
exit(exit_status);
}
/* end of file yait.c */