{"id":36220316,"url":"https://github.com/harehare/mq-ruby","last_synced_at":"2026-07-11T16:00:33.880Z","repository":{"id":331773203,"uuid":"1121120384","full_name":"harehare/mq-ruby","owner":"harehare","description":"Ruby bindings for mq, a jq-like command-line tool for processing Markdown.","archived":false,"fork":false,"pushed_at":"2026-07-03T15:00:10.000Z","size":62,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-03T15:13:00.211Z","etag":null,"topics":["markdown","md","mqlang","ruby"],"latest_commit_sha":null,"homepage":"https://mqlang.org","language":"Ruby","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/harehare.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-22T13:21:41.000Z","updated_at":"2026-07-03T15:00:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"2186dbd8-fc0c-40df-986e-e05da363e9e2","html_url":"https://github.com/harehare/mq-ruby","commit_stats":null,"previous_names":["harehare/mq-ruby"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/harehare/mq-ruby","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harehare%2Fmq-ruby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harehare%2Fmq-ruby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harehare%2Fmq-ruby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harehare%2Fmq-ruby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/harehare","download_url":"https://codeload.github.com/harehare/mq-ruby/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harehare%2Fmq-ruby/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35367446,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","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":["markdown","md","mqlang","ruby"],"created_at":"2026-01-11T04:55:55.824Z","updated_at":"2026-07-11T16:00:33.872Z","avatar_url":"https://github.com/harehare.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003emq-ruby\u003c/h1\u003e\n\n[![Gem Version](https://badge.fury.io/rb/mq-ruby.svg)](https://badge.fury.io/rb/mq-ruby)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nRuby bindings for [mq](https://mqlang.org/), a jq-like command-line tool for processing Markdown.\n\n## Installation\n\nAdd to your Gemfile:\n\n```ruby\ngem 'mq-ruby'\n```\n\n## Basic Usage\n\n```ruby\nrequire 'mq'\n\nmarkdown = \u003c\u003c~MD\n  # Main Title\n\n  ## Section 1\n\n  Some content here.\n\n  ## Section 2\n\n  More content.\nMD\n\n# Run a raw mq query string\nresult = MQ.run('.h2', markdown)\nresult.values.each { |h| puts h }\n# =\u003e ## Section 1\n# =\u003e ## Section 2\n\n# Access result as a single string\nputs result.text\n# =\u003e ## Section 1\n# =\u003e ## Section 2\n\n# Count matched nodes\nputs result.length  # =\u003e 2\n\n# Index into results (1-based)\nputs result[1]  # =\u003e ## Section 1\n```\n\n## Query Builder\n\n`MQ::Query` provides a Ruby DSL for building mq query strings programmatically.\nQueries are built by chaining methods and can be passed directly to `MQ.run`.\n\n```ruby\n# Equivalent to MQ.run('.h2', markdown)\nresult = MQ.run(MQ::Query.h2, markdown)\n\n# Chain with filters and transformations\nquery = MQ::Query.h2\n  .select { contains(\"Installation\") }\n  .to_text\n\nresult = MQ.run(query, markdown)\n```\n\n### Selectors\n\n#### Heading Selectors\n\n```ruby\nMQ::Query.h1        # .h1  — level-1 headings\nMQ::Query.h2        # .h2  — level-2 headings\nMQ::Query.h3        # .h3\nMQ::Query.h4        # .h4\nMQ::Query.h5        # .h5\nMQ::Query.h6        # .h6\nMQ::Query.heading   # .heading — any heading\n```\n\n#### Block Element Selectors\n\n```ruby\nMQ::Query.paragraph   # .p\nMQ::Query.code        # .code       — fenced code blocks\nMQ::Query.blockquote  # .blockquote\nMQ::Query.hr          # .hr         — horizontal rules\nMQ::Query.list        # .[]         — list items\nMQ::Query.table       # .table\nMQ::Query.table_align # .table_align\nMQ::Query.math        # .math       — math blocks\nMQ::Query.html        # .html       — raw HTML blocks\nMQ::Query.definition  # .definition — link definitions\nMQ::Query.footnote    # .footnote\nMQ::Query.toml        # .toml       — TOML front matter\nMQ::Query.yaml        # .yaml       — YAML front matter\n```\n\n#### Inline Element Selectors\n\n```ruby\nMQ::Query.text          # .text\nMQ::Query.strong        # .strong    — bold\nMQ::Query.emphasis      # .emphasis  — italic\nMQ::Query.delete        # .delete    — strikethrough\nMQ::Query.link          # .link\nMQ::Query.image         # .image\nMQ::Query.code_inline   # .code_inline\nMQ::Query.math_inline   # .math_inline\nMQ::Query.link_ref      # .link_ref\nMQ::Query.image_ref     # .image_ref\nMQ::Query.footnote_ref  # .footnote_ref\nMQ::Query.line_break    # .break\n```\n\n#### Task List Selectors\n\n```ruby\nMQ::Query.task  # .task — any task list item\nMQ::Query.todo  # .todo — unchecked task items\nMQ::Query.done  # .done — checked task items\n```\n\n#### Indexed Selectors\n\n```ruby\nMQ::Query.list_at(0)         # .[0]     — first list item\nMQ::Query.list_at(2)         # .[2]     — third list item\nMQ::Query.table_row(0)       # .[0][]   — all cells in row 0\nMQ::Query.table_col(1)       # .[][1]   — all cells in column 1\nMQ::Query.table_cell(0, 1)   # .[0][1]  — cell at row 0, column 1\n```\n\n#### MDX Selectors\n\n```ruby\nMQ::Query.mdx_jsx_flow_element  # .mdx_jsx_flow_element\nMQ::Query.mdx_text_expression   # .mdx_text_expression\nMQ::Query.mdx_jsx_text_element  # .mdx_jsx_text_element\nMQ::Query.mdx_flow_expression   # .mdx_flow_expression\nMQ::Query.mdx_js_esm            # .mdx_js_esm\n```\n\n#### Attribute Selectors\n\nAccess specific attributes of nodes directly:\n\n```ruby\nMQ::Query.code.lang    # .code | .lang  — language of code blocks\nMQ::Query.link.url     # .link | .url   — URL of links\nMQ::Query.image.alt    # .image | .alt  — alt text of images\nMQ::Query.link.title   # .link | .title — title of links\n\n# All available attribute selectors (class-level)\nMQ::Query.value    # .value\nMQ::Query.lang     # .lang\nMQ::Query.meta     # .meta\nMQ::Query.fence    # .fence\nMQ::Query.url      # .url\nMQ::Query.alt      # .alt\nMQ::Query.depth    # .depth   — heading depth\nMQ::Query.level    # .level\nMQ::Query.ordered  # .ordered — list ordered flag\nMQ::Query.checked  # .checked — task item checked state\nMQ::Query.column   # .column  — table cell column index\nMQ::Query.row      # .row     — table cell row index\nMQ::Query.align    # .align   — table alignment\n```\n\n#### Instance-level Attribute Access (for chaining)\n\n```ruby\n# After selecting a node, chain attribute selectors:\nMQ::Query.code.lang        # .code | .lang\nMQ::Query.link.url         # .link | .url\nMQ::Query.heading.depth    # .heading | .depth\nMQ::Query.task.checked     # .task | .checked\nMQ::Query.list.item_index  # .[] | .index  (item_index avoids naming conflict)\nMQ::Query.list.ordered     # .[] | .ordered\nMQ::Query.table.column     # .table | .column\nMQ::Query.table.row        # .table | .row\nMQ::Query.table_align.align           # .table_align | .align\nMQ::Query.mdx_jsx_flow_element.mdx_name  # .mdx_jsx_flow_element | .name\n```\n\n#### Recursive Selector\n\n```ruby\nMQ::Query.recursive  # .. — matches all nodes recursively\n```\n\n#### Dict Property Selector\n\n```ruby\nMQ::Query.property(\"title\")  # .\"title\"\nquery.property(\"key\")        # | .\"key\"\n```\n\n### Pipe Operator\n\nChain two queries with `|`:\n\n```ruby\nquery = MQ::Query.h2 | MQ::Query.to_text\n# =\u003e \".h2 | to_text()\"\n\nquery = MQ::Query.h2 | MQ::Query.select { contains(\"API\") } | MQ::Query.to_text\n# =\u003e '.h2 | select(contains(\"API\")) | to_text()'\n```\n\n### Filtering with `select`\n\n```ruby\n# Block form (recommended)\nMQ::Query.h2.select { contains(\"Feature\") }\n# =\u003e '.h2 | select(contains(\"Feature\"))'\n\n# Combine conditions with \u0026 (and) and | (or)\nMQ::Query.h2.select { contains(\"API\") \u0026 starts_with(\"## \") }\n# =\u003e '.h2 | select(contains(\"API\") and starts_with(\"## \"))'\n\nMQ::Query.h2.select { contains(\"A\") | contains(\"B\") }\n# =\u003e '.h2 | select(contains(\"A\") or contains(\"B\"))'\n\n# Negation\nMQ::Query.select { negate(contains(\"draft\")) }\n# =\u003e 'select(not(contains(\"draft\")))'\n\n# Class-level select (no leading selector)\nMQ::Query.select { is_mdx }\n# =\u003e \"select(is_mdx())\"\n\n# String or Filter argument\nMQ::Query.h2.select('contains(\"Feature\")')\nMQ::Query.h2.select(MQ::Filter.new('contains(\"Feature\")'))\n```\n\n### Mapping with `map`\n\n```ruby\nMQ::Query.list.map { contains(\"important\") }\n# =\u003e '.[] | map(contains(\"important\"))'\n```\n\n### Transformation Methods\n\n#### Output\n\n```ruby\n.to_text           # to_text()          — plain text\n.to_markdown       # to_markdown()      — markdown string\n.to_mdx            # to_mdx()           — MDX string\n.to_html           # to_html()          — HTML string\n.to_string         # to_string()        — string coercion\n.to_number         # to_number()        — numeric coercion\n.to_boolean        # to_boolean()       — boolean coercion\n.to_array          # to_array()\n.to_bytes          # to_bytes()\n.to_markdown_string # to_markdown_string()\n```\n\n#### String Operations\n\n```ruby\n.trim              # trim()\n.ltrim             # ltrim()\n.rtrim             # rtrim()\n.downcase          # downcase()\n.upcase            # upcase()\n.ascii_downcase    # ascii_downcase()\n.ascii_upcase      # ascii_upcase()\n.len               # len()\n.utf8bytelen       # utf8bytelen()\n.explode           # explode()          — string to codepoints\n.implode           # implode()          — codepoints to string\n.url_encode        # url_encode()\n.url_decode        # url_decode()\n.intern            # intern()\n\n.split(\",\")        # split(\",\")\n.gsub(\"pat\", \"r\")  # gsub(\"pat\", \"r\")  — regex replace all\n.replace(\"a\", \"b\") # replace(\"a\", \"b\") — literal replace\n.test(\"\\\\d+\")      # test(\"\\\\d+\")      — regex test → bool\n.capture(\"(\\\\w+)\") # capture(\"(\\\\w+)\") — regex capture\n.scan(\"(\\\\w+)\")    # scan(\"(\\\\w+)\")    — all regex matches\n.slice(0, 5)       # slice(0, 5)\n.index(\"sub\")      # index(\"sub\")      — position of substring\n.rindex(\"sub\")     # rindex(\"sub\")     — last position\n.repeat(3)         # repeat(3)\n```\n\n#### Collection Operations\n\n```ruby\n.length            # len()\n.len               # len()\n.add(\"x\")          # add(\"x\")\n.first             # first\n.last              # last\n.empty             # is_empty()\n.reverse           # reverse\n.sort              # sort\n.compact           # compact           — remove nils\n.uniq              # uniq\n.flatten           # flatten\n.keys              # keys\n.values            # values\n.entries           # entries\n.children          # .children\n.join(\", \")        # join(\", \")\n.nth(2)            # get(2)\n.limit(5)          # take(5)\n.range(3)          # range(3)\n.del(\"key\")        # del(\"key\")\n.insert(0, \"val\")  # insert(0, \"val\")\n.shuffle           # shuffle()         — random order\n.sample(3)         # sample(3)         — n random elements, no replacement\n```\n\n#### Math Operations\n\n```ruby\n.abs               # abs()\n.ceil              # ceil()\n.floor             # floor()\n.round             # round()\n.trunc             # trunc()\n.sqrt              # sqrt()\n.ln                # ln()\n.log10             # log10()\n.exp               # exp()\n.pow(2)            # pow(2)\n.min(0)            # min(0)\n.max(100)          # max(100)\n.negate_val        # negate()          — numeric negation\n.is_nan            # is_nan()\n```\n\n#### Type / Logic\n\n```ruby\n.type              # type\n.coalesce(\"default\") # coalesce(\"default\")\n.debug             # debug\n```\n\n#### Encoding\n\n```ruby\n.base64            # base64()\n.base64d           # base64d()\n.base64url         # base64url()\n.base64urld        # base64urld()\n.md5               # md5()\n.sha256            # sha256()\n.sha512            # sha512()\n.from_hex          # from_hex()\n.to_hex            # to_hex()\n.uuid              # uuid()            — random (v4) UUID\n.uuid_v4           # uuid_v4()         — alias of uuid\n.uuid_v7           # uuid_v7()         — time-ordered (v7) UUID\n.rand              # rand()            — pseudo-random number in [0, 1)\n.rand_int(1, 10)   # rand_int(1, 10)   — pseudo-random integer in [min, max]\n```\n\n`MQ::Query.uuid`, `.uuid_v4`, `.uuid_v7`, `.rand`, and `.rand_int(min, max)` are\nalso available as class-level generators to start a query without a preceding\nselector, e.g. `MQ::Query.uuid.to_query # =\u003e \"uuid()\"`.\n\n#### Path Operations\n\n```ruby\n.basename          # basename()\n.dirname           # dirname()\n.extname           # extname()\n.stem              # stem()\n.path_join(\"sub\")  # path_join(\"sub\")\n```\n\n#### Dict Operations\n\n```ruby\n.get(\"key\")            # get(\"key\")\n.set(\"key\", \"val\")     # set(\"key\", \"val\")\n.property(\"key\")       # .\"key\"\n```\n\n#### Markdown Attribute Operations\n\n```ruby\n.update(\"New content\")          # update(\"New content\")\n.attr(\"lang\")                   # attr(\"lang\")\n.set_attr(\"lang\", \"ruby\")       # set_attr(\"lang\", \"ruby\")\n.get_title                      # get_title\n.get_url                        # get_url\n.set_check(true)                # set_check(true)\n.set_ref(\"myref\")               # set_ref(\"myref\")\n.set_code_block_lang(\"python\")  # set_code_block_lang(\"python\")\n.set_list_ordered(true)         # set_list_ordered(true)\n```\n\n#### Markdown Construction\n\n```ruby\n.to_code(\"ruby\")               # to_code(\"ruby\")\n.to_code                       # to_code(null)   — no language\n.to_code_inline                # to_code_inline()\n.to_h(2)                       # to_h(2)          — convert to heading level 2\n.to_hr                         # to_hr()\n.to_link(\"url\", \"text\", \"title\")  # to_link(...)\n.to_link(\"url\", \"text\")           # to_link(...)   — empty title\n.to_link(\"url\")                   # to_link(...)   — current value as text\n.to_image(\"url\", \"alt\", \"title\")  # to_image(...)\n.to_math                       # to_math()\n.to_math_inline                # to_math_inline()\n.to_strong                     # to_strong()\n.to_em                         # to_em()\n.to_md_text                    # to_md_text()\n.to_md_list(0)                 # to_md_list(0)    — nesting level\n.to_md_name(\"component\")       # to_md_name(\"component\")\n.to_md_table_row(\"A\", \"B\", \"C\")   # to_md_table_row(...)\n.to_md_table_cell(\"val\", 0, 1)    # to_md_table_cell(...)\n```\n\n### Filter DSL\n\nAll filter methods return a `MQ::Filter` that can be combined with `\u0026` (and) and `|` (or).\n\n#### String Matching\n\n```ruby\ncontains(\"text\")       # contains(\"text\")\nstarts_with(\"## \")     # starts_with(\"## \")\nends_with(\".\")         # ends_with(\".\")\ntest(\"\\\\d+\")           # test(\"\\\\d+\")        — regex test\n```\n\n#### Regex\n\n```ruby\nis_regex_match(\"\\\\d+\")      # is_regex_match(\"\\\\d+\")\nis_not_regex_match(\"\\\\d+\")  # is_not_regex_match(\"\\\\d+\")\n```\n\n#### Comparison Operators\n\nThese compare the current pipeline value against the argument:\n\n```ruby\neq(\"value\")   # eq(\"value\")    — equal\nne(\"value\")   # ne(\"value\")    — not equal\ngt(5)         # gt(5)          — greater than\ngte(5)        # gte(5)         — greater than or equal\nlt(5)         # lt(5)          — less than\nlte(5)        # lte(5)         — less than or equal\n```\n\n#### Type Checks\n\n```ruby\nis_mdx   # is_mdx()\nis_none  # is_none()\nis_nan   # is_nan()\ntype     # type\n```\n\n#### Other\n\n```ruby\nnegate(contains(\"draft\"))  # not(contains(\"draft\"))\nlength                     # length\nempty                      # empty\nadd                        # add\n```\n\n### Combining Filters\n\n```ruby\nMQ::Query.h2.select { contains(\"API\") \u0026 negate(contains(\"Internal\")) }\n# =\u003e '.h2 | select(contains(\"API\") and not(contains(\"Internal\")))'\n\nMQ::Query.h2.select { starts_with(\"## \") | ends_with(\"!\") }\n# =\u003e '.h2 | select(starts_with(\"## \") or ends_with(\"!\"))'\n\n# Three-way AND\nMQ::Query.h2.select {\n  contains(\"API\") \u0026 negate(contains(\"Internal\")) \u0026 starts_with(\"## \")\n}\n```\n\n## Options\n\n```ruby\noptions = MQ::Options.new\noptions.input_format = MQ::InputFormat::MARKDOWN  # default\noptions.input_format = MQ::InputFormat::MDX\noptions.input_format = MQ::InputFormat::TEXT\noptions.input_format = MQ::InputFormat::HTML\noptions.input_format = MQ::InputFormat::RAW\noptions.input_format = MQ::InputFormat::NULL\n\nresult = MQ.run('.h1', content, options)\n```\n\n## HTML to Markdown\n\n```ruby\nhtml = '\u003ch1\u003eTitle\u003c/h1\u003e\u003cp\u003eThis is a \u003cstrong\u003etest\u003c/strong\u003e.\u003c/p\u003e'\nmarkdown = MQ.html_to_markdown(html)\n# =\u003e \"# Title\\n\\nThis is a **test**.\"\n\n# With conversion options\noptions = MQ::ConversionOptions.new\noptions.use_title_as_h1 = true\noptions.extract_scripts_as_code_blocks = true\noptions.generate_front_matter = true\n\nmarkdown = MQ.html_to_markdown(html, options)\n```\n\n## Examples\n\n```ruby\nrequire 'mq'\n\ncontent = File.read('README.md')\n\n# Extract all h2 headings containing \"API\"\nMQ.run(MQ::Query.h2.select { contains(\"API\") }, content).values\n\n# Get all code block languages used\nMQ.run(MQ::Query.code.lang, content).values\n\n# Get all link URLs\nMQ.run(MQ::Query.link.url, content).values\n\n# Extract headings as plain text (no # prefix)\nMQ.run(MQ::Query.h2.to_text, content).values\n\n# Find unchecked task items\nMQ.run(MQ::Query.todo, content).values\n\n# Get the first list item\nMQ.run(MQ::Query.list_at(0), content).values\n\n# Count h2 headings\nMQ.run(MQ::Query.h2.length, content).values\n\n# Extract YAML front matter\nMQ.run(MQ::Query.yaml, content).values\n```\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Links\n\n- [mq Website](https://mqlang.org/)\n- [GitHub Repository](https://github.com/harehare/mq)\n- [Command-line Tool](https://github.com/harehare/mq#installation)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharehare%2Fmq-ruby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fharehare%2Fmq-ruby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharehare%2Fmq-ruby/lists"}