This commit is contained in:
bth 2024-01-11 10:52:52 -08:00
commit 5e1b2ac657
26 changed files with 393 additions and 0 deletions

9
bash/last/last.sh Executable file
View file

@ -0,0 +1,9 @@
# Get the last file name of any numbered series of text files.
# file format:
# in regex terms:
# [0-9]+\.[.*\.]*md|txt
# in plain terms:
# [leading numbers with consistent width].[optional string descriptor and . separator][md|txt]
ls | egrep '^\d.*\.md|txt' | tail -n 1

0
bash/last/test/001.md Executable file
View file

0
bash/last/test/002.blah.md Executable file
View file

0
bash/last/test/004.foo.md Executable file
View file

0
bash/last/test/005.md Executable file
View file

0
bash/last/test/009.boo.md Executable file
View file

0
bash/last/test/098.baa.md Executable file
View file

0
bash/last/test/099.poo.md Executable file
View file

23
bash/mdcat/mdcat.sh Executable file
View file

@ -0,0 +1,23 @@
# cat for Markdown in my ultralight writing database
# wherein concatenated filed need a "\n---\n\n" between them.
#############################################################################
# cat with file headers | replace with section string | remove first instance
# NOTE: only need to add one newline, because the header inserted by tail
# implicitly adds two : \n==> foo <==\n
tail -n +1 "$@" | sed 's/^==> .* <==$/---\n/g' | tail -n +3
##############################
# failed approaches
# find . -type f -name "$@" -exec cat {} \; -printf "\n---\n\n"
# this leaves one trailing, could cat the whole thing and then cut the tail off
# MDCATALL=`for i in "$@";
# do
# cat $i
# echo '\n---\n\n'
# done`
# echo $MDCATALL | head -n +2

12
bash/next/args.sh Executable file
View file

@ -0,0 +1,12 @@
# why can't i use bash if statement, before appending an empty arg string?
last=`ls | egrep '^\d.*\.md|txt' | tail -n 1`
if ($* != "")
last=last + $*;
awk '
{
print $0
}
END {print NR}' last

2
bash/next/emacs_script.sh Executable file
View file

@ -0,0 +1,2 @@
# emacs --debug-init blah
emacs `ls | grep '^\d' | tail -n 1`

113
bash/next/next.sh Executable file
View file

@ -0,0 +1,113 @@
# Get the file name in any numbered series of text files and open with emacs
# Usage:
# $ . next [descriptor]
# Handles an optional string descriptor. Like this:
# $ ls
# 011.blah.md
# $ . next foo
# 012.foo.md
# File format:
# in regex terms:
# [0-9]+\.[.*\.]*md|txt
# in plain terms:
# [leadings numbers with consistent width].[optional string descriptor and . separator][md|txt]
# what this does:
# ls | grep [regex for number.*.md] | get last | awk script to increment the digits, preserve the zero prefix, add string descriptor if supplied, and add back extension
# Note the egrep '^\d.*\.md|txt' (matches whole string) to account for an optional string between the initial number and the file extension. As in '435.blah.md'.
# old version, relied on python, which didn't handle leading zeros properly, but treated them as binary:
# ls | grep '\d.*\.md' | tail -n 1 | sed s/'\..*md'// | sed s/.*/'print & + 1'/ | python - | sed s/.*/'&.md/'
# BUGS
# 1. Emacs won't accept piped input as a file name. I'd rather keep this as a separate utility, which only printed the right filename, and then use an alias to pipe the filename to emacs, or invoke it manually.
# A.
# What I want, is this syntax:
# next [optional arg]
# To both get the file and open emacs with it.
# B.
# But this doesn't work:
# next | emacs
# Because it sees the stdin the pipe wants to feed it as a substitute for the tty in the new session, rather than as input for startup only, i.e. the filename.
# C.
# Wrapping the whole shell command here works, like this:
# emacs `ls | grep ...`
# But emacs doesn't like being run inside a child level shell, because it can't load .emacs properly. So you must run this file within the current shell using ".", like this:
# . next
# . next foo
# D.
# This does not work, because .profile must be executed, meaning that the "" expression is peeked by the shell:
# alias next="emacs `next.sh $1`"
# E.
# This does not work either, because the arg never gets filled out when evoked:
# alias next='emacs `next.sh $1`'
# F.
# This works and is the current solution:
# alias next='. next.sh'
# Which relies on 2 somewhat obscured facts:
# 1. This script calls emacs itself, wrapping the real work in ``
# 2. The alias is a simple substitution, which fortunately seems to happen before args are processed by the shell, meaning that the shell sees: ". next.sh arg"
# 3. NOTE: the symbolic link in the PATH should not be simply "next", because it
# may in certain situations conflict with the alias. The link should be "next.sh" -> ~/config/..
# 4. NOTE: I use 'e' rather than 'emacs', to use my clever emacs/emacsclient function.
e `
ls | egrep '^[[:digit:]].*\.md|txt' | tail -n 1 | awk '
BEGIN {
# set field separator, blank by default
FS = "."
}
{
newnum = $1 + 1
nzeros = length($1) - length(int(newnum))
# could I do this with a simpler regex to select all leading zeros?
leadingzeros = ""
if (nzeros > 0)
for (i = 1; i <= nzeros; i++)
leadingzeros = sprintf("%s%s", leadingzeros, "0")
if (nzeros > 0)
newnum = sprintf("%s%s", leadingzeros, newnum)
# start by prefixing the number, now with leading zeros
finalstr = newnum
# allow an arg input to fill out descriptor string.
# single quote break to allow the shell to fill it out, double quotes so awk will treat it as a string
arg = "'$1'"
if (arg != "")
finalstr = sprintf("%s.%s", finalstr, arg)
# append last field by default
finalstr = sprintf("%s.%s", finalstr, $NF)
# previous loop to reassemble all fields. But I do not want the old middle string here
# Keeping it here for reference.
# for (i = 2; i <= NF; i++)
# finalstr = sprintf("%s.%s", finalstr, $i)
# }
}
END {
print finalstr
}'
`

