From a5a1df24f01a3cc6ee78ceebb7e3b4c6f1893ea0 Mon Sep 17 00:00:00 2001 From: dozens Date: Sun, 4 Aug 2024 22:03:38 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=F0=9F=97=84=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 126 ++++++++++++++++++++++++++++++-- doc/Tutorial.md | 19 ++--- doc/deps.png | Bin 0 -> 35944 bytes doc/src/readme.m4 | 65 ++++++++++++++++ doc/{ => v}/meta.rec | 0 doc/{ => v}/versions.rec | 0 justfile | 95 +++++++++++++++++------- src/flib.fnl => lib/filters.fnl | 0 {src => lib}/string.fnl | 0 {src => lib}/table.fnl | 0 src/filter.fnl | 6 +- src/main.fnl | 7 ++ src/story.fnl | 10 ++- 13 files changed, 279 insertions(+), 49 deletions(-) create mode 100644 doc/deps.png create mode 100644 doc/src/readme.m4 rename doc/{ => v}/meta.rec (100%) rename doc/{ => v}/versions.rec (100%) rename src/flib.fnl => lib/filters.fnl (100%) rename {src => lib}/string.fnl (100%) rename {src => lib}/table.fnl (100%) diff --git a/README.md b/README.md index f9c2473..4955f01 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,127 @@ for expanding random table entries +1. [Requirements](#requirements) +2. [Installing](#installing) +3. [Architecture](#architecture) +4. [Tutorial](#tutorial) + * [Random Selections](#random-selections) + * [Expansion](#expansion) + * [Filters](#filters) +5. [Roadmap](#roadmap) +6. [Resources](#resources) + ## Requirements -Built with -Fennel 1.3.1 on PUC Lua 5.4 +- Fennel 1.3.1 on PUC Lua 5.4 +- gnu recutils 1.9: for querying metadata +- just 1.34.0: just a task runner -## Usage +## Installing You can run the script: `fennel src/main.fnl`. Or you can compile a binary and use that. See `just compile`. +There is also a vim plugin for the `tbls` format. +See `vim-tbls/README.md`. + +## Architecture + +- `src/story.fnl`: core of the project. where all the file handling and text parsing happens +- `src/main.fnl`: wrapper for story.fnl. the ui. +- `src/filter.fnl` logic for applying filters to strings +- `lib/*.fnl` libraries and helper functions + +![Autogenerated Dependency Graph][deps] + +[deps]: doc/deps.png "Autogenerated Dependency Graph" + +## Tutorial + +### Random Selections + +At its most basic, `tbls` selects a random element from a table. + +Suppose you have a few tables: + +``` +:: suit +Hearts +Bells +Whistles +Cups +Knives +Shovels + +:: card +Ace +One +Two +[...] +Queen +King +Beast +``` + +`tbls` might return "Whistles" from suit. +Or "Two" from card. + +### Expansion + +But wait there's more. +`tbls` will also expand certain text found in a table entry. + +Let's add another table: + +``` +:: draw +[card] of [suit] +``` + +When you place the name of a table in `[squarebrackets]`, +then `tbls` views that as a placeholder +for a random item from that table. +It will expand that text. +So 'draw' might end up being "Thief of Shovels" +or "Twelve or Cups". + +### Filters + +`tbls` can run arbitary filters on text after expanding it. +Example filters can be found in `lib/filters.fnl`. + +To apply filters, +dot-chain them at the end +of a table reference. + +Consider the following tables: + +``` +:: origin +Evelyn eats one [fruit.c] +Evelyn eats many [fruit.c.s] + +:: fruit +banana +apple +pear +``` + +`s`, `plural`, and `pluralize` +are all different ways +to call the 'plural' filter function. +`c` is 'capitalize.' + +Example output: + +``` +Evelyn eats one Pear +Evelyn eats many Bananas +``` + + ## Roadmap - [x] random table entries (ADDED in vHydrogen) @@ -25,10 +134,13 @@ See `just compile`. ## Resources inspired heavily by tracery: -https://github.com/galaxykate/tracery + -and paper elemental's list-to-html generator: -https://paperelemental.blogspot.com/p/list-to-html-generator.html +and this list-to-html geneator from slight adjustments: + + +but also paper elemental's: + and perchance: -https://perchance.org/welcome + diff --git a/doc/Tutorial.md b/doc/Tutorial.md index e13616f..9ae6f01 100644 --- a/doc/Tutorial.md +++ b/doc/Tutorial.md @@ -1,6 +1,6 @@ -# Tutorial +## Tutorial -## Random Selections +### Random Selections At its most basic, `tbls` selects a random element from a table. @@ -28,7 +28,7 @@ Beast `tbls` might return "Whistles" from suit. Or "Two" from card. -## Expansion +### Expansion But wait there's more. `tbls` will also expand certain text found in a table entry. @@ -47,10 +47,10 @@ It will expand that text. So 'draw' might end up being "Thief of Shovels" or "Twelve or Cups". -## Filters +### Filters `tbls` can run arbitary filters on text after expanding it. -Example filters can be found in `src/flib.fnl`. +Example filters can be found in `lib/filters.fnl`. To apply filters, dot-chain them at the end @@ -60,8 +60,8 @@ Consider the following tables: ``` :: origin -Evelyn eats one [fruit] -Evelyn eats many [fruit.s] +Evelyn eats one [fruit.c] +Evelyn eats many [fruit.c.s] :: fruit banana @@ -72,10 +72,11 @@ pear `s`, `plural`, and `pluralize` are all different ways to call the 'plural' filter function. +`c` is 'capitalize.' Example output: ``` -Evelyn eats one pear -Evelyn eats one bananas +Evelyn eats one Pear +Evelyn eats many Bananas ``` diff --git a/doc/deps.png b/doc/deps.png new file mode 100644 index 0000000000000000000000000000000000000000..0d786e0dd35c1064c3beb4191860c8d1a7bce988 GIT binary patch literal 35944 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ⅅABEx&G+>Dysa%C`YQivY zQ>T&S!-@#&kyi;6g`(WXShXVPPzXiwD5w^cS3*sKqBwcy>$3s122u049V$ z1EJ14{*q~%?#Ppiq17V5m!*Aj}lL6t1v%UA|BK<7O3&XV%y;wc`qR!3Uz$C^T$7%4Q)QrnAJfGng%i$NVs<-pPbqM?3?C20(Dn0O4C!UI6J37`$4fz z^X@N>q$gNWnkvGOaD^(;`f9LqLhYSkzOtjHyGtEcK7qEXWINuCGJ%R6c*S{Kt)MN&jChO2#udW2RxYoy@qRJb8U+prT!|0C0#}c z!BLc_j^9KPLjZCJ_P-e@t)t3MOQYvCEIjOVs2&7iv7cECV z#C`|KT)E`?XaC6u|1lY(_v@UDJ4X?pi*iBqFrci`B`-r?kxvgNP63vCvln<<;J^Ue z^BW|EfbO`Ix0;###C zhjdqvR9)ZRj)m}C4pA%rS+;vP1YOa3qeKnA9qKUj^eu>UH{z z^(_B|>iH&joO>|a1Php26c7T_z$I_AtgPtAfnnDuk;;&^yya>{Xc6BBF(sy9A;y^t zaw)!#1eB}_HHX${y#wT0zmp}&(=tl55!1xM&P30qm@$C@82)qnBJ)V!prA*J(pUrF z94MMDq4Y#k5#&eKM<2hV^QAq+hcJ~KZ2q=W%NXpLqZ(;7e#=upl_j%ecK{p9su z3NXw`5c8tRRLtF>nTsNTp)tMaQx*&r!A5lVP(U~$0wt{&%>kJsT76-$_{*kHBd~$y_HWLFn0Id>*AwrH6%`K#aSdH6 z1SI(4i4~xbftVKxZQEoSH}Wabqz@n_7J~|i)74KRzL(-lIGnL1_MX&ITFSf>nxVu3 zfSSF8)tNkP+E3_un1V^)?f>xSScnQ1wbH{%9>s_7*k>7LZ9%aBWafsr1lh?W>*Jf? zu)Y57g$VaG0>u1kLcEM*cSJ~R%ogfz5VFw-4J%Fj)&6yD4%xMP$i~sxu0;_=DH5C8 zo6LHLp9!0tJv{s2L!aa==65}Q$-Y*3O~3c1$w&7Fj{N<>yNp@(No%XF1v{>=c`|ch zpuNfJy#tk%pLAasA6Q#!+R&y1t9)fstbVM4W#Xo<8EcfsyoEj#Y#NARD_D6_a&r0N zH*R@SUxsg-HVnwqgD~qp?2eB!O=Q&QcC<$${XVXJ_Y`yd7WK+mAna-`s3muihiP(A_pU z4;43`wLG)HTsp_j?Rv(+jC-#Cex04Q{o^0qaWi*5bM?VrfaUR0J1Qchh2r0(R(B3{ z2E5t(i(7={h!e}=4X@}<-Z}y=An$d*Pl$_J*P!3mQF)C0fdd{k70;3ia&v!4H}TPl zl;3o>X42OK*x|B7dsFg>8&l5Zt^0lG-jvFq4RQ;gkbzk!j;c9U`RdhpWZb<&b9@kw^z*LQ3@>{@=aB?8!VE`8)uL4 z$y&^LAF@nJX7N@1Q?SQ_PpOvO9+lf}IBnSFJ&S5W3(O_V=eha(+SB*o{!?W7T`9IK zs&~-HJXi0qRe8*u(F2d~JDE8idmF=mp+z4sqm~2*2cM6a;t{emhbLI~dtzPbzq<~H z+~{5ukT*fRK|fv*wWd`BeYT?Zieg0vV`Q!v)AFJ& zn~qqiN1TWl5jsDS%{MD>wcGXX1MSL#${le4yB+7*n7Y7Q{=xUx7yNRs%yP|~HA{Q# zXV0wHXBQ>jw*8vnvt#1}-JbUG(|*mn`L4b5SZG^x1$3_={w_{P*}=d@gEcFqfawP0ZDx6w~p&##`C|JHtU`Ov!ChSQeY zua!B@Fi@4yNZrZ0lVz6I?GBol8rR+AqknT^@1w%x^Yeklsa0`L?Veg!WVe5Ry7;5> zW@j6|RZnK!`U%54FE^%+=!KV`u3hJv4#&|aORci35+B%&Hovt|H`*S0irr-Q1JrL&Tpq+nGJnQs9q!vwXMB^u)dnl Or else `code blocks` confuse m4 +# tbls + +for expanding random table entries + +1. [Requirements](#requirements) +2. [Installing](#installing) +3. [Architecture](#architecture) +4. [Tutorial](#tutorial) + * [Random Selections](#random-selections) + * [Expansion](#expansion) + * [Filters](#filters) +5. [Roadmap](#roadmap) +6. [Resources](#resources) + +## Requirements + +- Fennel 1.3.1 on PUC Lua 5.4 +- gnu recutils 1.9: for querying metadata +- just 1.34.0: just a task runner + +## Installing + +You can run the script: `fennel src/main.fnl`. + +Or you can compile a binary and use that. +See `just compile`. + +There is also a vim plugin for the `tbls` format. +See `vim-tbls/README.md`. + +## Architecture + +- `src/story.fnl`: core of the project. where all the file handling and text parsing happens +- `src/main.fnl`: wrapper for story.fnl. the ui. +- `src/filter.fnl` logic for applying filters to strings +- `lib/*.fnl` libraries and helper functions + +![Autogenerated Dependency Graph][deps] + +[deps]: doc/deps.png "Autogenerated Dependency Graph" + +include() + +## Roadmap + +- [x] random table entries (ADDED in vHydrogen) +- [x] expanding macros (ADDED in vHydrogen) +- [x] table files plugin: syntax highlighting + folding (ADDED vHelium) +- [x] expansion filters (ADDED vHelium) +- [ ] table context + +## Resources + +inspired heavily by tracery: + + +and this list-to-html geneator from slight adjustments: + + +but also paper elemental's: + + +and perchance: + diff --git a/doc/meta.rec b/doc/v/meta.rec similarity index 100% rename from doc/meta.rec rename to doc/v/meta.rec diff --git a/doc/versions.rec b/doc/v/versions.rec similarity index 100% rename from doc/versions.rec rename to doc/v/versions.rec diff --git a/justfile b/justfile index 95eb57c..b473948 100644 --- a/justfile +++ b/justfile @@ -1,42 +1,85 @@ +set quiet + # show all recipes default: just --list --unsorted # compile binary +[group('build')] compile: fennel --compile-binary src/main.fnl tbls /usr/local/lib/liblua.a /usr/local/include/lua5.4 +alias build := compile -# run test file -_test-story: - fennel test/story.test.fnl - -# test main -_test-main: - for i in $(seq 1 10); do fennel src/main.fnl -i test/morpheme-word-epithet.txt -k name; done - -# run all tests -test: _test-main _test-story - -# bump version -bump: - #!/usr/local/bin/bash - currname=$(recsel doc/meta.rec -P version) - currnum=$(recsel doc/versions.rec -e "Name = '$currname'" -P Number) - nextnum=$((currnum + 1)) - nextname=$(recsel doc/versions.rec -e "Number = $nextnum" -P Name) - echo "Bumping version from $currname to $nextname:" - recset doc/meta.rec -f version -s $nextname - recset doc/meta.rec -f updated -S $(gdate +'%Y-%m-%d') - recsel doc/meta.rec - -# show full metadata -meta: - awk 'FNR==1{print ""}{print}' doc/*.rec | recsel -t meta -j version +# install all +[group('install')] +install: install-plugin install-binary # install vim plugin +[group('install')] install-plugin: cp -r vim-tbls/* ~/.config/nvim # install binary +[group('install')] install-binary: compile cp tbls ~/bin + +# run all tests +[group('test')] +test: test-main test-story + +# run test file +[group('test')] +test-story: + fennel test/story.test.fnl + +# test main +[group('test')] +test-main: + for i in $(seq 1 10); do fennel src/main.fnl -i test/morpheme-word-epithet.txt -k name; done + +# build all docs +[group('docs')] +docs: deps readme + +# build readme +[group('docs')] +readme: + m4 doc/src/readme.m4 > README.md + +# create dependency graph +[group('docs')] +deps: + ag require src \ + | sed 's/\(.*\.fnl\).*require :\([^\.]*\)\.\([^)]*\)).*/"\1" -> "\2\/\3.fnl"/' \ + | awk 'BEGIN { print "digraph {" } { print } END { print "}" }' \ + | dot -Tpng \ + > doc/deps.png + +# create dependency graph but sixel +[group('docs')] +depsxl: + ag require src \ + | sed 's/\(.*\.fnl\).*require :\([^\.]*\)\.\([^)]*\)).*/"\1" -> "\2\/\3.fnl"/' \ + | awk 'BEGIN { print "digraph {" } { print } END { print "}" }' \ + | dot -Tpng \ + | magick - -geometry 800 sixel:- + +# bump version +[group('docs')] +bump: + #!/usr/local/bin/bash + currname=$(recsel doc/v/meta.rec -P version) + currnum=$(recsel doc/v/versions.rec -e "Name = '$currname'" -P Number) + nextnum=$((currnum + 1)) + nextname=$(recsel doc/v/versions.rec -e "Number = $nextnum" -P Name) + echo "Bumping version from $currname to $nextname:" + recset doc/v/meta.rec -f version -s $nextname + recset doc/v/meta.rec -f updated -S $(gdate +'%Y-%m-%d') + recsel doc/v/meta.rec + +# show full metadata +[group('docs')] +meta: + awk 'FNR==1{print ""}{print}' doc/v/*.rec | recsel -t meta -j version + diff --git a/src/flib.fnl b/lib/filters.fnl similarity index 100% rename from src/flib.fnl rename to lib/filters.fnl diff --git a/src/string.fnl b/lib/string.fnl similarity index 100% rename from src/string.fnl rename to lib/string.fnl diff --git a/src/table.fnl b/lib/table.fnl similarity index 100% rename from src/table.fnl rename to lib/table.fnl diff --git a/src/filter.fnl b/src/filter.fnl index 29d7c5b..6d81223 100644 --- a/src/filter.fnl +++ b/src/filter.fnl @@ -1,9 +1,9 @@ -(local {: contains?} (require :src.table)) -(local {:split split-full} (require :src.string)) +(local {: contains?} (require :lib.table)) +(local {:split split-full} (require :lib.string)) (local split (partial split-full "[^%.]*")) (fn filter [s] - (let [filters (require :src.flib) + (let [filters (require :lib.filters) [str & funs] (split s)] (var res str) (each [_ f (ipairs funs)] diff --git a/src/main.fnl b/src/main.fnl index a509950..0c230ee 100644 --- a/src/main.fnl +++ b/src/main.fnl @@ -8,6 +8,7 @@ (print) (print "Basic Options:") (print " -h|--help print this message and exit") + (print " -v|--version print version and exit") (print " -i|--input name of input file") (print " -k|--origin-table-key name of a table in the input file") (print " -s|--origin-table-string a string template") @@ -28,6 +29,12 @@ (do (show-help) (os.exit 0)) + (where [a] (or (= a "-v") (= a "--version"))) + (let [handle (io.popen "recsel doc/meta.rec -P version") + result (handle:read "*a")] + (print result) + (handle:close) + (os.exit 0)) (where [a input] (or (= a "-i") (= a "--input"))) (set opts.input input) (where [a key] (or (= a "-k") (= a "--origin-table-key"))) diff --git a/src/story.fnl b/src/story.fnl index 2b5b68c..d5c3597 100644 --- a/src/story.fnl +++ b/src/story.fnl @@ -1,8 +1,8 @@ ;; helper funs -(local tbl (require :src.table)) +(local tbl (require :lib.table)) (local {: filter} (require :src.filter)) -(local {:split _split} (require :src.string)) -(local split (partial _split "[^%.]*")) +(local split + (partial (. (require :lib.string) :split) "[^%.]*")) (fn lines [filename callback] (case (pcall #(with-open [file (io.open filename)] (each [line (file:lines)] (callback line)))) @@ -58,7 +58,9 @@ str (do (assert (tbl.has-key? corpus word) - (string.format "Error trying to expand \"%s\". Corpus does not contain a table called \"%s\"" str word)) + (string.format + "Error trying to expand \"%s\". Corpus does not contain a table called \"%s\"" + str word)) (let [next-str (string.format "%s%s%s" (string.sub str 1 (- i 1)) (if (length fs)