{"id":18439063,"url":"https://github.com/demonstrandum/seam","last_synced_at":"2025-04-07T21:32:11.644Z","repository":{"id":96636539,"uuid":"273826891","full_name":"Demonstrandum/seam","owner":"Demonstrandum","description":"Symbolic Expressions As Markup","archived":false,"fork":false,"pushed_at":"2024-12-28T18:09:34.000Z","size":303,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-23T00:51:20.038Z","etag":null,"topics":["css","html","lisp","macros","markup","sexp","sgml","symbolic-expressions","text-processing","transpiler","xml"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Demonstrandum.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-21T03:26:50.000Z","updated_at":"2024-12-28T18:09:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"8a02d3e1-de37-4c4c-8c94-e0291e1d91f3","html_url":"https://github.com/Demonstrandum/seam","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Demonstrandum%2Fseam","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Demonstrandum%2Fseam/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Demonstrandum%2Fseam/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Demonstrandum%2Fseam/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Demonstrandum","download_url":"https://codeload.github.com/Demonstrandum/seam/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247732641,"owners_count":20986893,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","html","lisp","macros","markup","sexp","sgml","symbolic-expressions","text-processing","transpiler","xml"],"created_at":"2024-11-06T06:23:14.264Z","updated_at":"2025-04-07T21:32:11.634Z","avatar_url":"https://github.com/Demonstrandum.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SEAM\n\n\u003e **S**ymbolic **E**xpressions **A**s **M**arkup.\n\n## Why\n\nBecause all markup is terrible, especially XML/SGML and derivatives.\n\nBut mainly, for easier static markup code generation, such as by\nmacros and code includes and such.\n\n## Try it out\n\nThis may be used as a Rust library, such as from within a server,\ngenerating HTML (or any other supported markup) before it is served to the\nclient.  Personally, I just use the `seam` binary to statically\ngenerate my personal websites through a Makefile.\n\nRead the [USAGE.md](USAGE.md) file for code examples and documentation.\n\n### Current Formats\n\n - XML (`--xml`; including: SVG, MathML)\n - HTML (`--html`; SGML)\n - CSS (`--css`)\n - SExp (`--sexp`; S-expression, basically a macro expansion utility)\n - Plain Text (`--text`; renders escaped strings to text)\n\n### Installation\n\nYou may clone the repo, then build and install by\n```sh\ngit clone git://git.knutsen.co/seam\ncd seam\ncargo build --release\ncargo install --path .\n```\n\nOr install it from [crates.io](https://crates.io/crates/seam)\n```sh\ncargo install seam\n```\n\nEither way, you'll need the Rust (nightly) compiler and along\nwith it, comes `cargo`.\n\n### Using The Binary\n\nYou may use it by passing in a file and piping from STDOUT.\n```sh\nseam test.sex --html \u003e test.html\n```\n\n`test.sex` contains your symbolic-expressions, which is used to generate\nHTML, saved in `test.html`.\n\nLikewise, you may read from STDIN\n```sh\nseam --html \u003c example.sex \u003e example.html\n# ... same as\ncat example.sex | seam --html \u003e example.html\n```\nYou may also use here-strings or here-docs, if your shell\nsupports it.\n```sh\nseam --html \u003c\u003c\u003c \"(p Hello World)\"\n#stdout:\n#   \u003c!DOCTYPE html\u003e\n#   \u003chtml\u003e\n#   \u003chead\u003e\u003c/head\u003e\n#   \u003cbody\u003e\n#   \u003cp\u003eHello World\u003c/p\u003e\n#   \u003c/body\u003e\n#   \u003c/html\u003e\n```\n```sh\nseam --html --nodocument \u003c\u003c\u003c \"(p Hello World)\"\n#stdout:\n#   \u003cp\u003eHello World\u003c/p\u003e\n```\n```sh\nseam --xml \u003c\u003c\u003c '(para Today is a day in (%date \"%B, year %Y\").)'\n#stdout:\n#   \u003c?xml version=\"1.0\" encoding=\"UTF-8\" ?\u003e\n#   \u003cpara\u003eToday is a day in November, year 2020.\u003c/para\u003e\n```\n```sh\nseam --sexp \u003c\u003c\u003c '(hello (%define subject world) %subject)'\n#stdout:\n#   (hello world)\n```\n\n## Checklist\n - [ ] Literate mode for parser+lexer, where string nodes are not escaped and parentheses are converted to symbols.\n       The only time strings are escaped and parentheses make lists is inside a `(%` and `)` pair, i.e. when calling a macro.\n       So `hello world (earth) (%do (p letter \"\\x61\")) \"\\x61\"` turns in to (in HTML mode)\n       `hello world \u003cearth\u003e\u003c/earth\u003e \u003cp\u003ehi a\u003c/p\u003e a` normally, but in *literate* (HTML) mode turns into\n       `hello world (earth) \u003cp\u003eletter a\u003c/p\u003e \"\\x61\"`. Parentheses and quotes have been preserved.\n       Markdown source in `(%markdown ...)` should be parsed as literate files by default.\n       Provide command line `--literate` option; `%include` and `%embed` should also have options for enabling literate mode.\n - [ ] Way to match on unknown keywords in attributes, examples when `(%define dict (:a 1 :b 2 :c 3))`:\n       - `(%for (kw val) in (%items  %dict) (%log %kw = %val))`\n       - `(%for  kw      in (%keys   %dict) (%log %kw = (%get %kw %dict)))`\n       - `(%for     val  in (%values %dict) (%log ___ = %val))`\n - [ ] Extend `%get` to work with slicing `(%get (1 3) (a b c d e))` becomes `b c d`; negative indices `(%get -1 (a b c))` becomes `c`.\n - [ ] Shell-style `${var:...}` string manipulation.\n - [ ] `%while`, `%take`, `%drop`, `%split` on symbols in lists, `%intercalate`.\n - [ ] `(%basename :suffix \"txt\" /path/file.txt)` (-\u003e `file`), `(%dirname /path/file.txt)` (-\u003e `/path`) and `(%extension /path/file.txt)` (-\u003e `txt`), macros for paths.\n - [ ] Math operators: `+`, `-`, `*`, `/`, `mod`, `pow`, `exp`, `sqrt`, `log`, `ln`, `hypot`.\n - [ ] User `(%error msg)` macro for aborting compilation.\n - [x] List reverse macro `(%reverse (...))`.\n - [x] Literal/atomic conversion macros: `(%symbol lit)`, `(%number lit)`, `(%string lit)`, `(%raw lit)`.\n - [x] Sorting macro `(%sort (...))` which sorts alphanumerically on literals.\n       Allow providing a `:key` to sort \"by field\": e.g. sort by title name `(%sort :key (%lambda ((:title _ \u0026\u0026_)) %title) %posts)`\n - [x] Extend the strftime-style `(%date)` to be able to read UNIX numeric timestamps and display relative to timezones.\n       Add complementary strptime-style utility `(%timestamp)` to convert date-strings to timestamps (relative to a timezone).\n - [x] Pattern-matching `(%match expr (pat1 ...) (pat2 ...))` macro.\n       Pattern matching is already implemented for `%define` internally.\n - [x] The trailing keyword-matching operator. `\u0026\u0026rest` matches excess keyword.\n       Extracting a value from a map `(:a 1 :b 2 :c 3)` is done with:\n       `(%match %h ((:b default \u0026\u0026_) %b))`.\n - [x] `%get` macro: `(%get b (:a 1 :b 2))` becomes `2`; `(%get 0 (a b c))` becomes `a`.\n - [x] `(%yaml \"...\")`, `(%toml \"...\")` and `(%json \"...\")` converts\n       whichever config-lang definition into a seam `%define`-definition.\n - [x] `(%do ...)` which just expands to the `...`; the identity function.\n - [ ] Catch expansion errors: `(%try :catch index-error (%do code-to-try) :error the-error (%do caught-error %the-error))`.\n - [x] Implement `(%strip ...)` which evaluates to the `...` without any of the leading whitespace.\n - [x] Implement *splat* operation: `(%splat (a b c))` becomes `a b c`.\n - [x] `(%define x %body)` evaluates `%body` eagerly (at definition),\n       while `(%define (y) %body)` only evaluates `%body` per call-site `(%y)`.\n - [x] Namespace macro `(%namespace ns (%include \"file.sex\"))` will prefix all definitions in its body with `ns/`, e.g. `%ns/defn`.\n       Allows for a customizable separator, e.g. `(%namespace ns :separator \"-\" ...)` will allow for writing `%ns-defn`.\n       Otherwise, the macro leaves the content produced by the body completely unchanged.\n - [x] Command line `-I` include directory.\n - [x] First argument in a macro invocation should have its whitespace stripped.\n - [x] `(%os/env ENV_VAR)` environment variable macro.\n - [ ] Lazy evaluation for *user* macros (like in `ifdef`) with use of new `(%eval ...)` macro.\n - [x] `(%apply name x y z)` macro which is equivalent to `(%name x y z)`.\n - [x] `(%lambda (x y) ...)` macro which just evaluates to an secret symbol, e.g. `__lambda0`.\n       used by applying `%apply`, e.g. `(%apply (%lambda (a b) b a) x y)` becomes `y x`\n - [x] `(%string ...)`, `(%join ...)`, `(%map ...)`, `(%filter ...)` macros.\n - [x] `(%concat ...)` which is just `(%join \"\" ...)`.\n - [x] Add options to `%glob` for sorting by type, date(s), name, etc.\n - [x] `(%format \"{}\")` macro with Rust's `format` syntax. e.g. `(%format \"Hello {}, age {age:0\u003e2}\" \"Sam\" :age 9)`\n - [x] Add `(%raw ...)` macro which takes a string and leaves it unchanged in the final output.\n - [ ] `(%formatter/text ...)` can take any seam (sexp) source code, for which it just embeds the expanded code (plain-text formatter).\n - [ ] `(%formatter/html ...)` etc. which call the respective available formatters.\n - [ ] Implement lexical scope by letting macros store a copy of the scope they were defined in (or a reference?).\n - [x] `(%embed \"/path\")` macro, like `%include`, but just returns the file contents as a string.\n - [x] Variadic arguments via `\u0026rest` syntax.\n - [ ] Type-checking facilities for user macros.\n - [x] `%list` macro which expands from `(%list %a %b %c)` to `( %a %b %c )` but *without* calling `%a` as a macro with `%b` and `%c` as argument.\n - [x] `%for`-loop macro, iterating over `%list`s.\n - [x] `%glob` which returns a list of files/directories matching a glob.\n - [x] `%markdown` renders Markdown given to it as `%raw` html-string.\n - [x] Add keyword macro arguments.\n - [ ] Caching or checking time-stamps as to not regenerate unmodified source files.\n - [ ] HTML object `style=\"...\"` object should handle s-expressions well, (e.g. `(p :style (:color red :border none) Hello World)`)\n - [ ] Add more supported formats (`JSON`, `JS`, `TOML`, \u0026c.).\n - [ ] Allow for arbitrary embedding of code with their REPLs, that can be run by\n   a LISP interpreter (or any other language), for example.  (e.g. `(%chez (+ 1 2))` executes\n   `(+ 1 2)` with Chez-Scheme LISP, and places the result in the source (i.e. `3`).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdemonstrandum%2Fseam","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdemonstrandum%2Fseam","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdemonstrandum%2Fseam/lists"}