0
bash/next/test/001.md Executable file
View file

0
bash/next/test/002.blah.md Executable file
View file

0
bash/next/test/004.foo.md Executable file
View file

0
bash/next/test/005.md Executable file
View file

0
bash/next/test/009.boo.md Executable file
View file

0
bash/next/test/098.baa.md Executable file
View file

0
bash/next/test/099.poo.md Executable file
View file

43
bash/rename/rename.sh Executable file
View file

@ -0,0 +1,43 @@
# rename : renames the string descriptor used in numbered series.
# Usage:
# > rename 100.md bar
# or
# > rename 100.foo.md bar
# is equivalent to:
# > mv 100.md 100.bar.md
# > mv 100.foo.md 100.bar.md
# split the args by a newline so that awk reads them as separate "records"
mvcmd=`echo "$1
$2" |
awk '
BEGIN {
# Builtin variable, "field seperator". The neatness of this is the main reason
# I use awk to parse the filename.
FS="."
oldname = ""
num = ""
ext = ""
newname = ""
}
NR == 1 {
oldname = $0
num = $1
# important that this is the last field, not numbered, to allow for a file
# without a string descriptor.
ext = $NF
}
NR == 2 {
newname = sprintf("%s.%s.%s", num, $0, ext)
}
END {
print oldname, newname
}
'`
mv $mvcmd

0
bash/rename/test/001.bar.txt Executable file
View file

0
bash/rename/test/06.foo.md Executable file
View file

0
bash/rename/test/07.blah.txt Executable file
View file

0
bash/rename/test/100.foo.md Executable file
View file

View file

191
uldb.el Normal file
View file

