From: Emil Williams Date: Fri, 27 Sep 2024 03:13:43 +0000 (+0000) Subject: merge cbake/balex X-Git-Tag: v20240930~12 X-Git-Url: https://git.xolatile.top/?a=commitdiff_plain;h=8179969546579d195d436b48c1c2a5afbc9f5585;p=emil-bake.git merge cbake/balex --- 8179969546579d195d436b48c1c2a5afbc9f5585 diff --cc .gitignore index 0e05920,0fcb3f7..98eb6f9 --- a/.gitignore +++ b/.gitignore @@@ -1,1 -1,2 +1,2 @@@ -cbake +bake + lex.yy.c diff --cc bake.1 index 2420b18,0000000..d53d084 mode 100644,000000..100644 --- a/bake.1 +++ b/bake.1 @@@ -1,70 -1,0 +1,72 @@@ +.TH BAKE "1" "August 2024" "bake 20240804" "User Commands" +.SH NAME +.B bake +\- file embeddable scripts +.SH SYNOPSIS +.B bake +[\-chlnx] [\-s ] [ARGS...] +.SH DESCRIPTION + +bake is a simple tool meant to execute embedded shell commands within +any file. It executes with /bin/sh the command after a "\fB@BAKE\fP " to +the end of the line (a UNIX newline: '\fB\\n\fP'). + +This format may be embedded within \fBbinary files\fP, or any file where no unwanted preceding +instance of \fB@BAKE\fP appears. + +It roots the shell execution in the directory of the given file. + +Options must always be put first, and short options may be merged together, numerical options must be trailing. + +.HP + \fB\-c \-\-color\fP, Disables color + \-h \-\-help, Help message +\fB\-n \-\-dry\-run\fP, don't execute anything +\fB\-l \-\-list\fP, lists available shell commands +\fB\-s \-\-select\fP <\FBn\fP>, selects Nth shell command - \fB\-x \-\-expunge\fP, Removes what's specified in the expunge block ++\fB\-x \-\-expunge\fP, Removes what's specified in the expunge block +.PP +Macros + +All macros can be exempted by prefixing them with a backslash, +which'll be subtracted in the expansion. multi-line commands may be +done by a leading backslash, which are NOT subtracted. + +These macros will expand to their counterpart before execution. +.TP - .B @FILENAME, @NAME, $@ ++.B @FILE, @FILENAME, @NAME, $@ +returns target\-file (abc.x.txt) +.TP +.B @SHORT, $* +returns target\-file without suffix (abc.x.txt \-> abc.x) ++supports choice syntax, @SHORT:1 +.TP +.B @ARGS, $+ +returns +.B arguments ++, supports choice syntax, @ARGS:0 returns the first argument, @ARGS:N so on. +.TP +.B @LINE +returns the line number + +.PP +Additional Features And Notes + +Shell execution may be disabled with the \fB-n\fP or \fB--dry-run\fP option. + +Expunge removes exactly one file specified in the @{...} format. You may use +backslashes to remove + +.SH EXAMPLE +.\" SRC BEGIN (example.c) +.EX +// example.c @BAKE cc -o @SHORT @NAME @ARGS +#include +int main (void) { + puts("Hello."); + return 0; +} +.EE +.SH COPYRIGHT +.PP +Licensed under the public domain. diff --cc bake.l index 0000000,0000000..43dee33 new file mode 100644 --- /dev/null +++ b/bake.l @@@ -1,0 -1,0 +1,176 @@@ ++/* cbake.l @BAKE flex @FILE && cc -Wall -Wextra -std=c99 -D_GNU_SOURCE -o @SHORT lex.yy.c @ARGS -lfl @STOP */ ++/* TODO: implement expunge, color */ ++%{ ++#include ++ ++#undef ECHO ++#define ECHO do { fprintf(stdout, yytext); if (g_pipe) { fprintf(g_pipe, yytext); } } while (0) ++#define CHAR(c) do { fputc(c, stdout); if (g_pipe) { fputc(c, g_pipe); } } while (0) ++#define STRING(s) do { fputs(s, stdout); if (g_pipe) { fputs(s, g_pipe); } } while (0) ++#define FORMAT(...) do { fprintf(stdout, __VA_ARGS__); if (g_pipe) { fprintf(g_pipe, __VA_ARGS__); } } while (0) ++#define FWRITE(str, len) do { fwrite(str, 1, len, stdout); if (g_pipe) { fwrite(str, 1, len, g_pipe); } } while (0) ++ ++/* input from main to lexer */ ++FILE * g_pipe; ++char * g_filename; ++int g_ac; ++char ** g_av; ++int g_select = 1; ++/* for the lexers eyes only */ ++int line = 1, nth = 0, expunge_depth = 0, first_nl, tmpline; ++ ++extern void root(char * filename); ++extern void args(int n); ++extern void shorten(char * filename, int n); ++%} ++ ++SPACE [ \t\r\v\f] ++MACROS (@BAKE|@FILENAME|@FILE|@NAME|@SHORT|@ARGS|@LINE|@STOP|$@|$*|$+) ++ ++%x FOUND PADDING STOP ++%option nodefault noinput nounput noyywrap ++%% ++ ++@BAKE[[:space:]] { bake: ++ first_nl = 1; ++ ++ if (yytext[yyleng-1] == '\n') { ++line; } ++ if (!g_select) { ; } ++ else if (g_select < 0) { BEGIN FOUND; printf("\n%s:%d:s%d: ", g_filename, line, ++nth); } ++ else if (!--g_select) { BEGIN FOUND; } ++} ++ ++\n { ++line; } ++. {;} ++ ++{ ++ @BAKE[[:space:]]|@STOP { BEGIN INITIAL; yyless(0); if (first_nl) { CHAR('\n'); } if (!g_select) { return 0; } } ++ @FILENAME|@FILE|@NAME|$@ { STRING(g_filename); } ++ @SHORT:[[:digit:]]+ { shorten(g_filename, atoi(strrchr(yytext, ':')+1)); } ++ @SHORT|$\* { shorten(g_filename, 1); } ++ @ARGS:[[:digit:]]+ { args(atoi(strrchr(yytext, ':')+1)); } ++ @ARGS|$\+ { args(-1); } ++ @LINE { FORMAT("%d", line); } ++ @\{ { ++expunge_depth; } ++ \} { if (!expunge_depth--) { ECHO; } } ++ \\\n { BEGIN PADDING; ++line; CHAR(' '); } ++ \\{MACROS} { STRING(yytext + 1); } ++ \n { CHAR('\n'); ++line; if (first_nl) { BEGIN STOP; first_nl = 0; tmpline = 0; } } ++ {SPACE} { BEGIN PADDING; CHAR(' '); } ++ . { ECHO; } ++} ++ ++{ ++ {SPACE} { ; } ++ .|\n { yyless(0); BEGIN FOUND; } ++} ++ ++{ ++ @BAKE[[:space:]] { line += tmpline; goto bake; } ++ @STOP { BEGIN FOUND; yyless(0); } ++ \n { ++tmpline; yymore(); } ++ .|\\@ { yymore(); } ++} ++ ++%% ++ ++void root(char * filename) { ++ char * path, * terminator; ++ if (!(path = realpath(filename, NULL))) { return; } ++ if ((terminator = strrchr(path, '/'))) { ++ *terminator = '\0'; ++ chroot(path); ++ } ++ free(path); ++} ++ ++void args(int n) { ++ if (n < 0) { for (int i = 0; i < g_ac; ++i) { STRING(g_av[i]); if (i + 1 < g_ac) { CHAR(' '); } } } ++ else if (n < g_ac) { STRING(g_av[n]); } ++} ++ ++void shorten(char * filename, int n) { ++ char * end = filename + strlen(filename); ++ while (n && (end = memrchr(filename, '.', end - filename))) { --n; } ++ if (!end) { ++ fprintf(stderr, " context error: Argument out of range.\n"); ++ /* Ensures consistency. @SHORT will always return *something* that isn't filename */ ++ STRING("idiot"); ++ return; ++ } ++ FWRITE(filename, end - filename); ++} ++ ++void help(void) { fputs("see bake(1) - \"Buy high. Sell low.\"\n", stderr); } ++ ++int main (int ac, char ** av) { ++ int run = 1; ++ char * av0 = av[0]; ++ FILE * fp; ++ ++ /* supports long/short, -allinone, (-X ... -X=... -X) */ ++ while (++av, --ac) { ++ size_t i; ++ if (av[0][0] != '-') { goto start; } ++ if (av[0][1] == '-') { ++ if (av[0][2] == '\0') { ++av, --ac; goto start; } ++ if (!strcmp(av[0]+2, "dry-run")) { i = strlen(av[0]); goto opt_dry_run; } ++ if (!strcmp(av[0]+2, "select" )) { if (!ac-1 || isdigit(av[1][0])) { goto opt_arg; } ++ ++av, --ac; i = strlen(av[0]); goto opt_select; } ++ if (!strcmp(av[0]+2, "list" )) { i = strlen(av[0]); goto opt_list; } ++ if (!strcmp(av[0]+2, "help" )) { goto opt_help; } ++ goto opt_default; ++ } ++ for (i = 1; i < strlen(av[0]); ++i) { ++ switch (av[0][i]) { ++ opt_dry_run: case 'n': run = 0; break; ++ case 's': ++ /* Covers cases -s -s */ ++ if (isdigit(av[0][i+1])) { g_select = atoi(av[0]+i+1); } ++ else if (ac > 1 && isdigit(av[1][0])) { ++av, --ac; opt_select: g_select = atoi(av[0]); } ++ else { g_select = 0; } ++ if (!g_select) { fprintf(stderr, "%s: Invalid argument for -s\n", av0); return 1; } ++ i = strlen(av[0]); ++ break; ++ opt_list: case 'l': run = 0; g_select = -1; break; ++ opt_help: case 'h': help(); return 0; ++ opt_default: default: fprintf(stderr, "%s: Unknown option '%s'\n", av0, av[0]); return 1; ++ opt_arg: fprintf(stderr, "%s: Argument missing for '%s'\n", av0, av[0]); return 1; ++ } ++ } ++ } ++ ++ start: ++ if (!ac) { fprintf(stderr, "%s: Missing filename\n", av0); return 1; } ++ if (!g_select) { goto out_of_range; } ++ ++ g_filename = av[0]; ++ root(g_filename); ++ { /* ensures the filename doesn't have a relative path that would misdirect the command within the new root */ ++ char * tmp = strrchr(g_filename, '/'); ++ if (tmp) { g_filename = tmp+1; } ++ } ++ ++ /* open and prepare ac, av */ ++ if (!(yyin = fp = fopen(g_filename, "rb"))) ++ { fprintf(stderr, "%s: '%s' %s\n", av0, g_filename, strerror(errno)); return 1; } ++ g_ac = --ac, g_av = ++av; ++ ++ /* Prepares our UNIX pipe for input */ ++ if (run) { ++ g_pipe = popen("/bin/sh -e", "w"); ++ if (!g_pipe) { fprintf(stderr, "%s: %s\n", av0, strerror(errno)); return 1; } ++ } ++ ++ if (g_select > 0) { fprintf(stderr, "%s: ", av0); fflush(stderr); } ++ yylex(); fflush(stdout); ++ fclose(fp); ++ if (g_select > 0) { pclose(g_pipe); goto out_of_range; } ++ ++ if (!run) { return 0; } ++ fprintf(stderr, "output: "); fflush(stderr); ++ run = pclose(g_pipe); /* repurposed run */ ++ if (run) { printf("%s: Exit code %d\n", av0, run); } ++ return run; ++ out_of_range: fprintf(stderr, "%s: <%d> Out of range\n", av0, g_select); return 1; ++} diff --cc install.sh index 29da975,806173b..f591f0e mode 100755,100644..100755 --- a/install.sh +++ b/install.sh @@@ -1,15 -1,5 +1,13 @@@ +#!/bin/sh +# source install + +TARGET=${TARGET:-/usr/local} - INSTALL=${INSTALL:-bake shake} ++INSTALL=${INSTALL:-bake} + - cd $(dirname "$(readlink -f "$0")")/src - chmod +x shake + cd "$(dirname "$(readlink -f $0)")" -PREFIX=${PREFIX:-/usr/local} -echo "PREFIX=$PREFIX" -bake cbake.l -install -m755 -sv ./cbake $PREFIX/bin/ + - ./shake bake.c -s $@ && \ ++./shake bake.l -s $@ && \ +mkdir $TARGET/bin $TARGET/man/man1 -p && \ +install -m 755 $INSTALL $TARGET/bin + - gzip -c bake.1 > $TARGET/man/man1/bake.1.gz && \ - ln -f -s $TARGET/man/man1/bake.1.gz $TARGET/man/man1/shake.1.gz ++gzip -c bake.1 > $TARGET/man/man1/bake.1.gz diff --cc shake index 0000000,0000000..8407a54 new file mode 100755 --- /dev/null +++ b/shake @@@ -1,0 -1,0 +1,97 @@@ ++#!/bin/bash ++ ++# Originally written by Anon, modified by Emil to better match Bake functionality ++ ++# Issues: sloooow, fails to handle multi-line statements ++ ++VERSION="20240408" ++ ++BLUE='\033[34m' ++GREEN='\033[32m' ++YELLOW='\033[93m' ++DIM='\033[2m' ++BOLD='\033[1m' ++NORMAL='\033[0m' ++ ++MARKNAME="@BAKE" ++MARK="${MARKNAME} " ++MARKSTR="${GREEN}${MARKNAME}${NORMAL}" ++ ++enable -n echo ++ ++usage() { ++ echo -e "$0: [option] ${BOLD}target-file${NORMAL} [${GREEN}arguments${NORMAL} ...]\n" ++ echo -e "Use the format \`${BOLD}@BAKE${NORMAL} cmd ...' within the ${BOLD}target-file${NORMAL}." ++ echo -e "This will execute until the end of line, or if existing, until the ${BOLD}@STOP${NORMAL} marker.\n" ++ echo -e "Options [Must be first]" ++ echo -e "\t${DIM}-h --help${NORMAL}, ${BOLD}-n --dry-run${NORMAL}\n" ++ echo -e "Expansions\n" ++ echo -e "\t${YELLOW}@FILENAME${NORMAL} returns target-file (abc.x.txt)" ++ echo -e "\t${YELLOW}@SHORT${NORMAL} returns target-file without suffix (^-> abc.x)" ++ echo -e "\t${YELLOW}@ARGS${NORMAL} returns ${GREEN}arguments${NORMAL}" ++ echo -e "\t${YELLOW}@{${NORMAL}${BOLD}EXPUNGE_THIS_FILE${YELLOW}}${NORMAL} inline region to delete this or many files or directories," ++ echo -e "\tnon-recursive, only one file per block, removed from left to right. This has no\n\tinfluence on the normal command execution.\n" ++} ++ ++if [[ $# -lt 1 ]]; then ++ usage ++ exit 1 ++fi ++ ++if [[ $1 == "-h" ]] || [[ $1 == "--help" ]]; then ++ usage ++ exit 0 ++fi ++ ++if [[ $1 == "-v" ]] || [[ $1 == "--version" ]]; then ++ echo -e "$0: $VERSION" ++ exit 0 ++fi ++ ++run=1 ++if [[ $1 == "-n" ]] || [[ $1 == "--dry-run" ]]; then ++ if [[ $# -lt 2 ]]; then ++ usage ++ exit 1 ++ fi ++ run=0 ++ shift 1 ++fi ++ ++input_file=$1 ++shift 1 ++ ++if [[ ! -f $input_file ]]; then ++ echo -e "Input file '$input_file' does not exist." >&2 ++ exit 1 ++fi ++ ++cd $(dirname "$(readlink -f "$input_file")") ++input_file=${input_file##*/} ++ ++line=$(grep "$MARK" "$input_file" | head -1) ++ ++if [[ -n $line ]]; then ++ ++ line=${line//\$@/$input_file} ++ line=${line//\$\*/${input_file%.*}} ++ line=${line//\$+/$@} ++ line=${line//@FILENAME/$input_file} ++ line=${line//@SHORT/${input_file%.*}} ++ line=${line//@ARGS/$@} ++ line=${line//@NAME/$input_file} ++ line=${line//@FILE/$input_file} ++ line=$(echo "$line" | sed 's/@STOP.*//') ++ ++ echo -e "${BOLD}${GREEN}$0${NORMAL}: ${line#*${MARK}}" ++ ++ line=$(echo "$line" | sed -E 's/@\{([^ \}]+?)\}/\1/') ++ command="${line#*${MARK}}" ++ if [[ $run -eq 1 ]]; then ++ echo -e "${BOLD}${GREEN}output${NORMAL}:" ++ sh -c "$command" ++ fi ++else ++ echo -e "${MARKSTR} is not defined." >&2 ++ exit 1 ++fi