{"id":26697520,"url":"https://github.com/mudasobwa/markright","last_synced_at":"2025-07-23T20:05:42.283Z","repository":{"id":52544435,"uuid":"78226770","full_name":"mudasobwa/markright","owner":"mudasobwa","description":"A customizable markdown parser in Elixir: pure pattern matching.","archived":false,"fork":false,"pushed_at":"2023-01-01T07:10:53.000Z","size":4356,"stargazers_count":17,"open_issues_count":2,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-08T08:08:14.560Z","etag":null,"topics":["ast","callback","elixir","markdown","markdown-parser","markup-language","parsing"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","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/mudasobwa.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}},"created_at":"2017-01-06T17:55:06.000Z","updated_at":"2025-02-15T11:17:28.000Z","dependencies_parsed_at":"2023-01-31T21:02:07.525Z","dependency_job_id":null,"html_url":"https://github.com/mudasobwa/markright","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/mudasobwa/markright","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudasobwa%2Fmarkright","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudasobwa%2Fmarkright/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudasobwa%2Fmarkright/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudasobwa%2Fmarkright/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mudasobwa","download_url":"https://codeload.github.com/mudasobwa/markright/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudasobwa%2Fmarkright/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266738793,"owners_count":23976472,"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","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["ast","callback","elixir","markdown","markdown-parser","markup-language","parsing"],"created_at":"2025-03-26T21:19:53.458Z","updated_at":"2025-07-23T20:05:42.241Z","avatar_url":"https://github.com/mudasobwa.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ~~Markright~~\n\n## This proecjt is retired, Please check https://github.com/am-kantox/md instead.\n\n[![Build Status](https://travis-ci.org/mudasobwa/markright.svg?branch=master)](https://travis-ci.org/mudasobwa/markright) [![Hex.pm](https://img.shields.io/hexpm/v/markright.svg)](https://hex.pm/packages/markright) **The extended, streaming, configurable markdown-like syntax parser, that produces an AST.**\n\nOut of the box is supports the full set of `markdown`, plus some extensions.\nThe user of this library might easily extend the functionality with her own\nmarkup definition and a bit of elixir code to handle parsing.\n\nStarting with version `0.5.0` supports many different markright syntaxes\nsimultaneously, including the ability to create syntaxes on the fly.\n\nThere is no one single call to `Regex` used. The whole parsing is done solely\non pattern matching the input binary.\n\nThe AST produced is understandable by [`XmlBuilder`](https://github.com/joshnuss/xml_builder).\n\nThere are many callbacks available to transform the resulting AST. See below.\n\n## Is it of any good?\n\nIt is an incredible piece of handsome lovely software. Sure, it is.\n\n## Installation\n\nIf [available in Hex](https://hex.pm/docs/publish), the package can be installed\nby adding `markright` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [{:markright, \"~\u003e 0.5\"}]\nend\n```\n\n## Basic usage\n\n```elixir\n    @input ~s\"\"\"\n    If [available in Hex](https://hex.pm/docs/publish), the package can be installed\n    by adding `markright` to your list of dependencies in `mix.exs`:\n\n    ```elixir\n    def deps do\n      [{:markright, \"~\u003e 0.5\"}]\n    end\n    ```\n\n    ## Basic Usage\n    Blah...\n    \"\"\"\n\n    assert(\n      Markright.to_ast(@input) ==\n        {:article, %{},\n           [{:p, %{}, [\n              \"If \", {:a, %{href: \"https://hex.pm/docs/publish\"}, \"available in Hex\"},\n              \", the package can be installed\\nby adding \", {:code, %{}, \"markright\"},\n              \" to your list of dependencies in \", {:code, %{}, \"mix.exs\"}, \":\"]},\n            {:pre, %{},\n              [{:code, %{lang: \"elixir\"},\n              \"def deps do\\n  [{:markright, \\\"~\u003e 0.5\\\"}]\\nend\"}]},\n            {:h2, %{}, \"Basic Usage\"},\n            {:p, %{}, \"Blah...\\n\"}]}\n    )\n```\n\n## HTML generation\n\n```elixir\niex\u003e \"Hello, *[address]Aleksei*!\"\n...\u003e |\u003e Markright.to_ast\n...\u003e |\u003e XmlBuilder.generate\n\"\u003carticle\u003e\n\\t\u003cp\u003e\n\\t\\tHello,\n\\t\\t\u003cstrong class=\\\"address\\\"\u003eAleksei\u003c/strong\u003e\n\\t\\t!\n\\t\u003c/p\u003e\n\u003c/article\u003e\"\n```\n\n## Power tools\n\n### Callbacks\n\nOne might provide a callback to the call to `to_ast/3`. It will be not only\ncalled back on any AST node found (do not expect them to be called in the\nnatural order, though,) but it _allows to change the AST on the fly_. Just\nreturn a `%Markright.Continuation` object from the callback, and you are done\n(see `markright_test.exs` for inspiration):\n\n```elixir\nfun = fn\n  %Markright.Continuation{ast: {:p, %{}, text}} = cont -\u003e\n    IO.puts \"Currently dealing with `:p` node\"\n    %Markright.Continuation{cont | ast: {:div, %{}, text}}\n  cont -\u003e cont\nend\nassert Markright.to_ast(@input, fun) == @output\n```\n\n### Example: make fancy links inside blockquotes with callbacks\n\nWhen a last blockquote’s element is a link, make it to show the favicon,\nand make the blockquote itself to have `cite` attribute (in fact, this particular\ntransform is already done in `Markright.Finalizers.Blockquote` finalizer, but if\nit were not, this is how it could be implemented internally):\n\n```elixir\nbq_patch = fn\n  {:blockquote, bq_attrs, list} when is_list(list) -\u003e\n    case :lists.reverse(list) do\n      [{:a, %{href: href} = attrs, text} | t] -\u003e\n        img = with [capture] \u003c- Regex.run(~r|\\Ahttps?://[^/]+|, href) do\n          {:img,\n              %{alt: \"favicon\",\n                src: capture \u003c\u003e \"/favicon.png\",\n                style: \"height:16px;margin-bottom:-2px;\"},\n              nil}\n        end\n        patched = :lists.reverse([{:br, %{}, nil}, \"— \", img, \" \", {:a, attrs, text}])\n        {:blockquote, Map.put(bq_attrs, :cite, href), :lists.reverse(patched ++ t)}\n      _ -\u003e {:blockquote, bq_attrs, list}\n    end\n  other -\u003e other\nend\nfun = fn %Markright.Continuation{ast: ast} = cont -\u003e\n  %Markright.Continuation{cont | ast: bq_patch.(ast)}\nend\n```\n\n### Custom classes\n\nAll the “grip” elements (like `*strong*` or `~strike~`) have an option to specify\na class:\n\n```elixir\niex\u003e Markright.to_ast \"Hello, *[address]Aleksei*!\"\n{:article, %{},\n  [{:p, %{}, [\"Hello, \", {:strong, %{class: \"address\"}, \"Aleksei\"}, \"!\"]}]}\n```\n\nThe above is particularly helpful when writing a rich blog posts over, say,\nbootstrap css (or any other css, that provides cool flashes etc.)\n\n### Custom syntax\n\nTo add a new syntax is as easy as to put a new value into `config`:\n\n```elixir\nconfig :markright, syntax: [\n  grip: [\n    sup: \"^^\"\n  ]\n]\n```\n\nVoilà—you have this grip on hand:\n\n```elixir\niex\u003e Markright.to_ast \"Hello, ^^Aleksei^^!\"\n{:article, %{},\n  [{:p, %{}, [\"Hello, \", {:sup, %{}, \"Aleksei\"}, \"!\"]}]}\n```\n\n### Ninja handling: collectors\n\nCollectors play the role of accumulators, used for accumulating some data\nduring the parsing stage. The good example of it would be\n`Markright.Collectors.OgpTwitter` collector, that is used to build the\ntwitter/ogp card to embed into head section of the resulting html.\n\n```elixir\n  test \"builds the twitter/ogp card\" do\n    Code.eval_string \"\"\"\n    defmodule Sample do\n      use Markright.Collector, collectors: Markright.Collectors.OgpTwitter\n\n      def on_ast(%Markright.Continuation{ast: {tag, _, _}} = cont), do: tag\n    end\n    \"\"\"\n    {ast, acc} = Markright.to_ast(@input, Sample)\n    assert {ast, acc} == {@output, @accumulated}\n    assert XmlBuilder.generate(acc[Markright.Collectors.OgpTwitter]) == @html\n  after\n    purge Sample\n  end\n```\n\n### Custom syntax on the fly\n\nStarting with version `0.5.0`, markright accepts custom syntax to be passed\nto `Markright.to_ast/3`:\n\n```elixir\n@input ~S\"\"\"\nHello world.\n\n\u003e my blockquote\n\nRight _after_.\nNormal *para* again.\n\"\"\"\n```\n\nEmpty syntax will produce a set of paragraphs, ignoring anything else:\n\n```elixir\n@empty_syntax []\n@output_empty_syntax {:article, %{}, [\n  {:p, %{}, \"Hello world.\"},\n  {:p, %{}, \"\u003e my blockquote\"},\n  {:p, %{}, \"Right _after_.\\nNormal *para* again.\\n\"}]}\n\ntest \"works with empty syntax\" do\n  assert Markright.to_ast(@input, nil, syntax: @empty_syntax) == @output_empty_syntax\nend\n```\n\nThe simple syntax below accepts emphasized and bold text decorators only:\n\n```elixir\n@simple_syntax [grip: [em: \"_\", strong: \"*\"]]\n@output_simple_syntax {:article, %{}, [\n  {:p, %{}, \"Hello world.\"},\n  {:p, %{}, \"\u003e my blockquote\"},\n  {:p, %{}, [\"Right \", {:em, %{}, \"after\"}, \".\\nNormal \", {:strong, %{}, \"para\"}, \" again.\\n\"]}]}\n\ntest \"works with simple user-defined syntax\" do\n  assert Markright.to_ast(@input, nil, syntax: @simple_syntax) == @output_simple_syntax\nend\n```\n\n### Syntax reference\n\n#### **`block`** is a block element, roughly an analogue of HTML `\u003cdiv\u003e`:\n\n_Example:_\n```elixir\nblock: [h: \"#\", blockquote: \"\u003e\"]\n```\n\n_Markright:_\n```elixir\n# Hello, world!\n```\n\n_Result:_\n```elixir\n{:h1, %{}, \"Hello, world!\"]\n```\n\n#### **`flush`** is an empty element, roughly an analogue of HTML `clear: all`:\n\n_Example:_\n```elixir\nflush: [hr: \"\\n---\", br: \"  \\n\"]\n```\n\n_Markright:_\n```elixir\nHello, world!\n---\nHello, world!\n```\n\n_Result:_\n```elixir\n[\"Hello, world!\", {:hr, %{}, nil}, \"\\nHello, world!\\n\"]\n```\n\n#### **`lead`** is an item element, usually chained and having a surrounding (see below):\n\n_Example:_\n```elixir\nlead: [li: {\"-\", [parser: Markright.Parsers.Li]}]\n```\n\n_Markright:_\n```elixir\n- Hello, world!\n- Hello, world!\n```\n\n_Result:_\n```elixir\n{:ul, %{}, [{:li, %{}, \"Hello, world!\"}, {:li, %{}, \"Hello, world!\"}]}\n```\n\n#### **`surrounding`** the element to surround `lead`s (see above):\n\n_Example:_\n```elixir\nsurrounding: [li: :ul]\n```\n\n_Markright:_ **none**\n\n_Result:_ **see `li` above**\n\n#### **`magnet`** is a leading marker:\n\n_Example:_\n```elixir\nmagnet: [tag: \"#\", youtube: \"✇\"]\n```\n\n_Markright:_\n```elixir\nHello, #world! Check this video: ✇http://youtu.be/AAAAAA\n```\n\n_Result:_\n```elixir\n[\"Hello, \",\n  {:a, %{class: \"tag\", href: \"/tags/world!\"}, \"world!\"},\n  \" Check this video: \",\n  {:iframe,\n     %{allowfullscreen: nil, frameborder: 0, height: 315,\n       src: \"http://www.youtube.com/embed/AAAAAA\", width: 560},\n   \"http://www.youtube.com/embed/AAAAAA\"}]\n```\n\n#### **`grip`** is an inline surrounding element, usually used for inline formatting:\n\n_Example:_\n```elixir\ngrip: [i: \"_\", b: \"*\"]\n```\n\n_Markright:_\n```elixir\nHello, *world*!\n```\n\n_Result:_\n```elixir\n[\"Hello, \", {:strong, %{}, \"world\"}, \"!\"]\n```\n\n#### **`custom`** is a custom formatter, fully relying on the implementing module:\n\n_Example:_\n```elixir\ncustom: [img: \"![\"]\n```\n\n_Markright:_\n```elixir\nHi, ![my title](http://oh.me/image)!\n```\n\n_Result:_\n```elixir\n[\"Hi, \",\n  {:figure, %{},\n   [{:img, %{alt: \"my title\", src: \"http://oh.me/image\"}, nil},\n    {:figcaption, %{}, \"my title\"}]},\n  \"!\"]\n```\n\n---\n\n### Development\n\nThe extensions to syntax are not supposed to be merged into trunk. I am thinking\nabout creating a welcome plugin-like infrastructure. Suggestions are very welcome.\n\n## Documentation\n\nVisit [HexDocs](https://hexdocs.pm). Our docs can\nbe found at [https://hexdocs.pm/markright](https://hexdocs.pm/markright).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmudasobwa%2Fmarkright","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmudasobwa%2Fmarkright","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmudasobwa%2Fmarkright/lists"}