@ -0,0 +1,191 @@
;;; uldb.el --- An ultralight database for composition. -*- lexical-binding: t -*-
;; by bth
;; An ultralight database for writing and project composition, using only unique
;; filenames, POSIX filesystem conventions, and the builtin `project' package.
;; This is somewhat like org-roam, except without any database dependency. (And
;; it uses Markdown rather than org, which I don't care for.)
;; This depends on a few simple ideas:
;; 1. A numbered filename specification which serves as unique id.
;; 2. Placing that same id into the file header, so that concatenated files
;; have reference back to their source.
;; 3. Using a source "journal/" directory, where source files should first be
;; composed and where we can assume a file exists if not elsewhere.
;; 4. Using the built-in `project' package to organize compositions built from
;; these source files.
;; 5. (Optional) Use `embark' to visit files with a single keystroke.
;; features provided:
;; `uldb-journal-header' defines the standard header
;; `uldb-project-find-meta' via `project-switch-project': open a file like
;; *meta.md in a given project.
;; `uldb-file-at-point-advice' via `embark-dwim': open file in known projects
;; with fallback to journal/
;; `uldb-grep-tag': search files with same tag
(require 'package)
;; Primary path glob for the journal. This allows various mountpoints.
(setq uldb-journal-path-glob ".*/writing/journal")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; journal header
(defun uldb-iso-8601 ()
"Insert a date stamp in ISO 8601 order, but with periods in place of dashes."
(format-time-string "%Y.%m.%d")
)
(defun uldb-journal-header ()
"Create a uldb-journal header composed of:
# buffer-name minus the .md file type
: date
"
(interactive)
(insert
(format "# %s\n: %s\n\n" (substring (buffer-name) 0 -3) (uldb-iso-8601))
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; journal/.project.el
;; normally .git detection works fine. But for my journal/ I don't git.
;; https://michael.stapelberg.ch/posts/2021-04-02-emacs-project-override/
(defun uldb-project-override (dir)
"Returns the parent directory containing a .project.el file,
if any, to override the standard project.el detection logic when
needed.
"
(let ((override (locate-dominating-file dir ".project.el")))
(if override
(list 'vc nil override)
nil)))
;; Cannot use :hook because 'project-find-functions does not end in -hook
(add-hook 'project-find-functions #'uldb-project-override)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; find-file-at-point
;; the journal/ serves as content origin, so it must be treated specially.
(setq uldb-journal-path (car (seq-filter
(lambda (path)
;; this way I don't have to hardcode the full path:
(string-match uldb-journal-path-glob path))
;; stores all project roots in a list of strings:
(project-known-project-roots))))
;;;;;;;;;;;;;;;;;;;;;
;; find-file C-x C-f solution
;; combined with the hook into `file-name-at-point-functions', this means I can
;; run find-file at point, followed by M-n :
(defun uldb-journal-find-file ()
(let ((default-directory uldb-journal-path)
(thing (thing-at-point 'filename))
)
(expand-file-name thing))
)
(add-hook 'file-name-at-point-functions 'uldb-journal-find-file nil t)
;;;;;;;;;;;;;;;;;;
;; Embark solution (optional)
;; Load this with an advice:
;; (advice-add 'embark-target-file-at-point :around 'uldb-file-at-point-advice)
;; `embark-target-file-at-point' uses `ffap-file-at-point', which uses
;; `default-directory' to expand the filename.
;; NOTE: decided to use an advice rather than add my own function to
;; `embark-target-finders', since I'd have to remove the file finder and put my
;; function in the same place in the list anyway.
(defun uldb-file-at-point-advice (origfunc)
"Like find-file-at-point, but checks all known project paths.
In this order:
1. current project root
2. all other project roots
3. journal/ path
Since journal/ is the origin and other projects shadow it.
TODO: Use a special directory name for potentials/ or the like, in case it's not
in the root dir.
"
(or
;; current project
(funcall origfunc)
;; all other projects
(let ((result))
(cl-loop for project-path in
(seq-remove
(lambda (path)
(or (string-match uldb-journal-path path)
(when (project-current)
(string-match (caddr (project-current)) path))
))
(project-known-project-roots))
do (let ((default-directory project-path))
(setq result (funcall origfunc)))
if result
return result
))
;; journal path
(let ((default-directory uldb-journal-path))
(funcall origfunc))
))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; C-x p p
(defun uldb-project-find-meta ()
(interactive)
;; this works because `project-switch-project' overrides the dir:
(find-file (expand-file-name "*meta.md" (caddr (project-current))) t)
)
;; overwrite
(setq project-switch-commands
'(
(uldb-project-find-meta "open meta" "m")
(project-dired "dired")
(project-find-file "find file")
(project-find-regexp "find regexp")
))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; tags
;; TODO: do I want to search all projects?
(defun uldb-grep-tag ()
"Run project-find-regexp on the current identifer as a journal frontmatter tag.
NOTE: this searches in the current project root only.
"
(interactive)
(project-find-regexp (concat "\\+\\ " (thing-at-point 'word)))
)
;; NOTE: to export files with a given tag:
;; grep -l '\+ TAG' *md | xargs cp -t /tmp/
;; note the -l flag to grep to output files only, and the -t flag to cp to
;; specify a target dir (since it appends the list from xargs I think).
;; Or, in dired, run `find-grep-dired'.
(provide 'uldb)