{"id":21387113,"url":"https://github.com/russmatney/org-crud","last_synced_at":"2025-07-13T15:31:34.964Z","repository":{"id":40283387,"uuid":"276997988","full_name":"russmatney/org-crud","owner":"russmatney","description":"A tool for reading and writing org content via clojure","archived":false,"fork":false,"pushed_at":"2023-07-14T18:16:32.000Z","size":5324,"stargazers_count":12,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-04-14T19:19:40.970Z","etag":null,"topics":["clojure","markdown","org-mode"],"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/russmatney.png","metadata":{"files":{"readme":"readme.org","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":"2020-07-03T22:33:32.000Z","updated_at":"2023-05-01T18:16:36.000Z","dependencies_parsed_at":"2023-02-05T01:02:24.150Z","dependency_job_id":null,"html_url":"https://github.com/russmatney/org-crud","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russmatney%2Forg-crud","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russmatney%2Forg-crud/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russmatney%2Forg-crud/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russmatney%2Forg-crud/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/russmatney","download_url":"https://codeload.github.com/russmatney/org-crud/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225893451,"owners_count":17540916,"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","org-mode"],"created_at":"2024-11-22T12:11:52.712Z","updated_at":"2024-11-22T12:11:53.346Z","avatar_url":"https://github.com/russmatney.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+TITLE: Readme\n#+STARTUP: content\n\n* Org CRUD\n\nA tool for reading and writing org content via clojure, as well as converting\norg to markdown.\n\n\u003e Ninety percent of everything is crud.\n\u003e – [[https://www.quotes.net/quote/53367][Theodore Sturgeon]]\n\n** Status\n\nAlpha. I've recently namespaced the keys and flattened the props a bit.\nStill waiting for things to settle.\n\nI've used it internally for months, but the public api still needs to be\nproven. In particular, I'd like to iron out some things I'm doing with dynamic\nvars that no user should need to figure out/configure.\n\nIt works for my current use-cases, and there are unit tests! Feel free to take\nit for a spin.\n\n** Motivation\n\nThis library was pulled out of another tool, a productivity app built on top of\norg-mode. That tool needed to be able to treat org files like a database of\nitems.\n\nIt was pulled out to allow a few other libraries to use it independently\n([[https://github.com/russmatney/ralphie][russmatney/ralphie]]), and also to add\nsupport for converting org files to markdown. See [[#markdown][Markdown]].\n\nOrg-crud aims to provide simple interactions with org files to clojure\nenvironments.\n\n** Background\n\nThere is not much to the parser besides a thin layer on top of\n[[https://github.com/gmorpheme/organum][organum]]. Organum does not nest the org\nitems - it returns a flattened list, regardless of the items' hierarchical\nrelationship. Org-crud provides both a flattened and nested option for parsing\norg items.\n\nThis library is also [[https://github.com/borkdude/babashka][babashka]]\ncompatible, so you can drop it into a bb script without issue. This was\nnecessary for tools like [[https://github.com/russmatney/ralphie][ralphie]] to run\nthe exporter. You can see how ralphie consumes it [[https://github.com/russmatney/ralphie/blob/f6432e433e7e447aa1c0784e62ade2935c557cfc/src/ralphie/notes.clj*L21][in the ralphie.notes namespace]]\n\n** Features\n\n- [[https://github.com/borkdude/babashka][Babashka]] compatible\n- List nested or flattened org items\n- Update existing org items\n  - Updates by :ID:\n  - Add/remove tags, properties\n  - Change an item's name\n- Delete org items\n- Convert org files to markdown files\n\n** Org Item Model\n\nThis library parses org \"items\" from a given `.org` file.\n\nAn example item looks something like:\n\n#+begin_src clojure\n{:org/name \"My org headline\" ;; the name without the bullets/todo block\n :org/headline \"** [ ] My org headline\" ;; a raw headline\n :org/source-file \"\" ;; the file this item was parsed from\n :org/id *uuid \"\" ;; a unique id for the headline (parsed from the item's property bucket)\n :org/tags *{\"some\" \"tags\"}\n :org/level 2 ;; 1-6 or :level/root\n :org/body-string \"raw body string\\nwith lines\\nof content\"\n :org/body '() ;; a list of parsed lines, straight from organum TODO document this structure\n :org/status :status/not-started ;; parsed based on the\n ;; also supports :status/in-progress, :status/done, :status/cancelled\n\n ;; these dates are pulled through as strings\n :org/closed \"2022-04-30 Sat 17:42\"\n :org/deadline \"2022-04-30 Sat\"\n :org/scheduled \"2022-04-30 Sat\"\n\n ;; supports [#A], [#B], [#C] in headlines\n :org/priority \"B\"\n\n :org.prop/some-prop \"some prop value\" ;; props are lower-and-kebab-cased\n :org.prop/some-other-prop \"some other prop value\"\n :org.prop/created-at \"2020-07-21T09:25:50-04:00[America/New_York]\" ;; to be parsed by consumer\n\n :org/items '() ;; nested org-items (if parsed with the 'nested' helpers)\n\n ;; misc helper attrs\n :org/word-count 3 ;; a basic count of words in the name and body\n :org/urls '() ;; parsed urls from the body - helpful for some use-cases\n}\n#+end_src\n\nItems were originally implemented to support individual org headlines, but have\nbeen adapted to work with single org files as well (to fit org-roam tooling\nuse-cases).\n\n** Install\n\nCurrently, install requires referencing the git repo.\n\n#+begin_src clojure\n;; deps.edn\n{:deps\n {russmatney/org-crud {:git/url \"https://github.com/russmatney/org-crud.git\"\n                       :sha     \"a4b44022c690e1c8fb34512f1aad85bc49569d19\"}}}\n#+end_src\n\nTODO add to clojars\n\n** Usage\n\nTODO do some work on this section!\n\nYou can see the test files for example usage.\n\nI'm attempting to hold a public api at `org-crud.api`, but that is a WIP.\n\n*** Parsing\n\n#+begin_src clojure\n(ns your.ns\n (:require [org-crud.api :as org-crud]))\n\n;; a nested item represents an entire file, with items as children\n(let [item (org-crud/path-\u003enested-item \"/path/to/file.org\")]\n  (println item))\n\n;; parses every '.org' file in a directory into a list of nested items\n(let [items (org-crud/dir-\u003enested-items \"/path/to/org/dir\")]\n  (println (first items)\n\n;; 'flattened' items have no children - just a list of every headline\n;; (starting with the root itself)\n(let [items (org-crud/path-\u003eflattened-items \"/path/to/file.org\")]\n  (println (first items)))\n#+end_src\n\n*** Updating\n\nUpdates are performed with a passed item and an update map that resembles the\norg-item itself. It will use the passed item's id and source-file to find the\nitem to be updated, merge the updates in memory, then rewrite it.\n\n#+BEGIN_SRC clojure\n(ns your.ns\n (:require [org-crud.api :as org-crud]))\n\n(-\u003e (org-crud/path-\u003eflattened-items \"/path/to/file.org\")\n    second ;; grabbing some item\n    (org-crud/update!\n      {:org/name \"new item name\" ;; changing the item name\n       :org/tags \"newtag\" ;; adding a new tag\n       :org.prop/some-prop \"some-prop-value\"\n      }))\n#+END_SRC\n\nTODO document props-as-lists features\nTODO document refile!, add-item!, delete-item!\n\n*** Markdown\n\nOrg-crud provides a namespace for converting org files to markdown, and a\nbabashka-based cli tool for running this conversion on the command line.\n\nIn order for this to work, you'll need to have\n[[https://github.com/borkdude/babashka#quickstart][Babashka]] (and [[https://clojure.org/guides/getting_started][clojure]] installed and\navailable on the command line as `bb` and `clojure`.\n\n#+begin_src sh\nbb org-crud.jar org-to-markdown ~/Dropbox/notes tmp-out\n#+end_src\n\nNote that this support targets a use-case for publishing an\n[[https://github.com/org-roam/org-roam/][org-roam]] directory as markdown, but\notherwise is probably not a complete org-\u003emarkdown conversion solution. If you\nhave more use-cases that you'd like to see supported, please open an issue\ndescribing the use-case, and I'd be happy to take a shot at it.\n\nNote that Emacs/Org supports export that is fairly similar as well - I enjoyed\nputting this together and not needing to leave the joy of clojure-land.\n\nAn org file like `20200618104339-dated-example.org`:\n\n#+begin_src org\n*+TITLE: Dated Example\n*+ROAM_TAGS: dated\n\nAnother org file, now with a link!\n\n- [[file:example.org][example link]]\n\nDated to match the org-roam default style.\n#+end_src\n\nWill be converted to:\n\n#+begin_src markdown\n---\ntitle: \"Dated Example\"\ndate: 2020-06-18\ntags:\n  - dated\n  - note\n---\n\n\nAnother org file, now with a link!\n\n- [example link](/notes/example)\n\nDated to match the org-roam default style.\n#+end_src\n\n- The frontmatter pulls tags from `*+ROAM_TAGS`.\n  - TODO prevent `note` from being added every time.\n- The date is parsed from the filename.\n  - TODO support alternate sources for the date, if users don't have timestamps\n    in filenames.\n- The links to other notes are prepended with `/notes/\u003cfilename\u003e`\n  - TODO support custom link handling options, not just this hardcoded /notes/ prefix.\n\n**** Appended `Backlinks` section\n\nWhen run over a directory, a `Backlinks` section is built up as a basic markdown\nlist.\n\n#+begin_src org\n\u003c... rest of file\u003e\n\\* Backlinks\n\n- [Index](/notes/20200704184516-index)\n#+end_src\n\n** Notes\n\n*** Item IDs (UUIDs)\n\nItem IDs are more or less required for updating. Things will fallback to\nmatching on name if there are no ids, but this approach has a few issues,\nbecause names are not necessarily unique throughout files.\n\nI've updated my personal org templates/snippets in places to include IDs when\ncreating new items, and org-mode provides helpers that can be used to add them\nwithout too much trouble. (Ex: `org-id-get-create`).\n\nTODO share links to templates/snippets that create uuids\n\nIf this is a problem, let me know, there are other workarounds. Using IDs allows\nfor cases with repeated headlines in the same file - otherwise you might get\ninto tracking line numbers or parents, which did not seem worth it, especially\nas my usage benefitted from the IDs elsewhere.\n\n** Relevant/Related tools\n\n- [[https://github.com/kaushalmodi/ox-hugo][ox-hugo]]\n- [[https://github.com/gmorpheme/organum][organum]]\n- [[https://github.com/org-roam/org-roam][org-roam]]\n\n** Development\n*** Running the cli using the source\n\nRather than the built uberjar:\n\n#+BEGIN_SRC sh\n# from this repo's root\nbb -cp $(clojure -Spath) -m org-crud.cli org-to-markdown ~/Dropbox/notes tmp-out\n#+END_SRC\n\n*** Rebuild the uberjar\n\nTo rebuild the cli-based uberjar via babashka:\n\n#+begin_src sh\nbb -cp $(clojure -Spath) -m org-crud.cli --uberjar org-crud.jar\n#+end_src\n\n*** Running tests\n\n#+begin_src sh\n./bin/kaocha\n#+end_src\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frussmatney%2Forg-crud","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frussmatney%2Forg-crud","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frussmatney%2Forg-crud/lists"}