Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
187 lines (144 sloc) 5.29 KB

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 file
  • context for extra lines around the match.
  • include To only display org-mode files
  • no-messages to get rid of errors
  • word-regexp to match whole words
  • with-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