{"id":13681960,"url":"https://github.com/askonomm/clarktown","last_synced_at":"2025-04-04T17:05:54.362Z","repository":{"id":40318045,"uuid":"433643993","full_name":"askonomm/clarktown","owner":"askonomm","description":"A zero-dependency Markdown parser.","archived":false,"fork":false,"pushed_at":"2024-10-12T18:21:51.000Z","size":104,"stargazers_count":86,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T16:05:30.453Z","etag":null,"topics":["clojure","markdown"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/askonomm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2021-12-01T01:32:12.000Z","updated_at":"2025-02-02T20:03:05.000Z","dependencies_parsed_at":"2024-11-12T01:31:46.035Z","dependency_job_id":"a6f40b0f-8670-47c9-89c2-ee18b6d992bb","html_url":"https://github.com/askonomm/clarktown","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askonomm%2Fclarktown","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askonomm%2Fclarktown/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askonomm%2Fclarktown/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askonomm%2Fclarktown/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/askonomm","download_url":"https://codeload.github.com/askonomm/clarktown/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247217174,"owners_count":20903008,"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","markdown"],"created_at":"2024-08-02T13:01:38.391Z","updated_at":"2025-04-04T17:05:54.335Z","avatar_url":"https://github.com/askonomm.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"# Clarktown\n\n[![Tests](https://github.com/askonomm/clarktown/actions/workflows/tests.yml/badge.svg)](https://github.com/askonomm/clarktown/actions/workflows/tests.yml)\n\nAn extensible and modular zero-dependency, pure-Clojure Markdown parser.\n\n## Installation\n\n#### Leiningen/Boot\n\n```\n[com.github.askonomm/clarktown \"2.0.1\"]\n```\n\n#### Clojure CLI/deps.edn\n\n```\ncom.github.askonomm/clarktown {:mvn/version \"2.0.1\"}\n```\n\n## Basic usage example\n\n```clojure\n(ns myapp.core\n  (:require [clarktown.core :as clarktown]))\n\n(clarktown/render \"**Hello, world!**\") ; =\u003e \u003cstrong\u003eHello, world!\u003c/strong\u003e\n```\n\n## Built-in parsers\n\nAt its core, Clarktown is nothing more than a collection of parsers that collectively make up a Markdown parser. Those parsers are:\n\n- `bold`\n- `code-block`\n- `empty-block`\n- `heading-block`\n- `horizontal-line-block`\n- `inline-code`\n- `italic`\n- `link-and-image`\n- `list-block`\n- `paragraph-block`\n- `quote-block`\n- `strikethrough`\n\nSo whenever you call `clarktown.core/render`, those parsers are applied to the content you give it, and are defined collectively in \n`clarktown.renderers/parsers`. If you want to remove certain parsers feel free to duplicate that vector with whatever combination of \nparsers that works best for you, and pass it as the second parameter to the `clarktown.core/render` function, like so:\n\n```clojure\n(clarktown.core/render \"**Hello, world!**\" [..parsers-go-here..])\n```\n\n## Create your own parsers\n\nAs Clarktown is modular, you can easily create your own custom parsers as well. To see how the parsers are made, I really recommend \nchecking any of the existing parsers you can find in the `clarktown.renderers/*` namespaces and how they are used in the `clarktown.renderers/parsers` variable, \nbut overall the idea is very simple: there are (potential) matchers, and renderers.\n\nHowever, before we get into matchers and renderers, let me quickly explain how Clarktown does its thing. Clarktown splits the entire Markdown \ncontent you give it into blocks. Each paragraph would be its own block, each quote or heading would be its own block, and so on, basically \nwhenever you create a newline separation between text, that creates a new block. \n\nAnd so if you create a parser that does something with the entirety of a block, you want it to have a matcher. A function that returns a boolean `true`\nwhen it detects that it's the kind of block that the parser should run on. And then there's the renderer, the parser does its magical transformations \nin the renderer function.\n\nThere are also inline parsers, such as for bold or italic text, and those do not need a matcher because they are intended to be mixed with block parsers\nas a supplement rather than the main thing. Case in point: code blocks. You want a code block to have a block-level parser that parses it, but you don't \nwant a code block to be able to turn text bold or italic, so you wouldn't pass it those inline parsers, whereas to a quote block you would.\n\n### Example block parser\n\n```clojure\n(defn is?\n  \"Determines whether the given block is a X block or not.\"\n  [block]\n  true)\n\n\n(defn render\n  \"Renders the X block.\"\n  [block parsers]\n  \"\")\n```\n\nIn the above example the `is?` function is the matcher, and the `render` function makes sure the given `block` gets rendered into the sort of HTML that\nit should. \n\nYou may also notice that the `render` function takes in a second argument called `parsers`, and that's the list of parsers passed down to \nClarktown, so if you needed to run the whole Clarktown on just a subset of your block or something, you could call `clarktown.parser/parse` on that piece \nand pass those `parsers` to it. In most cases, you probably don't need to do that.\n\n### Example inline parser\n\n```clojure\n(defn render\n  \"Renders all occurring italic text as italic.\"\n  [block _]\n  (loop [block block\n         matches (-\u003e (re-seq #\"_.*?_\" block)\n                     distinct)]\n    (if (empty? matches)\n      block\n      (let [match (first matches)\n            value (subs match 1 (- (count match) 1))\n            replacement (str \"\u003cem\u003e\" value \"\u003c/em\u003e\")]\n        (recur (string/replace block match replacement)\n               (drop 1 matches))))))\n```\n\nIn the above example there is no matcher function, just the `render` function. This render function goes over the entire `block` and turns all string instances\nthat match `_some text goes here_` into `\u003cem\u003esome text goes here\u003c/em\u003e`. \n\n### Register your parser\n\nNow that you have a new parser (or a set of ones) you can add it to Clarktown. Remember that the second argument of `clarktown.core/render` takes in a \nvector of maps, each map representing one collection. And so to register you could simply `conj` to the default ones, like so:\n\n```clojure\n(ns myapp.core\n  (:require \n    [clarktown.core :as clarktown]\n    [clarktown.renderers :refer [parsers]]))\n\n(def my-parser\n  {:matcher heading-block/is?\n   :renderers [bold/render\n               italic/render\n               inline-code/render\n               strikethrough/render\n               link-and-image/render\n               heading-block/render]})\n\n(clarktown/render \"**Hello, world!**\" (conj parsers my-parser))\n```\n\nAnd then `my-parser` will run if the matcher returns `true`, and it will run through all the renderer functions you give it, in order from top to bottom.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faskonomm%2Fclarktown","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faskonomm%2Fclarktown","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faskonomm%2Fclarktown/lists"}