In zsh we can levy the power of named folders to ease the task of dealing with Johnny.Decimal folders.
There are two approaches to this:
First, figure out at each call if there is a jd folder corresponding to the number
Second, create a hash table that is renewed periodically (or better, using incron), since jd folders don’t change on a daily basis.
In the following posts, I will detail the two approaches, as I’ve been using the first for some months now, but am passing on to the second, as the first causes unnecessary lag imho.
THE FIRST APPROACH IS HERE FOR HISTORIC AND PAEDAGOGICAL REASONS ONLY
In the end, you get something like that (yes, I know, I’ve got a lot of stuff to do):
The first approach uses the function zsh_directory_name which can be defined, say, in .zshrc, so that ~[jXX.YY] or ~[jXX] are computed and understood as referring to Johnny.Decimal folders. As computations are kinda slow, this method is only recommended if you create j.d folders very often. I am myself transitioning to the second approach. Here is the commented code below for this approach. Do not hesitate to ask for further as this bit of code is slightly obscure if you haven’t read the zsh docs.
# named directories for Johnny.Decimal
JD_ROOT=/home/user/docs
function j_check_depth {
# This function checks if the depth for a j.d folder is correct
# The error code is used for use in `if` statements
# $1 is id
# $2 is path
local depth="$(realpath -Ls --relative-to "${JD_ROOT}" "${2}" | awk -F/ '{print NF}')"
if [[ ${1%%.*} = $1 ]]; then
(( ($1 % 10) == 0 )) && [[ $depth -eq 1 ]] || [[ $depth -eq 2 ]]
else
[[ $depth -eq 3 ]]
fi
}
function zsh_directory_name {
# check whether we are in a named directory situation
if [[ $1 = n ]]; then
# check if we are trying to resolve a j.d folder
if [[ $2 == j* ]]; then
local dir
local -a list_dir
# define an array of all possible dirs within JD_ROOT
list_dir=(${JD_ROOT}/**/${2:1}*)
# here we build a reply, returning 0 is it is successful
# see zsh docs for the requirements
typeset -ga reply
for dir in $list_dir; do
if [[ -d "${dir}" ]] && j_check_depth "${2:1}" "${dir}"; then
reply=("${dir}")
return 0
elif [[ -f "${dir}" ]] && j_check_depth "${2:1}" "${dir}"; then
reply=("$(dirname "${dir}")")
return 0
fi
done
fi
# Here we define the name of the directories as they may appear in the prompt
# Given a folder XX(.YY)?-name, this outputs ~[jXX\1|$name].
elif [[ $1 = d ]]; then
if [[ $2 =~ $JD_ROOT'/[0-9]0.+' ]]; then
local finished=false dir="$2" number=-1 name=null
while (( ${#dir} > 1 )) && ! $finished; do
bname="$(basename "${dir}")"
if [[ "$bname" =~ '^[0-9]{2}(.[0-9]{2})?-.+$' ]]; then
number=${bname%%-*}
name=${bname#*-}
finished=true
else
dir="$(dirname "${dir}")"
fi
done
typeset -ga reply
reply=("j$number|$name" ${#dir})
return 0
fi
fi
return 1
}
The second approach is less exact. It uses a function to build a dictionary of named directories
function isJD { [[ "$1" =~ '^[0-9]{2}(\.[0-9]{2,})?-.+$' ]] }
function get_jdnum { echo "$1" | cut -d- -f1 }
function makeJDvars {
# echoes commands to make the named directories
for jdpath in $(find docs -maxdepth 3 -type d -iregex 'docs/[0-9][0-9].+');
do
jd_folder="$(basename $jdpath)"
if isJD $jd_folder; then
jdnum="$(get_jdnum $jd_folder)"
echo "hash -d j$jdnum='/home/ax/$jdpath'"
else
echo $jdpath is not j.d >&2
fi
done
}
source $ZDOTDIR/jdfolders
The function makeJDvars should be either called periodically or at each opening of zsh, depending on what you prefer. One of the drawbacks of using this approach is that it hides the name part of the directory. This is easily fixed by using a modified version of the script in the first version
function zsh_directory_name {
if [[ $1 = d ]]; then
folder="$(basename $2)"
if isJD $folder; then
jdnum="$(get_jdnum $folder)"
name="$(echo $folder | cut -d- -f2)"
typeset -ga reply
reply=("j$jdnum|$name" ${#2})
return 0
fi
fi
return 1
}
As part of moving things into a JD system, I’ve been putting files into my Dropbox folder, but there are some files that I don’t want to sync (for example, large files like downloaded movies - I have limited space, and if I lose them I can download them again in the future).
The Dropbox docs explain how to ignore individual files.
On a mac you can use xattr -w com.dropbox.ignored 1 citizen_kane.mp4
to ignore a file, or xattr -wr com.dropbox.ignored 1 movies/
to ignore all the files in a directory and its subdirectories.
You can use xattr -l to list the extended attributes on a file (including “com.dropbox.ignored” if it is there), which could be useful if you have some automated way to build your index and want to check if it something is synced with Dropbox or not.
This is a really useful thread all round, but I particularly wanted to thank @bbfile for this Dropbox tip. I wish I had know about this ages ago, as it would have save me a lot of fiddling around with symlinks!
I wanted to exclude my Financial and Medical directories from Dropbox for privacy and security reasons (and because I don’t need to consult them on other devices), and this works perfectly: I can sync the rest of my JD directory but just store these two areas locally.
Just curious, how do you back these up? Locally somehow I presume, do you also trust e.g. Backblaze with its option to encrypt before it leaves your device?
Yes, I have a Time Machine backup and also a local clone (using Carbon Copy Cloner) to a USB drive. Both backup disks use FileVault encryption, and both backup everything on my drive. Then I also (belt, braces and emergency piece of string!) back up to Backblaze, using a private encryption key, as an off-site backup.
I do find it useful to have the other files in Dropbox, because occasionally I need to access some of them on other devices. I keep all my manuals on there for example, and also PDF sewing patterns. If I find myself in a fabric shop, and see some fabric I think might be good for a particular pattern I own, I can open the PDF on my phone and check what length I would need to buy to make the garment, and what other haberdashery items I would need (interfacing, zips, buttons etc.). It takes the guesswork out of impulse fabric purchases! Not that I have been able to get to a bricks and mortar fabric shop in over a year .
I use fasd or any other variant of z jumping. It lets you jump to any JD folder instantly. If you wanna get really fancy you can manually weight the “frecency” of your folders but I’ve never had any collisions.
A small update concerning this. Using the following function with names defined as above:
function j {
pushd ~j$1
}
I’ve defined the following completion function which fetches longer descriptions from the index file using the module ~xaltsc/johnny.scm - sourcehut hg
_j () {
local ret=1
local -a dir_map
dir_map=("${(@f)$(~j91.03/johnny.scm --complete)}")
_describe 'johnny.decimal' dir_map && ret=0
return $ret
}
I changed to JD recently. Using zsh on an Arch OS I looked for a way to move between JD directories fast without remebering too many of the names. Instead I wanted to rely on tab completion, because thats my practiced way to move through the filesystem using the terminal. Furthermore, I wanted the completion candidates to be created at the moment I hit tab and not be sourced from an index which has to be updated regularly.
Because I couldn’t find a simple function which handles this as I want it, I wrote one myself. Here’s a small GIF showing how it works:
The function itself as well as the corresponding completion function should be inserted into the .zshrc directly.
The code is still a work in progress, since I want to add some more features to move through the JD system even more comfortable.
Everybody who is interested can find it on Codeberg (together with another small JD script to rename JD-directories). Don’t be confused by the repo name conataining “bash”, but the function only works with zsh. I’m happy if someone finds it useful.