[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
retags 1.0
From: |
Tom Tromey |
Subject: |
retags 1.0 |
Date: |
Fri, 07 Mar 2008 10:41:59 -0700 |
User-agent: |
Gnus/5.11 (Gnus v5.11) Emacs/22.1 (gnu/linux) |
This is release 1.0 of "retags", a script for auto-updating TAGS
files.
For a long time, I've wanted to have my TAGS files auto-update when
files change. This script implements this.
First, make a .retags file in your source directory. This file tells
retags how to run etags on your source files. Each line in this file
is a glob which matches file names followed (optionally) by etags
arguments. For instance, this simple file suffices for many c-based
projects:
*.[chyl]
Now, run retags: retags --force $srcdir
--force causes retags to make the TAGS file for the first time. Now
it will run in the background and update the TAGS file whenever a
source file changes.
You can stop a running retags with "retags --kill $srcdir".
You need the inotifywait program for retags to run.
Let me know what you think,
Tom
#! /bin/bash
# retags [--force] [--verbose] [--kill] DIR
# Run in background and watch for changes in DIR.
# Re-run etags whenever something changes.
# --kill means stop a running retags in DIR.
# --force means force re-creating all TAGS files
# --verbose turns on debug output
# Reads DIR/.retags to see how to run etags.
# Each line of .retags is a glob matching a file name, followed
# by etags arguments (don't use -o or the like).
# Comments (with '#') or blank lines are ok.
# For instance, a typical, simple one is:
# *.[chyl]
# A more complicated one:
# *.def --language=none --regex='/DEFTREECODE [(]\([A-Z_]+\)/\1/'
# By Tom Tromey <address@hidden>
# Licensed under the GPL.
# Version 1.0.
kill=
force=
verbose=
function usage ()
{
echo "usage: retags [--force] [--verbose] [--kill] DIR" 1>&2
exit 1
}
while [[ $# -ne 1 ]]; do
case "$1" in
(--kill) kill=yes ;;
(--force) force=yes ;;
(--verbose) verbose=yes ;;
(*) break
esac
shift
done
if test $# -ne 1; then
usage
fi
dir=$1
if test -n "$kill"; then
rm -f $dir/.retags.pid
exit 0
fi
if ! test -f $dir/.retags; then
echo "retags: no such file $dir/.retags" 1>&2
exit 1
fi
dir=$(cd $dir && pwd)
cd $dir
if test -f .retags.pid; then
# Kill the running retags.
rm .retags.pid
fi
mkdir -p .retags.d
declare -a file_contents
function verbose ()
{
if test -n "$verbose"; then
echo "$@"
fi
}
function run ()
{
verbose "$@"
"$@"
}
function read_retags ()
{
local -i idx=0
file_contents=()
while read line; do
line=$(echo "$line" | sed -e 's, *#.*$,,')
if test "$line" != ""; then
verbose "Adding $line"
file_contents[$idx]=$line
idx=idx+1
fi
done < .retags
}
function finish ()
{
if test "$(cat .retags.pid 2> /dev/null)" = "$$"; then
rm -f .retags.pid
fi
exit 0
}
function update_etags ()
{
local args
local files=$(echo .retags.d/*)
if test "$files" != '.retags.d/*'; then
# We could use etags -i, but having a large number of includes
# makes emacs freak out.
cat $files > TAGS
fi
}
function handle_file ()
{
local event=$1
local file=$2
local -i idx=0
local update=
case $file in
(*~ | .#* | \#* | .retags.d* | TAGS)
return
;;
(.retags.pid | */.retags.pid)
finish
return
;;
(.retags | */.retags)
if test $event = DELETE; then
finish
elif test $event = CLOSE_WRITE; then
read_retags
fi
return
esac
# We may get these even without listening for them explicitly
if test "$event" = DELETE_SELF || test $event = "UNMOUNT"; then
finish
fi
# See if the file matches any pattern the user provided.
local found pattern args
while [[ $idx -lt address@hidden ]]; do
pattern=$(echo "${file_contents[$idx]}" | sed -e 's, .*$,,')
case $file in
($pattern)
found=yes
args=$(echo "${file_contents[$idx]}" | sed -e 's,^[^ ]*\(
\|$\),,')
break;;
esac
idx=idx+1
done
if test -z "$found"; then
return
fi
# Lop off the pattern, leaving the arguments.
shift
# A silly way to generate a variable name from the file name. Why
# did I write this in sh again? Note the leading 't'.
md5=t$(echo "$file" | md5sum | sed 's/ *.*$//')
tfile=.retags.d/$md5
case $event in
(MODIFY)
eval $md5=modified
;;
(CREATE)
eval $md5=modified
update=yes
;;
(DELETE)
if test -f $tfile; then
rm -f $tfile
update=yes
fi
;;
(CLOSE_WRITE)
if test "$(eval echo \$$md5)" = modified; then
# Update the tags file for this source file. We have to
# play games here to prevent relativization, so we can
# cat the files into TAGS. If we ever use -i instead,
# fix this.
run eval "etags $args -o .retags.tmp $file"
mv .retags.tmp $tfile
eval $md5=
# See comment in update_etags.
update=yes
fi
;;
esac
if test -n "$update"; then
update_etags
fi
}
# Make the TAGS file when it is missing.
function first_time ()
{
local file
echo -n "Making TAGS file..."
for file in *; do
# Pretend the file was updated.
handle_file MODIFY $file
handle_file CLOSE_WRITE $file
done
update_etags
echo "done"
}
# Read the config file and make the tags file the first time, if
# needed.
read_retags
if ! test -f TAGS || test -n "$force"; then
first_time
fi
{
# FIXME: use -r and handle subdirs
# FIXME: handle moved_to and moved_from
inotifywait -mq -e modify -e close_write -e delete -e create . |
while read ignore events file; do
# Only bother with the first event.
event=$(echo "$events" | sed -e 's/,.*$//')
handle_file $event $file
done
} &
echo $! > .retags.pid
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- retags 1.0,
Tom Tromey <=