commit 5e1b2ac657e6ac684f3f74ba6bdbacb9c3f55b19 Author: bth Date: Thu Jan 11 10:52:52 2024 -0800 init diff --git a/bash/last/last.sh b/bash/last/last.sh new file mode 100755 index 0000000..8cf8655 --- /dev/null +++ b/bash/last/last.sh @@ -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 diff --git a/bash/last/test/001.md b/bash/last/test/001.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/last/test/002.blah.md b/bash/last/test/002.blah.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/last/test/004.foo.md b/bash/last/test/004.foo.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/last/test/005.md b/bash/last/test/005.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/last/test/009.boo.md b/bash/last/test/009.boo.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/last/test/098.baa.md b/bash/last/test/098.baa.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/last/test/099.poo.md b/bash/last/test/099.poo.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/mdcat/mdcat.sh b/bash/mdcat/mdcat.sh new file mode 100755 index 0000000..ace6c27 --- /dev/null +++ b/bash/mdcat/mdcat.sh @@ -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 diff --git a/bash/next/args.sh b/bash/next/args.sh new file mode 100755 index 0000000..2278e57 --- /dev/null +++ b/bash/next/args.sh @@ -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 diff --git a/bash/next/emacs_script.sh b/bash/next/emacs_script.sh new file mode 100755 index 0000000..b5b2f31 --- /dev/null +++ b/bash/next/emacs_script.sh @@ -0,0 +1,2 @@ +# emacs --debug-init blah +emacs `ls | grep '^\d' | tail -n 1` diff --git a/bash/next/next.sh b/bash/next/next.sh new file mode 100755 index 0000000..a5885d0 --- /dev/null +++ b/bash/next/next.sh @@ -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 +}' +` diff --git a/bash/next/test/001.md b/bash/next/test/001.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/next/test/002.blah.md b/bash/next/test/002.blah.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/next/test/004.foo.md b/bash/next/test/004.foo.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/next/test/005.md b/bash/next/test/005.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/next/test/009.boo.md b/bash/next/test/009.boo.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/next/test/098.baa.md b/bash/next/test/098.baa.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/next/test/099.poo.md b/bash/next/test/099.poo.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/rename/rename.sh b/bash/rename/rename.sh new file mode 100755 index 0000000..2fb9cae --- /dev/null +++ b/bash/rename/rename.sh @@ -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 diff --git a/bash/rename/test/001.bar.txt b/bash/rename/test/001.bar.txt new file mode 100755 index 0000000..e69de29 diff --git a/bash/rename/test/06.foo.md b/bash/rename/test/06.foo.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/rename/test/07.blah.txt b/bash/rename/test/07.blah.txt new file mode 100755 index 0000000..e69de29 diff --git a/bash/rename/test/100.foo.md b/bash/rename/test/100.foo.md new file mode 100755 index 0000000..e69de29 diff --git a/bash/rename/test/245.blah_foo.md b/bash/rename/test/245.blah_foo.md new file mode 100755 index 0000000..e69de29 diff --git a/uldb.el b/uldb.el new file mode 100644 index 0000000..2fd70bd --- /dev/null +++ b/uldb.el @@ -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)