Compare commits
No commits in common. "77e19d25d428916791cf56cc2a24df1f867e2328" and "8f3b26f2191b36229d700f9ff8596a9f147bab45" have entirely different histories.
77e19d25d4
...
8f3b26f219
2 changed files with 0 additions and 73 deletions
|
|
@ -31,8 +31,6 @@ setting, and every setting has a default, so a site can switch it on with just
|
||||||
Notes section; off by default since not
|
Notes section; off by default since not
|
||||||
every site using this plugin has been
|
every site using this plugin has been
|
||||||
checked for footnotes
|
checked for footnotes
|
||||||
SMOLNET_CALLOUTS {} callout kind -> label, for `> [!kind]`
|
|
||||||
blocks; see rewrite_callouts
|
|
||||||
|
|
||||||
Every run of links here is a **Markdown list**, which is what each format needs:
|
Every run of links here is a **Markdown list**, which is what each format needs:
|
||||||
Mews Profile reads a dated link as a feed entry only when it starts a list item
|
Mews Profile reads a dated link as a feed entry only when it starts a list item
|
||||||
|
|
@ -122,11 +120,6 @@ FOOTNOTE_REF = re.compile(r"\[\^([^\]]+)\]")
|
||||||
# citation it was meant to carry.
|
# citation it was meant to carry.
|
||||||
LINKREF_DEF = re.compile(r"^\[(\d+)\]:", re.MULTILINE)
|
LINKREF_DEF = re.compile(r"^\[(\d+)\]:", re.MULTILINE)
|
||||||
|
|
||||||
# The opening line of a GitHub/Obsidian-style callout, "> [!kind] title".
|
|
||||||
# The trailing newline is part of the match, so a marker with no label can be
|
|
||||||
# dropped whole instead of leaving the quote to open on a blank line.
|
|
||||||
CALLOUT = re.compile(r"^([ ]{0,3}>[ ]?)\[!(\w+)\][ \t]*(.*)\n?", re.MULTILINE)
|
|
||||||
|
|
||||||
|
|
||||||
def rot13(text):
|
def rot13(text):
|
||||||
"""The obfuscation used on addresses in the HTML templates."""
|
"""The obfuscation used on addresses in the HTML templates."""
|
||||||
|
|
@ -329,36 +322,6 @@ def rewrite_footnotes(text, labels, warn):
|
||||||
return body.rstrip() + "\n\n" + "\n".join(notes) + "\n"
|
return body.rstrip() + "\n\n" + "\n".join(notes) + "\n"
|
||||||
|
|
||||||
|
|
||||||
def rewrite_callouts(text, kinds):
|
|
||||||
"""Turn a `> [!kind]` marker into a bold label line, leaving the rest of
|
|
||||||
the passage an ordinary blockquote.
|
|
||||||
|
|
||||||
The HTML build renders these as boxes (an <aside>, via a Markdown
|
|
||||||
extension in the site itself). gemtext has nothing to draw a box with, and
|
|
||||||
a quote is the one block it does set apart, so the passage stays one; only
|
|
||||||
the marker would otherwise print literally as "[!aside]". Kinds not in
|
|
||||||
`kinds` are left alone, matching the HTML side, which also only boxes the
|
|
||||||
kinds it was told about.
|
|
||||||
"""
|
|
||||||
if not kinds:
|
|
||||||
return text
|
|
||||||
kinds = {k.lower(): v for k, v in kinds.items()}
|
|
||||||
|
|
||||||
def sub(match):
|
|
||||||
kind = match.group(2).lower()
|
|
||||||
if kind not in kinds:
|
|
||||||
return match.group(0)
|
|
||||||
title = match.group(3).strip() or kinds[kind]
|
|
||||||
if not title:
|
|
||||||
# No label: the marker line goes, the quote stays.
|
|
||||||
return ""
|
|
||||||
# The label as a paragraph of its own inside the quote, so it can't run
|
|
||||||
# into the passage's first sentence.
|
|
||||||
return "%s**%s**\n>\n" % (match.group(1), title)
|
|
||||||
|
|
||||||
return CALLOUT.sub(sub, text)
|
|
||||||
|
|
||||||
|
|
||||||
def toml_escape(value):
|
def toml_escape(value):
|
||||||
return str(value).replace("\\", "\\\\").replace('"', '\\"')
|
return str(value).replace("\\", "\\\\").replace('"', '\\"')
|
||||||
|
|
||||||
|
|
@ -421,7 +384,6 @@ class CapsuleGenerator(Generator):
|
||||||
s.get("LOSTIMAGES_DEAD_HOSTS", [])}
|
s.get("LOSTIMAGES_DEAD_HOSTS", [])}
|
||||||
sidecar_name = s.get("SMOLNET_SIDECAR", ".itsybitsy.toml")
|
sidecar_name = s.get("SMOLNET_SIDECAR", ".itsybitsy.toml")
|
||||||
footnotes = s.get("SMOLNET_FOOTNOTES", False)
|
footnotes = s.get("SMOLNET_FOOTNOTES", False)
|
||||||
callouts = s.get("SMOLNET_CALLOUTS", {})
|
|
||||||
# kapara serves the same remark42 thread the HTML widget shows, over
|
# kapara serves the same remark42 thread the HTML widget shows, over
|
||||||
# Gemini, Spartan and Nex. Articles only: the widget is on articles
|
# Gemini, Spartan and Nex. Articles only: the widget is on articles
|
||||||
# only, and a page with a thread nobody can reach from the site is a
|
# only, and a page with a thread nobody can reach from the site is a
|
||||||
|
|
@ -454,7 +416,6 @@ class CapsuleGenerator(Generator):
|
||||||
body = rewrite_html_links(body, urls, self._warn)
|
body = rewrite_html_links(body, urls, self._warn)
|
||||||
body = reveal_rot13(body, rot_attr)
|
body = reveal_rot13(body, rot_attr)
|
||||||
body = drop_dead_images(body, dead_hosts)
|
body = drop_dead_images(body, dead_hosts)
|
||||||
body = rewrite_callouts(body, callouts)
|
|
||||||
if footnotes:
|
if footnotes:
|
||||||
body = rewrite_footnotes(body, labels, self._warn)
|
body = rewrite_footnotes(body, labels, self._warn)
|
||||||
# Blank lines, not newlines: a heading and the line under it are
|
# Blank lines, not newlines: a heading and the line under it are
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
Title: otag 2: Plain-Text Tags for Your Files
|
|
||||||
Date: 2026-10-25 14:00
|
|
||||||
Category: MINIMAL
|
|
||||||
Slug: orvanitag-v2
|
|
||||||
Summary: A todo.txt style file tagger written in one week of June 2023 and then left alone, now back as version 2 with its YAML replaced by plain-text .otag files and nine plugins that write tags into the files themselves.
|
|
||||||
Tags: python, orvanitag, todo.txt
|
|
||||||
|
|
||||||
In June 2023 I spent one week writing a file tagger and then didn't touch it again for over three years. It is called *Orvani Tagger*, after עוֹרְבָנִי, the Hebrew name for the Eurasian jay, and the idea was simple: tag files the way *todo.txt* tags tasks. A `+project`, an `@context`, some `#tags` and a free note, typed on one line, searchable from the shell.
|
|
||||||
|
|
||||||
The 2023 version kept those tags in a `.context.yaml` in every folder, with a `.index.yaml` cache next to it and a configurable schema on top.
|
|
||||||
|
|
||||||
## One Line per File
|
|
||||||
|
|
||||||
Version 2 throws the YAML out. Each folder now has a plain `.otag` file, and every line in it is written in exactly the syntax you type on the command line:
|
|
||||||
|
|
||||||
```
|
|
||||||
Boombap Loop.wav --- +album @drums #boombap #lofi
|
|
||||||
song.mp3 --- +album @drums #lofi #snare first take
|
|
||||||
stems/ --- @archive
|
|
||||||
```
|
|
||||||
|
|
||||||
You can open it in any editor, grep it, diff it, and it travels with the folder when you copy, sync or zip it. A line without the ` --- ` separator stops `otag` with an error naming the file and the line, so a typo of mine can't quietly eat the rest of the folder. The configurable schema is gone too.
|
|
||||||
|
|
||||||
## Tags That Live in the File
|
|
||||||
|
|
||||||
The part that grew most is the plugins, from five to nine. `scan` reads tags that are already embedded in files and `write` puts them back, each in the place its format already has for it: the comment tag of an *MP3* or *FLAC*, the keywords field of a PDF, the description of an *ODT* or *DOCX*, a COM segment in a JPEG, an iTXt chunk in a PNG, the archive comment of a ZIP, a pax header in a tar, a `<meta name="otag">` in an EPUB, an `otag:` comment line at the top of a Python or Rust file. *TagSpaces* sidecars are read and written as well, so the two tools can share a folder. In every case the file's own data stays untouched, and another tool's comment (like the commit id `git archive` stores in a ZIP) is never overwritten.
|
|
||||||
|
|
||||||
The rest is housekeeping I had been putting off since 2023: `setup.py` and `requirements.txt` replaced by a `pyproject.toml` and `uv`, a committed `build/` directory deleted, the CLI rebuilt on argparse with one handler per command, a real test suite, and the licence moved from MIT to Apache-2.0. There is also a `SKILL.md`, so a coding agent can tag and search files with it without me explaining the syntax every time.
|
|
||||||
|
|
||||||
It's not on PyPI yet. For now it lives in the repository below.
|
|
||||||
|
|
||||||
## Repository
|
|
||||||
|
|
||||||
[orvanitag](https://code.randogoth.com/randogoth/orvanitag)
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue