Tagging Files in Org-Mode
Each of my notes are formatted in org-mode for Emacs, and I would like a small collection of scripts for dealing with their tags.
Essentially, a tag is just a line by itself that starts with #+TAGS:
Some rules:
- tags are separated with spaces
- tags are letters, numbers and underbars
- tags are lowercase (for me anyway)
Each of my perl scripts generated by this file, begin with the word,
tag
.
Tag Set
If a file doesn’t have a tag line, this script adds one with the given tags. If it already has a tag line, then we replace it with the given tags.
Call it with the option: -tags=”tag1 tag2”
$state++ if (/^\s*$/);
if (/^#\+TAGS: /) {
$_ = "#+TAGS: $tags\n";
$state = 10; # Setting this to 10 means we won't insert it
}
if ($state == 1) {
print "#+TAGS: $tags\n";
$state++;
}
This could be called will a shell loop like:
for FILE in *.org do echo -n "$FILE: " read TAGS ~/bin/tag-set -tags="$TAGS" $FILE done
Tag Add
Uses the automatic looping available in perl, and when we come to the magical “tags” line, we convert it by replacing the contents of the $_ variable.
Call it with the option: -tags=”tag1 tag2”
if (/^#\+TAGS:\s+(.*)\s*$/) { # Tags, as a string, in $1
@tags=split(/\s+/, $1); # Take original tags as an array
@newtags=split(/\s+/, $tags); # Take --tags="a b" as an array
$stags{$_}++ for (@tags); # Put the array into a hash "set"
$stags{$_}++ for (@newtags);
@set = keys(%stags); # Convert hash back into an array
$_="#+TAGS: @set\n" # Print the new version
}
Tag Find
Filters the files given to it on the command line to the ones that match the given tag.
Since this is a shell script that is called a lot, we might as well be flexible in how we specify the tags. Including this block gives us the “$tags” variable.
if [ $1 = "-t" -o $1 = "-tags" -o $1 = "--tags" ]; then
tags=$2
shift 2
elif echo $1 | grep -- '-t[a-z]*='; then
tags=$(echo $1 | sed 's/.*=//')
shift
else
tags=$1
shift
fi
The files to search follow the options for the name of the tag. To limit the files to both tags and some searchable text, see tgrep.
<<getopts-tag>>
exec grep --files-with-matches "#+TAGS: .*$tags" $*
Tag Grep
The tgrep
is similar to Tag Find, but limits the results based on
some searchable text strings. Also, it searches for particular
files in directories that contain my notes, specified by the
NOTEPATH
variable.
This version of the script is more verbose, but more attractive.
Perhaps it should have a different name than tgrep
.
<<getopts-tag>>
EXP="$*"
if [ -z "$NOTEPATH" ]; then
DIRS=$HOME/Technical
else
DIRS=$(echo $NOTEPATH | sed 's/:/ /g')
fi
for FILE in $(grep -r --files-with-matches --include='*.org' "#+TAGS: .*$tags" $DIRS)
do
if grep --silent --word-regexp --ignore-case "$EXP" $FILE
then
echo $FILE
echo "----------------------------------------------"
grep --max-count=1 --context=3 --no-messages --word-regexp \
--ignore-case "$EXP" $FILE
echo
fi
done
This version of the script looks like grep.
<<getopts-tag>>
DIRS=$(echo $NOTEPATH | sed 's/:/ /g')
for FILE in $(grep -r --files-with-matches --include='*.org' "#+TAGS: .*$tags" $DIRS)
do
grep --max-count=1 --context=0 --no-messages --word-regexp \
--ignore-case --with-filename "$*" $FILE
done
Notable grep
options include:
max-count
to only display the first match from filecontext
for extra lines around the match.include
To only display org-mode filesno-messages
to get rid of errorsword-regexp
to match whole wordswith-filename
to print the filename for each match.
Tag Listing
What tags have I allocated among a collection of note files? Pass this script a list of org files, and it will display all of the tags associated with them.
my %stags;
while (<>) {
if (/^#\+TAGS:\s+(.*)\s*$/) { # Tags, as a string, in $1
@tags=split(/\s+/, $1); # Take original tags as an array
$stags{$_}++ for (@tags); # Put the array into a hash "set"
}
}
@set = sort(keys(%stags)); # Convert hash back into an array
print "@set\n" # Print the new version
Technical Section
This file originally came from an org-mode file.
Create the script by tangling it with: C-c C-v t