{"id":13726240,"url":"https://github.com/jaredly/reason-macros","last_synced_at":"2026-03-11T10:31:03.219Z","repository":{"id":57185312,"uuid":"199373933","full_name":"jaredly/reason-macros","owner":"jaredly","description":"Template-based macros for Reason/OCaml","archived":false,"fork":false,"pushed_at":"2019-08-03T06:27:05.000Z","size":115,"stargazers_count":105,"open_issues_count":2,"forks_count":0,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-09-26T01:58:51.672Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jaredly.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-29T03:47:32.000Z","updated_at":"2024-11-05T00:37:05.000Z","dependencies_parsed_at":"2022-09-17T07:22:42.172Z","dependency_job_id":null,"html_url":"https://github.com/jaredly/reason-macros","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jaredly/reason-macros","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredly%2Freason-macros","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredly%2Freason-macros/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredly%2Freason-macros/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredly%2Freason-macros/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaredly","download_url":"https://codeload.github.com/jaredly/reason-macros/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredly%2Freason-macros/sbom","scorecard":{"id":506391,"data":{"date":"2025-08-11","repo":{"name":"github.com/jaredly/reason-macros","commit":"41d4e61ad6294d2d68d33664d07ee88f2ca27ebc"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.6,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 2/28 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 4 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T23:19:41.567Z","repository_id":57185312,"created_at":"2025-08-19T23:19:41.567Z","updated_at":"2025-08-19T23:19:41.567Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30378070,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-11T06:09:32.197Z","status":"ssl_error","status_checked_at":"2026-03-11T06:09:17.086Z","response_time":84,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":[],"created_at":"2024-08-03T01:02:56.636Z","updated_at":"2026-03-11T10:31:03.201Z","avatar_url":"https://github.com/jaredly.png","language":"OCaml","funding_links":[],"categories":["OCaml"],"sub_categories":[],"readme":"# reason-macros\nTemplate-based macros for Reason/OCaml!\n\n## Try it out for yourself\n\nhttps://astexplorer-macros.surge.sh , switch the language to \"Reason\" and toggle the \"Transform\" to on.\n\n## Basic examples\n\n```re\nlet%macro.toplevel ionicon = (name: capIdent, iconName: capIdent) =\u003e {\n  [%str // toplevel\n    module Eval__name = {\n      let name = \"$eval{name}\";\n      [@bs.module] [@react.component]\n      external make:\n        (\n          ~className: string=?,\n          ~fontSize: string=?,\n          ~color: string=?,\n          ~onClick: 'event =\u003e unit=?\n        ) =\u003e\n        React.element =\n        \"react-ionicons/lib/$eval{iconName}\";\n    }\n  ];\n};\n\n[%%ionicon (Link, IosLink)];\n[%%ionicon (Download, MdDownload)];\n```\nbecomes\n```re\nmodule Link = {\n  let name = \"Link\";\n  [@react.component]\n  external make:\n    (\n      ~className: string=?,\n      ~fontSize: string=?,\n      ~color: string=?,\n      ~onClick: 'event =\u003e unit=?\n    ) =\u003e\n    React.element =\n    \"react-ionicons/lib/IosLink\"\n};\nmodule Download = {\n  let name = \"Download\";\n  [@react.component]\n  external make:\n    (\n      ~className: string=?,\n      ~fontSize: string=?,\n      ~color: string=?,\n      ~onClick: 'event =\u003e unit=?\n    ) =\u003e\n    React.element =\n    \"react-ionicons/lib/MdDownload\"\n};\n```\n\n```re\nlet%macro.let opt = (pattern, value, continuation) =\u003e\n  switch (eval__value) {\n  | None =\u003e None\n  | Some(eval__pattern) =\u003e eval__continuation\n  };\n\nlet doSomethingWithOptionals = () =\u003e {\n  let%opt who = Some(\"world\");\n  let%opt greeting = Some(\"Hello\");\n  Some(greeting ++ \"\" ++ who);\n};\n```\nbecomes\n```re\nlet doSomethingWithOptionals = () =\u003e\n  switch (Some(\"world\")) {\n  | None =\u003e None\n  | Some(who) =\u003e\n    switch (Some(\"Hello\")) {\n    | None =\u003e None\n    | Some(greeting) =\u003e Some(greeting ++ \"\" ++ who)\n    }\n  };\n```\n\n```re\nlet%macro.let async = (pattern, value, continuation) =\u003e {\n  Js.Promise.then_(\n    eval__pattern =\u003e eval__continuation,\n    eval__value\n  );\n};\n\nlet doSomethingAsync = () =\u003e {\n  let%async text = fetch(\"Hello\");\n  let%async more = text-\u003ejson;\n  Js.Promise.resolve(more);\n};\n```\nbecomes\n```re\nlet doSomethingAsync = () =\u003e\n  Js.Promise.then_(\n    text =\u003e Js.Promise.then_(more =\u003e Js.Promise.resolve(more), json(text)),\n    fetch(\"Hello\"),\n  );\n```\n\n## Installation\n\n### Bucklescript\n\n`npm i reason-macros-bin`\n\n```\n  \"ppx-flags\": [\"reason-macros-bin/ppx.js\"]\n```\n\n### esy + dune\n\nadd to package.json / esy.json\n`\"reason-macros\": \"git+https://github.com/jaredly/reason-macros\"`\n\nand then in a `dune` file\n\n```\n  (preprocess (pps macros.ppx))\n```\n\n\n# !IMPORTANT! The rest of this Readme is very disorganized and wrong.\n\n\nUser-defined macros.\nWithout ppxs.\n\n```reason\n\nlet%macro.fn something = (one, two, three) =\u003e {\n  one + two + three\n};\n\n[%macro something(5, 6, 7)]\n\n// I would like to support spreads (rest arguments).\n\n// Also, macro-ify a record literal\n// Or a js object literal\n\n// Allow special things like [%FILENAME] and [%LINENO] and stuff\n// Also would like a way to include a representation of the expression as text or something.\n\nlet%macro.switch thing = (value, cases) =\u003e {\n  // hrmmm\n  // is there a way to allow pseudo-procedures?\n  // not suer.\n  // Like going through each case and doing something to it.\n  [%map cases(({pattern, condition, body}) =\u003e {\n    [%case (pattern, condition, body)]\n  })]\n}\n\n\nlet%macro.let opt = (pattern, value, continuation) =\u003e {\n  switch value {\n    | Some(pattern) =\u003e continuation\n    | None =\u003e None\n  }\n};\n\nlet%macro.let async = (pattern, value, continuation) =\u003e {\n  Js.Promise.then_(value, pattern =\u003e continuation)\n};\n\nlet%macro.async awesome = 10;\n\n\n```\n\n\n\n- Simple macros\n  - List of magics\n    - for all macros\n      - \n\n    - for decorator macros\n      - `payload` is the thing we're attached to. Will have to get fancy for local bindings, how to include just the binding and not the continuation\n\n      - for a binding\n        - `payload.binding.name` will give you the name as an identifier token\n          - will throw exception if it's attached to not-a-binding\n\n        - `payload.binding.value` will give you the stuff to the right of the equals\n\n        - `payload.binding.continuation` will give you the stuff after the binding, if we're in an expression.\n\n        - `payload.binding.value.args` will give you the args? if the value is a function. not sure how you would manipulate the args \n\n      - for a type\n        - `payload.type.name` the name\n\n        - `payload.type.manifest` the manifest\n\n        - not sure what you can do with it.\n\n        - ```\n[%macro.decorator.withList\n[%payload];\ntype arrayThing = array([%payload.type.name])\n]\n```\n\n  - Things to figure out\n    - how to determine whether to replace the target (when it's a decorator) or not? maybe always replace. So will have to specify that it's a decorator.\n      - macro.decorator\n\n    - Do I want to be able to do any logic? maybe with like an `[%conditional if ([%arg \"name\"] == \"awesome\") { ... } else { ... } ]`. Yeah! call it `@eval` or `@preval`\n      - would have a fairly limited set of comparisons you could do.\n\n      - Exercise: can I reproduce conditional compilation? I'll want a way to rect to ENV vars\n        - ```\n[%macro.decorator.native\n  if%eval ([%env \"bsb-backend\"] == \"native\") { [%payload] }\n];\n[%macro.decorator.js\n  if%eval ([%env \"bsb-backend\"] == \"js\") { [%payload] }\n];\n \n[@native]\nlet platform = \"native\";\n \n[@js]\nlet platform = \"js\";\n```\n\n      - What about a switch on multiple backends? like Platform.select\n        - ```\n[%macro.platform\n  switch%eval ([%env \"bsb-backend\"]) {\n  | \"native\" =\u003e [%arg \"native\"]\n  | \"js\" =\u003e [%arg \"js\"]\n  }\n];\n\n\n## Examples of things I want to be able to do:\n\n### A platform macro\n```reason\nlet%macro platform = (record: record) =\u003e switch%eval ([%env \"bsb-backend\"]) {\n  | \"native\" =\u003e record.native\n  | \"js\" =\u003e record.js\n}\n\n[%platform {\n  | \"native\" =\u003e 10\n  | \"js\" =\u003e 5\n}]\n\n----\n\nlet%macro.decorator js = (payload: expression) =\u003e if%eval ([%env \"bsb-backend\"] == \"js\") { payload } else {()};\nlet%macro.decorator.str js = (payload: structure_item) =\u003e if%eval ([%env \"bsb-backend\"] == \"js\") { payload } else {()};\n\n```\n\n### let_ppx let%Anything style\n\n```reason\nlet%macro.let async = (pattern, value, continuation) =\u003e {\n  Js.Promise.then_(value, pattern =\u003e continuation)\n}\n\nlet%async {something} = aPromise;\nJs.Promise.resolve(ok)\n\n-\u003e\n\nJs.Promise.then_(aPromise, ({something}) =\u003e Js.Promise.resolve(ok))\n```\n\n```reason\nlet%macro.try async = (value, cases: rest(case)) =\u003e {\n  Js.Promise.catch(value, err =\u003e [%construct.switch (err, cases)])\n}\n\nlet caught = try%async (bPromise) {\n  | \"someText\" =\u003e Js.Promise.resolve(ok)\n  | exn =\u003e Js.Promise.reject(exn)\n};\n\n-\u003e\n\nJs.Promise.catch(bPromise, err =\u003e switch err {\n  | \"someText\" =\u003e Js.Promise.resolve(ok)\n  | exn =\u003e Js.Promise.reject(exn)\n})\n\n```\n\n\n\n### (nope) A \"super assert\"\n```reason\n[%super_assert a == 5]\n\n-\u003e if (!(a == b)) {\n  print_endline(\"a == 5\")\n  assert(false)\n}\n```\n\nor maybe\n```reason\n[%assert_equal (a, b)]\n\n-\u003e\nif (a != b) {\n  print_endline(ermmm ok I need type info here, darn)\n}\n```\n\n## Basic form\n\nMacroTypes:\n- pattern (generic pattern, can do a switch on it)\n- expression (generic epxression, can switch probably)\n\n- ident - if this is an argument that is already a pattern, then we parse a pattern ident. Otherwise an expression ident.\n- (tuple literal) translates into the corresponding tuple form, for pattern or expr\n- record - some kind of record literal. attributes can be gotten out, and will be checked at macro evaluation time. Can also get the fields as array((ident, expr)), and the rest arg maybe?\n- js_object - a js object literal\n- string (string literal)\n- int (int literal)\n- float (float literal)\n- poly_variant - `\u0026backtick;Awesome`\n  - dunno about args. Might be nice to switch on the arg\n  - but I guess I can cover that with - the type is an expression, and I do a switch on it.\n  - yeah so maybe I don't need to be able to specify poly_variant? Unless I want to use the string of the variant name somehow. could be a future task\n- \n\n### YOOO if I make this, I totally need an online playground for working with it. Must make jsoo compatible if at all possible.\n\nForms:\n- `%macro` - a basic macro, multiple arguments given as a tuple literal. non-expression arguments given via `[%t: ]` and `[%p? ]`\n  - rest argument as the last one, `others: rest(expression)`\n- `%macro.let` has two forms:\n  - `let%macro.let a = (pattern: pattern, typ: type, expr: expression, expr: expression) =\u003e`\n    - maybe cann this `macro.let.typed`?\n  - `let%macro.let a = (pattern: pattern, expr: expression, expr: expression) =\u003e`\n- `%macro.let.toplevel`\n- `%macro.decorator`\n- `%macro.decorator.str`\n- `%macro.decorator.pat`\n- `%macro.decorator.typ`\n- `%macro.switch`\n  - `let%macro.switch a = (expr: expression, (pattern, cexpr): case, cases: rest(case)) =\u003e`\n  - not sure what can be done with this... other than constructing a new switch, I guess maybe with the pattern altered or something\n  - `[%construct.switch (expr, cases.map(((pattern, cexpr)) =\u003e ([%p? Some(pattern)], cexpr)))]`\n- `%macro.try`\n  - `let%macro.try a = (expr: expression, cases: rest(case)) =\u003e Js.Promise.catch(expr, err =\u003e [%construct.switch (err, cases)])`\n\nEval functions:\n- `[%eval switch_(expr, cases)]`\n- `[%eval map(value, item =\u003e { /* treated the same as a macro body */ })]` // if the body is a `[%str]`, then will make multiple strs. Otherwise, will make a list literal probably?\n- `[%eval foreach(value, item =\u003e {})]` // if in a structure context, the body must be `%str`. Otherwise, will be exp_sequence'd together\n- `let%eval something = otherthing` // if it's a constant, will be processed as such. otherwise, will be a plain expr\n- `if%eval (x == 2) {}`\n- `for%eval (x in 0 to 5) {}` // if the body is a `[%str]`, then it will make structured items. otherwise, will be `exp_sequence`d together.\n\n\n- `[%construct.switch (expr, cases)]` cases must be a `list(case)`, expr must be some kind of `expression`\n- `[%construct.let (pattern, expr, continuation)]`\n- `[%construct.let.typed (pattern, typ, expr, continuation)]`\n- `[%error \"Some macro error message\"]`\n  - would be nice to be able to provide an ast node to attach it to\n- `[%construct.attribute (expr, string or ident)]`\n- `[%construct.js_attribute (expr, string or ident)]`\n- `[%env \"some string\"]` get an environmental variable at compile-time, you can do logic with it, becomes a string literal\n\nTransformations:\n- `[%string! some_ast_node]` - pretty prints it out using refmt\n- `somearray.map(fn =\u003e thing)` - transform an array of something into something else....\n- `somearray.reduce(base, (collector, item) =\u003e collector)` - reduce. not sure how far I can go with this\n  - UPDATE: maybe get_in isn't even interesting\n  - would be nice to make it so I can support `get_in(some, [\"a\", \"b\", \"c\"])`\n  - even fancier `get_in(some, [\"a\", \u0026backquot;opt(\"b\"), \"c\"])`\n```reason\nlet%macro get_in = (target: expression, path: list(string)) =\u003e {\n  path.reduce(target, (target, item) =\u003e [%construct.attribute (target, item)])\n}\n\n// dunno if I can get this done tbh\nlet%macro get_in = (target: expression, path: list(expression)) =\u003e {\n  path.reduce(target, (target, item) =\u003e switch%eval item {\n    | `opt(expr) =\u003e switch ([%construct.]) {},\n  });\n\n  // yeah way too complicated\n  loop((target, items), (target, items) =\u003e {\n    switch%eval items {\n      | [] =\u003e target\n      | [one, ...rest] =\u003e [%recur (target, rest)]\n    }\n  })\n}\n```\n\n- \n\n\n```reason\nlet%macro name = (arg1: type1, arg2: type2) =\u003e {\n  body\n}\n\n[%name (arg1, arg2)]\n\nlet%macro.let name = (pattern: pattern, expr: expression, continuation: expression) =\u003e {\n\n}\n\nlet%macro name = (arg: array(string)) =\u003e {\n  arg.map(item =\u003e [%eval.concat (item, \"hello\")])\n}\n\n\n\n\n\n-\u003e\u003e instead of `arg`\nI think I'd rather have it be\nlet%macro.record platform = record =\u003e {\n  switch%eval ([%env \"bsb-backend\"]) {\n    | \"native\" =\u003e record.native\n    | \"js\" =\u003e record.js\n  }\n}\n \nlet x = [%platform {\n  native: \"someNative\",\n  js: \"someJs\"\n}];\n```\n\n      - How to do arguments?\n        - magically interpolate a record definition into arguments\n          - so like\n```reason\nlet x = [%platform {\n  native: \"someNative\",\n  js: \"someJs\",\n}]\n```\n\n        - use `[@arg.somearg \"contents\"]` decorators\n          - \n```reason\nlet x = [@arg.native \"someNative\"]\n[@arg.js \"someJs\"]\n[%platform]\n```\n\n```reason\n[@t {\n  name: \"contents\",\n  otherThing: 10,\n}]\n//\n[%macro.t\n  Testing.run([%arg \"name\"], [%arg \"otherThing\"])\n]\n```\n\n  - \n\n\n# Some rust macros\n\nhttps://doc.rust-lang.org/1.7.0/book/macros.html\n\n```rs\n\n#[macro_export]\nmacro_rules! vec {\n    ( $( $x:expr ),* ) =\u003e {\n        {\n            let mut temp_vec = Vec::new();\n            $(\n                temp_vec.push($x);\n            )*\n            temp_vec\n        }\n    };\n}\n\n\nmacro_rules! write_html {\n    ($w:expr, ) =\u003e (());\n\n    ($w:expr, $e:tt) =\u003e (write!($w, \"{}\", $e));\n\n    ($w:expr, $tag:ident [ $($inner:tt)* ] $($rest:tt)*) =\u003e {{\n        write!($w, \"\u003c{}\u003e\", stringify!($tag));\n        write_html!($w, $($inner)*);\n        write!($w, \"\u003c/{}\u003e\", stringify!($tag));\n        write_html!($w, $($rest)*);\n    }};\n}\n\nfn main() {\n    use std::fmt::Write;\n    let mut out = String::new();\n\n    write_html!(\u0026mut out,\n        html[\n            head[title[\"Macros guide\"]]\n            body[h1[\"Macros are the best!\"]]\n        ]);\n\n    assert_eq!(out,\n        \"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eMacros guide\u003c/title\u003e\u003c/head\u003e\\\n         \u003cbody\u003e\u003ch1\u003eMacros are the best!\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e\");\n}\n\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaredly%2Freason-macros","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaredly%2Freason-macros","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaredly%2Freason-macros/lists"}