{"id":26298828,"url":"https://github.com/jerems/prose","last_synced_at":"2025-08-26T22:05:47.608Z","repository":{"id":42441149,"uuid":"301409368","full_name":"JeremS/prose","owner":"JeremS","description":"An alternate syntax for Clojure inspired by Pollen.","archived":false,"fork":false,"pushed_at":"2023-06-11T14:53:39.000Z","size":232,"stargazers_count":45,"open_issues_count":2,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-14T11:04:24.404Z","etag":null,"topics":["clojure","clojurescript"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JeremS.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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}},"created_at":"2020-10-05T12:58:18.000Z","updated_at":"2024-12-20T02:28:40.000Z","dependencies_parsed_at":"2024-01-15T03:59:02.219Z","dependency_job_id":null,"html_url":"https://github.com/JeremS/prose","commit_stats":{"total_commits":81,"total_committers":2,"mean_commits":40.5,"dds":"0.012345679012345734","last_synced_commit":"c066e96e534ad0b44b63fe0c687fb63a94e06fb0"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/JeremS/prose","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeremS%2Fprose","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeremS%2Fprose/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeremS%2Fprose/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeremS%2Fprose/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JeremS","download_url":"https://codeload.github.com/JeremS/prose/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeremS%2Fprose/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261282320,"owners_count":23134940,"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":["clojure","clojurescript"],"created_at":"2025-03-15T06:37:04.609Z","updated_at":"2025-06-22T11:08:48.403Z","avatar_url":"https://github.com/JeremS.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Prose\n\nAlternate syntax for Clojure, similar to what [Pollen](https://github.com/mbutterick/pollen) brings to [Racket](https://racket-lang.org/).\n\n## Installation\n```clojure\n{io.github.jerems/prose {:git/tag \"v83\", :git/sha \"08fe24e3c1\"}}\n```\n\n## Usage\nThe main idea is to have programmable documents in Clojure. To do so, Prose\nflips the relationship between plain text and code. In a clojure file, text is\nassumed to be code except in special cases like strings and comments.\nIn prose, text is assumed to be just plain text except in special cases i.e.\nclojure code.\n\n### Syntax\nProse provides a reader similar to what we can find in [Pollen](https://github.com/mbutterick/pollen). Text is\neither plain text or a special construct. All special constructs begin with\nthe character `◊`(lozenge).\n\n#### Clojure calls:\nThe text:\n```text\nWe can call ◊(str \"code\") in text\n```\nreads as:\n```clojure\n[\"We can call \" (str \"code\") \" in text\"]\n```\n\n#### Clojure symbols:\nThe text:\n```text\nWe can use symbols ◊|some-symbol\n```\nreads as:\n```clojure\n[\"We can use symbols \" some-symbol]\n```\n\n#### Tag function:\nThe text:\n```text\nThere is a tag function syntax looking like:\n◊div[{:class \"grid\"}]{ some content}\n◊div{ some ◊em{content}}\n\nor even:\n◊str{text}\n```\nreads as:\n```clojure\n[\"There is a tag function syntax looking like:\\n\" (div {:class \"grid\"} \" some content\") \"\\n\" (div \" some \" (em \"content\")) \"\\n\\nor even:\\n\" (str \"text\")]\n```\n\n- clojure code argument in brackets\n- text argument in braces\n\n#### Escaped / verbatim text:\nThe text:\n```text\nThe ◊\"◊\" character.\n```\nreads as:\n```clojure\n[\"The \" \"◊\" \" character.\"]\n```\n\n\n### Documents as programs\nTo get programmable documents prose provides several apis that are meant to\nwork together. We have:\n- a reader turning text into code as data\n- an API help to evaluate that data using Clojure's eval capabilities\n- an API to compile the result of evaluations into the final document\n\nLet's see the whole process in action. We start by requiring the necessary\napis and setting up a little helper:\n```clojure\n\n(require '[clojure.java.io :as io])\n(require '[clojure.pprint :as pp])\n(require '[fr.jeremyschoffen.prose.alpha.reader.core :as reader])\n(require '[fr.jeremyschoffen.prose.alpha.eval.common :as eval-common])\n(require '[fr.jeremyschoffen.prose.alpha.out.html.compiler :as html-compiler])\n\n```\n\n```clojure\n\n(defn display [x]\n  (with-out-str\n    (pp/pprint x)))\n\n```\n\nThis is the document we are using for our example:\n```text\n◊(require '[fr.jeremyschoffen.prose.alpha.out.html.tags :refer [div ul li]])\n\n◊div{\n  some text\n  ◊ul {\n    ◊li {1}\n    ◊li {2}\n  }\n}\n```\n\nLet's read it:\n```clojure\n\n(def document\n  (-\u003e \"fr/jeremyschoffen/prose/alpha/docs/pages/readme/example-doc.html.prose\"\n    io/resource\n    slurp\n    reader/read-from-string))\n\n(display document)\n\n```\n;=\u003e\n```clojure\n[(require\n  '[fr.jeremyschoffen.prose.alpha.out.html.tags :refer [div ul li]])\n \"\\n\\n\"\n (div\n  \"\\n  some text\\n  \"\n  (ul \"\\n    \" (li \"1\") \"\\n    \" (li \"2\") \"\\n  \")\n  \"\\n\")]\n\n```\n\nEval it:\n```clojure\n\n(def evaled-document (eval-common/eval-forms-in-temp-ns document))\n\n(display evaled-document)\n\n```\n;=\u003e\n```clojure\n[nil\n \"\\n\\n\"\n {:tag :div,\n  :content\n  [\"\\n  some text\\n  \"\n   {:tag :ul,\n    :content\n    [\"\\n    \"\n     {:tag :li, :content [\"1\"], :type :tag}\n     \"\\n    \"\n     {:tag :li, :content [\"2\"], :type :tag}\n     \"\\n  \"],\n    :type :tag}\n   \"\\n\"],\n  :type :tag}]\n\n```\n\nCompile it to html:\n```clojure\n\n(html-compiler/compile! evaled-document)\n\n```\n;=\u003e\n```clojure\n\n\n\u003cdiv\u003e\n  some text\n  \u003cul\u003e\n    \u003cli\u003e1\u003c/li\u003e\n    \u003cli\u003e2\u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/div\u003e\n```\n\nThere are some helpers to make this process easier:\n```clojure\n\n(require '[fr.jeremyschoffen.prose.alpha.document.clojure :as doc])\n\n```\n```clojure\n\n(defn slurp-doc [path]\n  (-\u003e path\n    io/resource\n    slurp))\n\n(def evaluate (doc/make-evaluator {:slurp-doc slurp-doc\n                                   :read-doc reader/read-from-string\n                                   :eval-forms eval-common/eval-forms-in-temp-ns}))\n(-\u003e \"fr/jeremyschoffen/prose/alpha/docs/pages/readme/example-doc.html.prose\"\n  evaluate\n  html-compiler/compile!)\n\n```\n;=\u003e\n```clojure\n\n\n\u003cdiv\u003e\n  some text\n  \u003cul\u003e\n    \u003cli\u003e1\u003c/li\u003e\n    \u003cli\u003e2\u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/div\u003e\n```\n\nThe namespaces `fr.jeremyschoffen.prose.alpha.document.*` provide more\nfunctionality than just composing `slurp`, `read` and `eval` functions.\nThe `make-evaluator` functions there sets up the possibility\nfor documents to import other documents, passing input data to documents...\n\n\n## The ◊ (lozenge) character\nOne of the first question that came to mind when I discovered [Pollen](https://github.com/mbutterick/pollen) was:\nwhy this `◊` character? I expect the same question will arise for this\nproject.\n\n[Pollen](https://github.com/mbutterick/pollen) and Prose use `◊` for several reasons. Mainly this character\nisn't used as a special character in programming languages. To stick to\nClojure, characters like `@`, `#` or even `\u0026` have special meaning.\n`◊` not being used either in clojure nor very much in plain text allows\nus to have expressions such as:\n```text\n◊(defn template [v]\n   ◊div { Some value: ◊|v})\n```\n\nIn this example there is prose syntax used inside clojure code without\nambiguity. Using the `@` as [Scribble](https://docs.racket-lang.org/scribble/index.html) does would cause problems:\n```text\n@(defn template [v]\n   @div { Some value: @|v})\n```\n\nIn that case which `@` hold Prose's meaning and which are a `deref` reader\nmacro? Using `◊` gets us out of most of these problems. When we want to\nuse `◊` as text we can use the escaping/verbatim syntax `◊\"◊\"`.\nAlso this:\n```text\n◊(str \"◊\")\n```\nbehaves as you'd expect, the ◊ insisde the clojure string isn't special.\nThat should be the extent of our troubles with this character.\n\nFor reference here is the answer in the case of pollen from\n[its documentation](https://docs.racket-lang.org/pollen/pollen-command-syntax.html#%28part._the-lozenge%29).\n\n\n## Clojure vs sci evaluation\nCurrently Prose provides 2 apis to evaluate code. The first one uses Clojure's\neval function. The second uses [Sci](https://github.com/borkdude/sci).\n\nThere are pros and cons to each approach.\n\n### Clojure\nPros:\n- An evaluation can use anything that is in the classpath making requiring\n  namespaces easier.\n- The api may generally be a bit easier to use.\n\nCons:\n- An evaluation can use anything that is in the classpath which isn't secure.\n- I believe clojure doesn't allow code ran outside of its main thread to\n  create / destroy namespaces.\n- Porting that functionality to Clojurescript requires going self hosted\n  (eval needs to be there somehow).\n\n### Sci\nPros:\n- Runs in clojure and clojurescript\n- Bringing it's own reifed environment, [Sci](https://github.com/borkdude/sci) evaluations can easily happen in\n  several threads.\n- Allows us to sandbox what's accessible to the code / document being evaluated.\n\nCons:\n- May be a bit of a perf hit.\n- Managing the sci context makes for an api not as easy to use.\n\n### Limitations\nAt the moment using Clojure's shortened syntax for namespace qualified keywords\nis a bit tricky to use, it requires knowledge of namespace aliases before\nreading documents. The main reader function, using\n[edamame](https://github.com/borkdude/edamame) under the hood accepts\noptions passed down to edamame allowing this shortened syntax.\n(see the docstring of `fr.jeremyschoffen.prose.alpha.reader.core/read-from-string` and\n[edamame's docs](https://github.com/borkdude/edamame#auto-resolve))\n\n\n## Mentions\nThis work is of course inspired and influenced by [Pollen](https://github.com/mbutterick/pollen) and [Scribble](https://docs.racket-lang.org/scribble/index.html).\nThe [enlive](https://github.com/cgrand/enlive) library and ClojureScript are also\na big source of inspiration where document compilation is concerned.\n\n## License\n\nCopyright © 2020 Jeremy Schoffen.\n\nDistributed under the Eclipse Public License v 2.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjerems%2Fprose","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjerems%2Fprose","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjerems%2Fprose/lists"}