better feature parity in Dmerge, updated docs

This commit is contained in:
Levi Neely 2025-11-25 14:33:21 +01:00
parent 58f6fa4fe8
commit 582870fc3b
2 changed files with 271 additions and 68 deletions

110
README.md
View File

@ -118,23 +118,17 @@ This program supports encrypted notes by integrating with [acme-crypt](https://g
## Extensions
Some extensions have also been ported.
Some extensions have also been ported. While these extensions, like the main program, try to stay as true as possible to the original program and be as feature-complete as possible, they is intentionally *not* exact replicas.
### Djournal
Concept from prot's [denote-journal](https://github.com/protesilaos/denote-journal). While `Djournal` tries to stay as true as possible to the original program and be as feature-complete as possible, it is intentionally *not* an exact replica.
Create or open journal entries with date-based titles. Supports daily, weekly, monthly, or yearly journals. Concept from prot's [denote-journal](https://github.com/protesilaos/denote-journal).
Create or open today's journal entry (or this week's, month's, or year's, depending on `interval` configuration):
**Configuration:**
```
Djournal
```
Customize journal behavior by editing variables in the `Djournal` script:
Supports: `d` (days), `h` (hours), `m` (minutes). You can use this to create multiple entries on the same day. To find an existing journal entry, use the search function in `Denote` described above. You can also pass the args in with the `2-1` chord.
Configure `Djournal` by editing the configuration options in the `Djournal` script.
```
```rc
# Journal interval: daily, weekly, monthly, yearly
interval=daily
@ -150,5 +144,97 @@ titleformat=full
signature=''
```
### Merge
**Basic Usage:**
Open today's journal entry (creates if it doesn't exist):
```
Djournal
```
Journal entries are stored in `journal/` subdirectory with the `journal` tag.
**Time Navigation:**
Navigate to past or future dates using time offsets:
```
Djournal +2d # 2 days from now
Djournal -1d # yesterday
Djournal -3h # 3 hours ago
Djournal +30m # 30 minutes from now
```
Supported units: `d` (days), `h` (hours), `m` (minutes)
### Dmerge
Merge notes together or move regions of text between notes while maintaining referential integrity. Concept from prot's [denote-merge](https://github.com/protesilaos/denote-merge).
**Configuration:**
Customize annotations by editing variables in the `Dmerge` script:
```rc
# Annotation for merged files (set to '' to disable)
annotation='MERGED FILE:'
# Annotation for merged regions (set to '' to disable)
regionannotation='MERGED REGION:'
```
**File Merge**
Merge an entire note into another note:
```
Dmerge <source-id> <dest-id>
```
This will:
- Strip frontmatter from source note
- Append source content to destination note with annotation
- Update all backlinks pointing to source to point to destination
- Delete the source note file
Example:
```
Dmerge 20251125T120000 20251125T130000
```
**Region Merge**
Move a selected region of text from one note to another. Select text in an Acme window, then:
```
Dmerge <dest-id>
```
This will:
- Move the selected text to the destination note
- Replace the selection with a link to the destination: `denote:<dest-id>`
- Leave the source note open (dirty) for you to `Put`
**With Formatting:**
You can wrap the merged region in various formats:
```
Dmerge <dest-id> <format>
```
Available formats:
- `plain` - No formatting (default)
- `plain-indented` - Add 4-space indentation
- `org-src` - Org source block (`#+begin_src` / `#+end_src`)
- `org-quote` - Org quote block (`#+begin_quote` / `#+end_quote`)
- `org-example` - Org example block (`#+begin_example` / `#+end_example`)
- `md-quote` - Markdown blockquote (prefix with `> `)
- `md-fence` - Markdown fenced code block (` ``` `)
Examples:
```
Dmerge 20251125T130000 org-quote
Dmerge 20251125T130000 md-fence
```

View File

@ -1,66 +1,183 @@
#!/usr/local/plan9/bin/rc
# Merge - Merge source note into destination note
# Usage: Merge <source-id> <dest-id>
# Merge - Merge notes or regions
# Usage: Dmerge <source-id> <dest-id> (file merge)
# Dmerge <dest-id> [format] (region merge, in acme)
# Formats: plain, plain-indented, org-src, org-quote, org-example, md-quote, md-fence
if(~ $#* 2) {
src=$1
dst=$2
}
if not {
echo 'usage: Merge source-id dest-id' >[1=2]
exit 1
}
# Annotation for merged files (set to '' to disable)
annotation='MERGED FILE:'
# Read paths and titles
srcpath=`{9p read denote/n/$src/path}
dstpath=`{9p read denote/n/$dst/path}
srctitle=`{9p read denote/n/$src/title}
# Annotation for merged regions (set to '' to disable)
regionannotation='MERGED REGION:'
if(~ $#srcpath 0 || ~ $#dstpath 0) {
echo 'error: note not found' >[1=2]
exit 1
}
# Extract content without frontmatter (everything after first blank line)
awk '
BEGIN { found_blank = 0 }
found_blank { print }
!found_blank && /^$/ { found_blank = 1 }
' $srcpath > /tmp/content.$pid
# Append source content to destination with annotation
{
cat $dstpath
echo ''
echo '* * *'
echo ''
echo 'MERGED FILE:' $srctitle
echo ''
cat /tmp/content.$pid
} > /tmp/merge.$pid
rm /tmp/content.$pid
# Update all backlinks pointing to source
9p read denote/n/$src/backlinks | awk -F'|' '{print $1}' | while(read linkid) {
linkid=`{echo $linkid | awk '{gsub(/^ +| +$/, ""); print}'}
if(! ~ $#linkid 0) {
linkpath=`{9p read denote/n/$linkid/path}
if(! ~ $#linkpath 0) {
# Replace denote:src with denote:dst in the file
sed 's/denote:'$src'/denote:'$dst'/g' $linkpath > /tmp/relink.$pid
cp /tmp/relink.$pid $linkpath
rm /tmp/relink.$pid
}
# Check for selection if in acme
hasselection=0
if(! ~ $winid '') {
selection=`{9p read acme/$winid/rdsel}
if(! ~ $#selection 0 && ! ~ $selection '') {
hasselection=1
}
}
# Write merged content to destination
cp /tmp/merge.$pid $dstpath
rm /tmp/merge.$pid
# Parse arguments and determine mode
if(~ $#* 2) {
# Two args: region merge with format OR file merge
if(~ $hasselection 1) {
# Region merge with format
dst=$1
format=$2
mode=region
# Delete source note
echo 'd' | 9p write denote/n/$src/ctl
# Extract source note ID from window tag
tag=`{9p read acme/$winid/tag}
filename=`{echo $tag | awk '{for(i=1;i<=NF;i++) if($i ~ /[0-9]{8}T[0-9]{6}/) {print $i; exit}}'}
src=`{echo $filename | sed 's/--.*//'}
}
if not {
# File merge
src=$1
dst=$2
mode=file
}
}
if not if(~ $#* 1) {
# One arg: region merge (plain format)
if(~ $winid '') {
echo 'region merge requires running from acme window' >[1=2]
exit 1
}
if(~ $hasselection 0) {
echo 'no text selected' >[1=2]
exit 1
}
echo 'Merged' $src 'into' $dst
dst=$1
format=plain
mode=region
# Extract source note ID from window tag
tag=`{9p read acme/$winid/tag}
filename=`{echo $tag | awk '{for(i=1;i<=NF;i++) if($i ~ /[0-9]{8}T[0-9]{6}/) {print $i; exit}}'}
src=`{echo $filename | sed 's/--.*//'}
}
if not {
echo 'usage: Dmerge source-id dest-id OR Dmerge dest-id [format] (in acme)' >[1=2]
exit 1
}
# Branch based on mode
if(~ $mode file) {
# FILE MERGE MODE
# Read paths and titles
srcpath=`{9p read denote/n/$src/path}
dstpath=`{9p read denote/n/$dst/path}
srctitle=`{9p read denote/n/$src/title}
if(~ $#srcpath 0 || ~ $#dstpath 0) {
echo 'error: note not found' >[1=2]
exit 1
}
# Extract content without frontmatter (everything after first blank line)
awk '
BEGIN { found_blank = 0 }
found_blank { print }
!found_blank && /^$/ { found_blank = 1 }
' $srcpath > /tmp/content.$pid
# Append source content to destination with optional annotation
{
cat $dstpath
if(! ~ $annotation '') {
echo ''
echo '* * *'
echo ''
echo $annotation $srctitle
echo ''
}
cat /tmp/content.$pid
} > /tmp/merge.$pid
rm /tmp/content.$pid
# Update all backlinks pointing to source
9p read denote/n/$src/backlinks | awk -F'|' '{print $1}' | while(read linkid) {
linkid=`{echo $linkid | awk '{gsub(/^ +| +$/, ""); print}'}
if(! ~ $#linkid 0) {
linkpath=`{9p read denote/n/$linkid/path}
if(! ~ $#linkpath 0) {
# Replace denote:src with denote:dst in the file
sed 's/denote:'$src'/denote:'$dst'/g' $linkpath > /tmp/relink.$pid
cp /tmp/relink.$pid $linkpath
rm /tmp/relink.$pid
}
}
}
# Write merged content to destination
cp /tmp/merge.$pid $dstpath
rm /tmp/merge.$pid
# Delete source note
echo 'd' | 9p write denote/n/$src/ctl
echo 'Merged' $src 'into' $dst
}
if not if(~ $mode region) {
# REGION MERGE MODE
# Read destination path
dstpath=`{9p read denote/n/$dst/path}
if(~ $#dstpath 0) {
echo 'error: destination note not found' >[1=2]
exit 1
}
# Apply formatting to selection
switch($format) {
case plain
formatted=$selection
case plain-indented
formatted=`{echo $selection | sed 's/^/ /'}
case org-src
formatted=`{echo '#+begin_src'; echo $selection; echo '#+end_src'}
case org-quote
formatted=`{echo '#+begin_quote'; echo $selection; echo '#+end_quote'}
case org-example
formatted=`{echo '#+begin_example'; echo $selection; echo '#+end_example'}
case md-quote
formatted=`{echo $selection | sed 's/^/> /'}
case md-fence
formatted=`{echo '```'; echo $selection; echo '```'}
case *
echo 'unknown format:' $format >[1=2]
echo 'valid formats: plain, plain-indented, org-src, org-quote, org-example, md-quote, md-fence' >[1=2]
exit 1
}
# Append formatted selection to destination with optional annotation
{
cat $dstpath
if(! ~ $regionannotation '') {
echo ''
echo '* * *'
echo ''
echo $regionannotation
echo ''
}
echo "$formatted"
} > /tmp/merge.$pid
# Write merged content to destination
cp /tmp/merge.$pid $dstpath
rm /tmp/merge.$pid
# Replace selection with link to destination
echo -n 'denote:'$dst | 9p write acme/$winid/wrsel
echo 'Region merged from' $src 'into' $dst '(format:' $format')'
}