]> git.xolatile.top Git - emil-bake.git/commitdiff
merge cbake/balex
authorEmil Williams <emilemilemil@cock.li>
Fri, 27 Sep 2024 03:13:43 +0000 (03:13 +0000)
committerEmil Williams <emilemilemil@cock.li>
Fri, 27 Sep 2024 03:13:43 +0000 (03:13 +0000)
1  2 
.gitignore
bake.1
bake.l
install.sh
shake

diff --cc .gitignore
index 0e059206ea4046f4730be8a3c11d9ee36931a2cc,0fcb3f7a77cce437a5fda0b084852d21b848c99d..98eb6f9707c2264cc8582931b7289938ddfe28ed
@@@ -1,1 -1,2 +1,2 @@@
 -cbake
 +bake
+ lex.yy.c
diff --cc bake.1
index 2420b181275cce178a6e609e7a7235ac1565fa58,0000000000000000000000000000000000000000..d53d084c8d55194d252c3c4173ce2160066f4337
mode 100644,000000..100644
--- 1/bake.1
--- /dev/null
+++ b/bake.1
@@@ -1,70 -1,0 +1,72 @@@
- \fB\-x \-\-expunge\fP, Removes what's specified in the expunge block
 +.TH BAKE "1" "August 2024" "bake 20240804" "User Commands"
 +.SH NAME
 +.B bake
 +\- file embeddable scripts
 +.SH SYNOPSIS
 +.B bake
 +[\-chlnx] [\-s <N>] <FILENAME> [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
- .B @FILENAME, @NAME, $@
++\fB\-x \-\-expunge\fP, Removes what's specified in the expunge block <disabled for this build>
 +.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 @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 <stdio.h>
 +int main (void) {
 +    puts("Hello.");
 +      return 0;
 +}
 +.EE
 +.SH COPYRIGHT
 +.PP
 +Licensed under the public domain.
diff --cc bake.l
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..43dee331464746870f3dd09ba600db0ea6862a01
new file mode 100644 (file)
--- /dev/null
--- /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 <ctype.h>
++
++#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; }
++.  {;}
++
++<FOUND>{
++ @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;                                                                                   }
++}
++
++<PADDING>{
++  {SPACE} {                       ; }
++  .|\n    { yyless(0); BEGIN FOUND; }
++}
++
++<STOP>{
++ @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, "<SHORTEN> 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<NUM>) */
++  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 -<LAST>s<NUM> -<LAST>s <NUM> */
++               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: <g_pipe> %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 29da9750e3fff35cd1a298c197b08ee9dbf762cd,806173b1b85e04c71f68cb5d948eca57fd7ac806..f591f0efe12949b21103ba5b297329d761d38897
mode 100755,100644..100755
@@@ -1,15 -1,5 +1,13 @@@
- INSTALL=${INSTALL:-bake shake}
 +#!/bin/sh
 +# source install
 +
 +TARGET=${TARGET:-/usr/local}
- cd $(dirname "$(readlink -f "$0")")/src
- chmod +x shake
++INSTALL=${INSTALL:-bake}
 +
 -PREFIX=${PREFIX:-/usr/local}
 -echo "PREFIX=$PREFIX"
 -bake cbake.l
 -install -m755 -sv ./cbake $PREFIX/bin/
+ cd "$(dirname "$(readlink -f $0)")"
- ./shake bake.c -s $@ && \
 +
- 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 
++./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
diff --cc shake
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8407a541b045ddb37978e055f26185c7345c2ab8
new file mode 100755 (executable)
--- /dev/null
--- /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