gnu-emacs-sources
[Top][All Lists]
Advanced

[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





reply via email to

[Prev in Thread] Current Thread [Next in Thread]