8000 GitHub - bzg/hop: Hush Org Parser · GitHub
[go: up one dir, main page]

Skip to content
bzgPublic

About

Hush Org Parser

Resources

Stars

4 stars

Watchers

0 watching

Forks

Latest commit

 

History

62 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hop! Org Parser

https://img.shields.io/badge/versioning-intver.org-blue.svg?style=for-the-badge

(\___/)
(='.'=)
(")_(")

Hop! Org Parser – render and export Org files to Markdown, HTML, Org, JSON, EDN, YAML and ICS from the command line.

Built on organ, a Clojure Org parser: hop parses the file once into an AST, applies the filters, then renders the AST in the requested format.

Usage

hop [options] <org-file>        # "-" reads the Org text from stdin

Output formats (-f)

FormatOutput
jsonThe AST, text fields rendered as Markdown (default)
ednSame AST as EDN
yamlSame AST as YAML
mdMarkdown document
htmlStandalone HTML page (inline CSS, highlight.js when there is code)
orgOrg document, unwrapped and normalised, faithful to the source
icsiCalendar: SCHEDULED items as VEVENT, DEADLINE + TODO as VTODO
ics-anoniCalendar: SCHEDULED items only, anonymised as “Busy” blocks
hop notes.org                   # AST as JSON
hop -f html notes.org           # HTML page
hop -f org notes.org            # Org, re-exported
hop -r org notes.org            # AST as JSON, text fields rendered as Org (-r: md, html, org)

The AST formats keep organ’s node structure (:section, :paragraph, :list, :table, :src-block, :drawer, …), with inline markup already rendered in the -r format.

Filters

Filters select headings; the rest of the document is dropped.

hop -l 2 notes.org              # Headings of level <= 2
hop -L 2 notes.org              # Same, deeper headings rendered as bold paragraphs
hop -t "Release" notes.org      # Heading title matches the regex
hop -T "Projects" notes.org     # Any ancestor title matches the regex (keeps its subtree)
hop -i "section[0-9]+" notes.org  # ID or CUSTOM_ID property matches the regex
hop -I "chapter\d+" notes.org   # Any ancestor ID matches the regex (keeps its subtree)

handling (every format)

Subtrees whose heading is in a DONE state are stripped from every output; -k/--keep-done keeps them. The DONE set comes from the #+TODO:-style directives of the file (Org’s TODO=/=DONE defaults otherwise), extended with -D:

hop -D CANX,CANCELED notes.org  # Extra DONE keywords, comma-separated
hop -k notes.org                # Keep DONE subtrees

HTML export

hop -f html -c https://example.com/style.css notes.org  # Link a remote stylesheet
hop -f html -c file:///path/to/style.css notes.org      # Inline a local file (file:// URL)
hop -f html -c ../styles/theme.css notes.org            # Inline a local .css file (any path)
hop -f html -c org notes.org                            # Link the "org" theme from pico-themes
hop -f html -b https://base.io/ index.org               # Prepend a base URL to relative links

A bare theme name resolves to pico-themes on jsDelivr; a .css path that does not exist is an error, not a CDN fall-back. Local images ([[file:img.png]]) are inlined as data URIs in HTML and Markdown.

#+OPTIONS is honoured for HTML and Markdown: toc:t (or toc:N) emits a table of contents, num:t numbers the headings, H:N bounds the TOC depth. Section anchors come from CUSTOM_ID or a slug of the title, deduplicated across the document. Footnotes are gathered at the end of the page.

Org export

-f org aims at a faithful round trip: parse → export → parse yields the same document. The #+ preamble (#+TITLE:, #+OPTIONS:, #+TODO:, #+STARTUP:, …) is re-emitted in file order, drawers, LaTeX fragments and environments, planning lines, property drawers and tables nested in lists are preserved. Because the #+OPTIONS line carries the intent, the TOC and section numbers are not written into the text.

hop -f org notes.org            # Lines are unwrapped (one paragraph per line)
hop -f org -n notes.org         # Keep the original line breaks

#+KEY: lines found later in the body (a #+TODO: or #+PROPERTY: under a heading) are kept in place as well; they are dropped from every other format, as they carry no rendering of their own.

ICS export (ics and ics-anon)

OptionEffect
-w/--weeks NWeeks ahead to scan (unbounded unless set)
-R/--reference-dateWindow start, YYYY-MM-DD (today when only -w is set; reproducible runs)
-d/--default-durationMinutes for events without an end time (none added unless set)
-z/--time-zoneZone anchoring the Org timestamps (system zone by default), validated
-a/--all-dayAlso emit all-day entries (excluded by default)

SCHEDULED timestamps become VEVENT; a DEADLINE under a heading with a TODO keyword becomes a VTODO (a due date reserves no time, so it never counts as busy). Repeaters (+1w) become RRULE; with -w, ics-anon expands them over the window and merges overlapping blocks instead. Inline timestamps and CLOSED are not exported. UIDs derive from the entry itself (title, timestamp, kind), so reordering the file does not churn them. Both formats emit UTC datetimes, anchored in the -z zone, no VTIMEZONE.

For ics-anon, an all-day SCHEDULED entry counts as busy only with -a and only when its heading bears no TODO keyword: a “Vacation” heading blocks its day, a task merely scheduled on a date does not.

hop -f ics -z Europe/Paris -d 45 notes.org               # 45-minute default events
hop -f ics-anon -R 2026-01-05 -w 12 -a notes.org         # 12-week free/busy from a fixed date

Other options

hop -n notes.org                # Preserve original line breaks (every format)
hop -s notes.org                # Document statistics (sections, words, images, planning, ...)
hop -v                          # Version, as installed by bbin
hop --help                      # Every option

Parse warnings (unterminated blocks or drawers, invalid timestamps) go to stderr, the document is still rendered. Invalid option values (an unknown time zone, a non-numeric duration) and unreadable files exit with status 1.

Install

Assuming bbin is installed:

bbin install io.github.bzg/hop

Requirements

hop is a Clojure Babashka script; its only dependency is organ.

brew install babashka/brew/bbin

Development

Run hop from the source, without installing it:

~$ bb -m bzg.hop -f md notes.org

The whole program lives in src/bzg/hop.clj: inline rendering, block rendering (render-node dispatches on the node type), ICS export, statistics, then the CLI. Each renderer takes the format as an argument (:html, :md, :org) rather than existing in three copies.

Test

~$ bb test              # golden files + unit tests
~$ bb test:generate     # regenerate the golden files after an intended output change

bb test compares every format’s output for test/bzg/test.org with test/bzg/expected/ (ICS DTSTAMP lines are normalised), then runs the clojure.test suite in test/bzg/hop_unit_test.clj: pure helpers (RFC 5545 folding, slugs, #+OPTIONS parsing, TOC nesting, repeater windows, interval merging), CLI option typing and short-flag uniqueness, and the Org round trip. Review the golden diffs before committing them.

Contributing

You can also send me an email and support my work on liberapay.

Intentional Versioning

This project uses Intentional Versioning, here are the three audiences:

  • Users : end users who use hop
  • Integrators : external packagers
  • Maintainers : maintainers to the codebase

Support the Clojure(script) ecosystem

If you like Clojure(script), please consider supporting maintainers by donating to clojuriststogether.org.

License

Copyright © 2026 Bastien Guerry

Distributed under the Eclipse Public License 2.0.

About

Hush Org Parser

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

0