From d5857189b15ebd9c4a1c6f328c50df988b036b52 Mon Sep 17 00:00:00 2001 From: Xavier Maillard <108915858+xmailla@users.noreply.github.com> Date: Sun, 2 Feb 2025 18:36:25 +0100 Subject: [PATCH] Upload of 1.3 version released on 1999-05-24 --- FAQ | 51 +++ INSTALL | 83 ++++ Makefile.in | 167 +++++++ README | 58 +++ configure | 956 +++++++++++++++++++++++++++++++++++++++++ configure.in | 18 + install-sh | 238 ++++++++++ mailcrypt/mailcrypt.el | 463 ++++++++++++++++++++ mailcrypt/mc-pgp.el | 603 ++++++++++++++++++++++++++ mailcrypt/mc-remail.el | 831 +++++++++++++++++++++++++++++++++++ mailcrypt/mc-toplev.el | 641 +++++++++++++++++++++++++++ mkinstalldirs | 32 ++ records-dindex.el | 147 +++++++ records-index.el | 306 +++++++++++++ records-load.el | 7 + records-util.el | 283 ++++++++++++ records-vars.el | 144 +++++++ records.el | 919 +++++++++++++++++++++++++++++++++++++++ records.info | 777 +++++++++++++++++++++++++++++++++ records.ps | Bin 0 -> 147159 bytes recordsadmin.in | 801 ++++++++++++++++++++++++++++++++++ 21 files changed, 7525 insertions(+) create mode 100644 FAQ create mode 100644 INSTALL create mode 100644 Makefile.in create mode 100644 README create mode 100644 configure create mode 100644 configure.in create mode 100644 install-sh create mode 100644 mailcrypt/mailcrypt.el create mode 100644 mailcrypt/mc-pgp.el create mode 100644 mailcrypt/mc-remail.el create mode 100644 mailcrypt/mc-toplev.el create mode 100644 mkinstalldirs create mode 100644 records-dindex.el create mode 100644 records-index.el create mode 100644 records-load.el create mode 100644 records-util.el create mode 100644 records-vars.el create mode 100644 records.el create mode 100644 records.info create mode 100644 records.ps create mode 100644 recordsadmin.in diff --git a/FAQ b/FAQ new file mode 100644 index 0000000..14aa9bf --- /dev/null +++ b/FAQ @@ -0,0 +1,51 @@ +FAQ +--- + +1. Can I change the location of .emacs-records? +2. Can I have spaces in the subject title? + + +-------------------------------------------------------------------------- +1. The package installs .emacs-records in my home directory. I somehow dont +like the idea of cluttering my home directory. I was wondering if it was +possible to move this file to my records directory. Can I do it after I +install it and change the corresponding lines in recordsadmin or do I have to +install it again? If I have to install it again, which files do I have to +change for this to take effect? + +This is a bootstraping problem. Since recordsadmin creates this file the first +time and then uses this file everytime you invoke it, I assumed a fixed place +for it. Otherwise I would have to store the location of this file in some +known place. I could use configure to ask the user to specify an area, but +then what is the appropriate default? Making the default be inside the records +directory requires some work because the records directory variable itself is +customizable. + +The way you can move .emacs-records to ~/records is the following: + a) Set the following variable in recordsadmin + # global - should be set by makefile + $records_init_file="$ENV{HOME}/records/.emacs-records"; + + b) In .emacs + (load "~/records/.emacs-records") + (setq records-init-file (concat (getenv "HOME") "/records/.emacs-records")) + +2. When I insert a new record using C-c C-i or the menu, and type in the +subject (or title) of the record, emacs does completion. And completion does +not allow for space in the record title. So I am forced to have either single +word titles or words-separated-by-hyphens or something similar titles. Is this +a feature or a bug? I dont mind having to type to hyphens or underscores, but +it would help to have spaces. + +You can have space in titles. The way you do it is to type C-q before typing +in the space. That puts the literal space character in emacs. So for example +you would type + +C-cC-iSpaceC-qTest + +and that would generate + +* Space Test +------------ +link: <../../99/04/040999#* Space Test> + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..f7b9e83 --- /dev/null +++ b/INSTALL @@ -0,0 +1,83 @@ +Make sure that you read the README file first. + +1. Run ./configure +This program is provided in the source (do not run your own configure). +If you want to specify the directory in which recordsadmin should be installed, +you could say something like + +./configure --prefix $HOME + +Make sure ${bindir} (generally ${prefix}/bin) in the Makefile exists and is +writable by you. + +2. Run make install (or just make) +After the lisp files have been compiled, this procedure is interactive and asks +for user defaults. Note: The lisp files are not installed anywhere. + +3. The changes to your system that "make install" does are the following: + + a) Adds ~/.emacs-records (if this file already exists, it converts only + specific parts of the file). + + b) Adds a couple of lines to ~/.emacs + + c) Adds recordsadmin to ${bindir} + + d) Creates your records directory (~/records or whatever you have specified + during make) and adds some indexes there. If this directory already + exists, then it reindexes your records and converts them to the new + format. If you don't trust the conversion, I would really suggest that + you KEEP A COPY OF YOUR RECORDS BEFORE DOING THE CONVERSION. Check the + conversion by hand to make sure that the conversion is fine. Specially + look for the following: HAVE EMBEDDED LINKS IN YOUR RECORDS BEEN + CONVERTED CORRECTLY. Please tell me if this conversion is not okay. + +4. Copy the next set of lines from +";;;; records-mode" to +";;;; records-mode end" into your ~/.emacs. Make sure that these lines occur +after the following line (which has been automatically added during install) in +your .emacs +(load "~/.emacs-records") +I did not automate this process since users may wish to change the key +settings. Users will probably use the records-goto-today function most +often. Bind it to a simple key. + +;;;; records-mode +; Define key bindings for functions called from outside records mode + +; The preferred binding for records-goto-today - uncomment to use it +(define-key global-map [?\C-c ?n] 'records-goto-today) +(define-key global-map [?\C-c ?-] 'records-underline-line) + +; The Ctrl-x n map +(define-key global-map [?\C-x ?n ?t] 'records-goto-today) +(define-key global-map [?\C-x ?n ?r] 'records-insert-record-region) +(define-key global-map [?\C-x ?n ?b] 'records-insert-record-buffer) + + +; Hook up to the calendar mode +(add-hook 'calendar-load-hook + (function + (lambda () + (define-key calendar-mode-map "n" 'records-calendar-to-record)))) + +;;;*** OPTIONAL ***;;; + +; If you like abbrev mode +(add-hook 'records-mode-hooks + (function + (lambda () + (abbrev-mode 1)))) + +; If you want to be brought to today's record on startup +(records-goto-today) + +;;;; records-mode end + +5. Restart your emacs (unless you know how to evaluate regions in a buffer - in +which case get the key bindings and load in .emacs-records). + +After restarting emacs, go back to the README file for using records mode in +emacs. + +Ashvin Goel (ashvin@cse.ogi.edu) diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..c0ed7b3 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,167 @@ +### +### Makefile.in +### +### $Id: Makefile.in,v 1.6 1999/05/25 02:07:22 ashvin Exp $ +### +### Copyright (C) 1996 by Ashvin Goel +### +### This file is under the Gnu Public License. + +# $Log: Makefile.in,v $ +# Revision 1.6 1999/05/25 02:07:22 ashvin +# Renamed encrypt to mailcrypt. +# Added records.info file. +# +# Revision 1.5 1999/04/10 19:42:13 ashvin +# Added FAQ in DISTFILES +# +# Revision 1.4 1999/04/10 18:36:23 ashvin +# Minor bug fix. +# +# Revision 1.3 1997/05/01 21:21:13 ashvin +# Changed names from notes to record. +# +# Revision 1.2 1997/01/23 00:02:35 ashvin +# The first release +# +# Revision 1.1 1996/12/24 23:56:07 asgoel +# Initial revision +# + +# the Emacs binary on your system +EMACS=@EMACS_BIN@ +# the perl binary on your system +PERL=@PERL@ + +# The installation prefix for architecture-independent files. +prefix=@prefix@ + +# The installation prefix for architecture-dependent files. +exec_prefix=@exec_prefix@ + +# The directory for installing read-only architecture-independent data. +datadir=@datadir@ + +# Currently not used. +# This will be wrong if you use private lispdir areas. +# In that case, set it explicitly during make. +lispdir=@lispdir@ + +# The directory for installing executables that users run. +bindir = @bindir@ + +# Where to put the Info file +infodir=@infodir@ + +# Installation command +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ + +# Various auxiliary programs +MAKEINFO=makeinfo +DVIPS=dvips +TEXI2DVI=texi2dvi +TEXI2HTML=texi2html +TAR=tar + +srcdir = @srcdir@ +VPATH = @srcdir@ + +SOURCES = records-vars.el records-dindex.el records-index.el records-util.el records.el +OBJECTS = records-vars.elc records-dindex.elc records-index.elc records-util.elc records.elc + +PRELOADS = -l records-load.el -l records-vars.el -l records.el +EFLAGS =-batch -q -no-site-file $(PRELOADS) + +DISTFILES = $(SOURCES) FAQ INSTALL Makefile.in \ + README configure configure.in install-sh \ + mkinstalldirs recordsadmin.in records-load.el \ + records.info records.ps \ + mailcrypt/mailcrypt.el mailcrypt/mc-pgp.el \ + mailcrypt/mc-remail.el mailcrypt/mc-toplev.el + +# records.info records.dvi records.texi + +.SUFFIXES: +.SUFFIXES: .elc .el + +.el.elc: + $(EMACS) $(EFLAGS) -f batch-byte-compile $< + +all: $(OBJECTS) + ./recordsadmin -i + +# install: all installdirs $(infodir)/records +install: all + $(INSTALL) recordsadmin $(bindir) + +# for f in $(SOURCES); do \ +# $(INSTALL_DATA) $(srcdir)/$$f $(lispdir); \ +# done; +# for f in $(OBJECTS); do \ +# $(INSTALL_DATA) $(srcdir)/$$f $(lispdir); \ +# done; + +# Make sure all installation directories actually exist +# by making them if necessary. +installdirs: mkinstalldirs + $(srcdir)/mkinstalldirs $(infodir) + +# There may be a newer info file in . than in srcdir. +$(infodir)/records: records.info + -if test -f records.info; then d=.; \ + else d=$(srcdir); fi; \ + $(INSTALL_DATA) $$d/records.info $@; \ + if $(SHELL) -c 'install-info --version' >/dev/null 2>&1; then \ + install-info --infodir=$(infodir) $$d/records.info; \ + else true; fi + +info: records.info + +records.info: records.texi + $(MAKEINFO) $(srcdir)/records.texi + +dvi: records.dvi + +records.dvi: records.texi + $(TEXI2DVI) $(srcdir)/records.texi + +ps: records.ps + +records.ps: records.dvi + $(DVIPS) $(srcdir)/records.dvi + +TAGS: $(SOURCES) + cd $(srcdir) && etags $(SOURCES) + +clean: + rm -f $(OBJECTS) records.aux records.cp records.cps records.dvi + rm -f records.fn records.pg records.ky records.kys records.toc + rm -f records.tp records.vr records.vrs records.log + +distclean: clean + -rm -f *~ *.tar.gz + rm -f records.ps records.info + rm -f Makefile config.status config.cache config.log + +${srcdir}/configure: configure.in + cd ${srcdir} && autoconf + +Makefile: Makefile.in recordsadmin.in config.status + ./config.status + +config.status: ${srcdir}/configure + ./config.status --recheck + +dist: $(DISTFILES) + version=`perl -ne 'print $$1 if /defconst records-version \"(.*)\"/' \ + records.el`; \ + distname=records-$$version; \ + rm -rf $$distname; \ + mkdir $$distname; \ + mkdir $$distname/mailcrypt; \ + for file in $(DISTFILES); do \ + ln $$file $$distname/$$file; \ + done; \ + $(TAR) -chz -f $$distname.tar.gz $$distname; \ + rm -rf $$distname diff --git a/README b/README new file mode 100644 index 0000000..a21959c --- /dev/null +++ b/README @@ -0,0 +1,58 @@ +This is the first version of the records mode that I am releasing. There is +not much documentation provided. However, most of the functionality is +available from the RECORDS menu when you are visiting a records file. + +A brief documentation on records usage is described below. + +Steps: + +1. Since you are reading this file, you have untarred the gzip'ed file. The +elisp files in the current directory (in which you are right now) will not be +installed in any other directory, and installation will automatically add the +current directory to the load path in emacs. So if you wish to move the records +software to some other emacs-related directory, now is the time to do it. + +2. Read ./INSTALL and follow its instructions. The install procedure makes the +byte compiled elisp files, reads in the user settings for records (by running +recordsadmin), and installs these settings into the records initialization file +~/.emacs-records. This initialization file is read by emacs and the records +administration program (recordsadmin). Install also adds the loading of +.emacs-records into your .emacs. When you start emacs, all the records defaults +will be automatically loaded. + +GO AHEAD AND DO THE INSTALLATION. + +3. Type key "Ctrl-c n" (for records-goto-today). This will get you today's +records file. If you are starting fresh, it will be empty. + +4. To insert a records subject type "Ctrl-c Ctrl-i" or look for "Insert Record" +in the menu. Once you have added a subject, you can start typing ... +Regarding spaces in subject titles, look at the FAQ. + +5. Multiple subjects can be added in each day's records file. Infact the same +subject can be added multiple times in each day's records file. + +6. For all the other functionality, look at the records menu. The menu has been +divided so that most of the functionality at the top of the menu is related to +records traversal (up, down, prev, next, goto etc). The middle section adds, +deletes and renames records. The last section does administration tasks such as +encryption, concatanation of records by subject or days etc. Make sure you look +at the records TODO facility. + +7. Remember that if you just have one records file (today's) then records +traversal's are not very useful! + +8. The program recordsadmin has been provided in order to do meta-level things +- initialize (or reinitialize) your records software, change date format, +change directory structure and recreate records indexes if they are broken (for +example if your system crashes while emacs is updating your indexes on the +fly). You should not need to use it in normal records use. + +9. Tell me if you like the package, have improvements to suggest, or have +found bugs. + +Good Luck. + +Ashvin Goel (ashvin@cse.ogi.edu) + + diff --git a/configure b/configure new file mode 100644 index 0000000..9ca55c0 --- /dev/null +++ b/configure @@ -0,0 +1,956 @@ +#! /bin/sh + +# Guess values for system-dependent variables and create Makefiles. +# Generated automatically using autoconf version 2.13 +# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc. +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. + +# Defaults: +ac_help= +ac_default_prefix=/usr/local +# Any additions from configure.in: + +# Initialize some variables set by options. +# The variables have the same names as the options, with +# dashes changed to underlines. +build=NONE +cache_file=./config.cache +exec_prefix=NONE +host=NONE +no_create= +nonopt=NONE +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +target=NONE +verbose= +x_includes=NONE +x_libraries=NONE +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datadir='${prefix}/share' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +libdir='${exec_prefix}/lib' +includedir='${prefix}/include' +oldincludedir='/usr/include' +infodir='${prefix}/info' +mandir='${prefix}/man' + +# Initialize some other variables. +subdirs= +MFLAGS= MAKEFLAGS= +SHELL=${CONFIG_SHELL-/bin/sh} +# Maximum number of lines to put in a shell here document. +ac_max_here_lines=12 + +ac_prev= +for ac_option +do + + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval "$ac_prev=\$ac_option" + ac_prev= + continue + fi + + case "$ac_option" in + -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) ac_optarg= ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case "$ac_option" in + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir="$ac_optarg" ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build="$ac_optarg" ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file="$ac_optarg" ;; + + -datadir | --datadir | --datadi | --datad | --data | --dat | --da) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ + | --da=*) + datadir="$ac_optarg" ;; + + -disable-* | --disable-*) + ac_feature=`echo $ac_option|sed -e 's/-*disable-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + eval "enable_${ac_feature}=no" ;; + + -enable-* | --enable-*) + ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "enable_${ac_feature}='$ac_optarg'" ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix="$ac_optarg" ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he) + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat << EOF +Usage: configure [options] [host] +Options: [defaults in brackets after descriptions] +Configuration: + --cache-file=FILE cache test results in FILE + --help print this message + --no-create do not create output files + --quiet, --silent do not print \`checking...' messages + --version print the version of autoconf that created configure +Directory and file names: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [same as prefix] + --bindir=DIR user executables in DIR [EPREFIX/bin] + --sbindir=DIR system admin executables in DIR [EPREFIX/sbin] + --libexecdir=DIR program executables in DIR [EPREFIX/libexec] + --datadir=DIR read-only architecture-independent data in DIR + [PREFIX/share] + --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data in DIR + [PREFIX/com] + --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var] + --libdir=DIR object code libraries in DIR [EPREFIX/lib] + --includedir=DIR C header files in DIR [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include] + --infodir=DIR info documentation in DIR [PREFIX/info] + --mandir=DIR man documentation in DIR [PREFIX/man] + --srcdir=DIR find the sources in DIR [configure dir or ..] + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM + run sed PROGRAM on installed program names +EOF + cat << EOF +Host type: + --build=BUILD configure for building on BUILD [BUILD=HOST] + --host=HOST configure for HOST [guessed] + --target=TARGET configure for TARGET [TARGET=HOST] +Features and packages: + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --x-includes=DIR X include files are in DIR + --x-libraries=DIR X library files are in DIR +EOF + if test -n "$ac_help"; then + echo "--enable and --with options recognized:$ac_help" + fi + exit 0 ;; + + -host | --host | --hos | --ho) + ac_prev=host ;; + -host=* | --host=* | --hos=* | --ho=*) + host="$ac_optarg" ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir="$ac_optarg" ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir="$ac_optarg" ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir="$ac_optarg" ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir="$ac_optarg" ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst \ + | --locals | --local | --loca | --loc | --lo) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* \ + | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) + localstatedir="$ac_optarg" ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir="$ac_optarg" ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir="$ac_optarg" ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix="$ac_optarg" ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix="$ac_optarg" ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix="$ac_optarg" ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name="$ac_optarg" ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir="$ac_optarg" ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir="$ac_optarg" ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site="$ac_optarg" ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir="$ac_optarg" ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir="$ac_optarg" ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target="$ac_optarg" ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers) + echo "configure generated by autoconf version 2.13" + exit 0 ;; + + -with-* | --with-*) + ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "with_${ac_package}='$ac_optarg'" ;; + + -without-* | --without-*) + ac_package=`echo $ac_option|sed -e 's/-*without-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + eval "with_${ac_package}=no" ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes="$ac_optarg" ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries="$ac_optarg" ;; + + -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; } + ;; + + *) + if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then + echo "configure: warning: $ac_option: invalid host type" 1>&2 + fi + if test "x$nonopt" != xNONE; then + { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; } + fi + nonopt="$ac_option" + ;; + + esac +done + +if test -n "$ac_prev"; then + { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; } +fi + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +# File descriptor usage: +# 0 standard input +# 1 file creation +# 2 errors and warnings +# 3 some systems may open it to /dev/tty +# 4 used on the Kubota Titan +# 6 checking for... messages and results +# 5 compiler messages saved in config.log +if test "$silent" = yes; then + exec 6>/dev/null +else + exec 6>&1 +fi +exec 5>./config.log + +echo "\ +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. +" 1>&5 + +# Strip out --no-create and --no-recursion so they do not pile up. +# Also quote any args containing shell metacharacters. +ac_configure_args= +for ac_arg +do + case "$ac_arg" in + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) ;; + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;; + *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*) + ac_configure_args="$ac_configure_args '$ac_arg'" ;; + *) ac_configure_args="$ac_configure_args $ac_arg" ;; + esac +done + +# NLS nuisances. +# Only set these to C if already set. These must not be set unconditionally +# because not all systems understand e.g. LANG=C (notably SCO). +# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'! +# Non-C LC_CTYPE values break the ctype check. +if test "${LANG+set}" = set; then LANG=C; export LANG; fi +if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi +if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi +if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -rf conftest* confdefs.h +# AIX cpp loses on an empty file, so make sure it contains at least a newline. +echo > confdefs.h + +# A filename unique to this package, relative to the directory that +# configure is in, which we can look for to find out if srcdir is correct. +ac_unique_file=records.el + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then its parent. + ac_prog=$0 + ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'` + test "x$ac_confdir" = "x$ac_prog" && ac_confdir=. + srcdir=$ac_confdir + if test ! -r $srcdir/$ac_unique_file; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r $srcdir/$ac_unique_file; then + if test "$ac_srcdir_defaulted" = yes; then + { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; } + else + { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; } + fi +fi +srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'` + +# Prefer explicitly selected file to automatically selected ones. +if test -z "$CONFIG_SITE"; then + if test "x$prefix" != xNONE; then + CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site" + else + CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" + fi +fi +for ac_site_file in $CONFIG_SITE; do + if test -r "$ac_site_file"; then + echo "loading site script $ac_site_file" + . "$ac_site_file" + fi +done + +if test -r "$cache_file"; then + echo "loading cache $cache_file" + . $cache_file +else + echo "creating cache $cache_file" + > $cache_file +fi + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +ac_exeext= +ac_objext=o +if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then + # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu. + if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then + ac_n= ac_c=' +' ac_t=' ' + else + ac_n=-n ac_c= ac_t= + fi +else + ac_n= ac_c='\c' ac_t= +fi + + + +for ac_prog in emacs xemacs +do +# Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:531: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_EMACS_BIN'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$EMACS_BIN"; then + ac_cv_prog_EMACS_BIN="$EMACS_BIN" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_EMACS_BIN="$ac_prog" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +EMACS_BIN="$ac_cv_prog_EMACS_BIN" +if test -n "$EMACS_BIN"; then + echo "$ac_t""$EMACS_BIN" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +test -n "$EMACS_BIN" && break +done + + +for ac_prog in perl perl5 +do +# Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:566: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_PERL'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$PERL" in + /*) + ac_cv_path_PERL="$PERL" # Let the user override the test with a path. + ;; + ?:/*) + ac_cv_path_PERL="$PERL" # Let the user override the test with a dos path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_PERL="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + ;; +esac +fi +PERL="$ac_cv_path_PERL" +if test -n "$PERL"; then + echo "$ac_t""$PERL" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +test -n "$PERL" && break +done + + +ac_aux_dir= +for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do + if test -f $ac_dir/install-sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f $ac_dir/install.sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + fi +done +if test -z "$ac_aux_dir"; then + { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; } +fi +ac_config_guess=$ac_aux_dir/config.guess +ac_config_sub=$ac_aux_dir/config.sub +ac_configure=$ac_aux_dir/configure # This should be Cygnus configure. + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# ./install, which can be erroneously created by make from ./install.sh. +echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6 +echo "configure:633: checking for a BSD compatible install" >&5 +if test -z "$INSTALL"; then +if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS=":" + for ac_dir in $PATH; do + # Account for people who put trailing slashes in PATH elements. + case "$ac_dir/" in + /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + if test -f $ac_dir/$ac_prog; then + if test $ac_prog = install && + grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + else + ac_cv_path_install="$ac_dir/$ac_prog -c" + break 2 + fi + fi + done + ;; + esac + done + IFS="$ac_save_IFS" + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL="$ac_cv_path_install" + else + # As a last resort, use the slow shell script. We don't cache a + # path for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the path is relative. + INSTALL="$ac_install_sh" + fi +fi +echo "$ac_t""$INSTALL" 1>&6 + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL_PROGRAM}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + + +lispdir='${datadir}/emacs/site-lisp' + + + +srcdirpath=`pwd` + + + +trap '' 1 2 15 +cat > confcache <<\EOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs. It is not useful on other systems. +# If it contains results you don't want to keep, you may remove or edit it. +# +# By default, configure uses ./config.cache as the cache file, +# creating it if it does not exist already. You can give configure +# the --cache-file=FILE option to use a different cache file; that is +# what configure does when it calls configure scripts in +# subdirectories, so they share the cache. +# Giving --cache-file=/dev/null disables caching, for debugging configure. +# config.status only pays attention to the cache file if you give it the +# --recheck option to rerun configure. +# +EOF +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, don't put newlines in cache variables' values. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +(set) 2>&1 | + case `(ac_space=' '; set | grep ac_space) 2>&1` in + *ac_space=\ *) + # `set' does not quote correctly, so add quotes (double-quote substitution + # turns \\\\ into \\, and sed turns \\ into \). + sed -n \ + -e "s/'/'\\\\''/g" \ + -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p" + ;; + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p' + ;; + esac >> confcache +if cmp -s $cache_file confcache; then + : +else + if test -w $cache_file; then + echo "updating cache $cache_file" + cat confcache > $cache_file + else + echo "not updating unwritable cache $cache_file" + fi +fi +rm -f confcache + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Any assignment to VPATH causes Sun make to only execute +# the first set of double-colon rules, so remove it if not needed. +# If there is a colon in the path, we need to keep it. +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d' +fi + +trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +cat > conftest.defs <<\EOF +s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%-D\1=\2%g +s%[ `~#$^&*(){}\\|;'"<>?]%\\&%g +s%\[%\\&%g +s%\]%\\&%g +s%\$%$$%g +EOF +DEFS=`sed -f conftest.defs confdefs.h | tr '\012' ' '` +rm -f conftest.defs + + +# Without the "./", some shells look in PATH for config.status. +: ${CONFIG_STATUS=./config.status} + +echo creating $CONFIG_STATUS +rm -f $CONFIG_STATUS +cat > $CONFIG_STATUS </dev/null | sed 1q`: +# +# $0 $ac_configure_args +# +# Compiler output produced by configure, useful for debugging +# configure, is in ./config.log if it exists. + +ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]" +for ac_option +do + case "\$ac_option" in + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion" + exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;; + -version | --version | --versio | --versi | --vers | --ver | --ve | --v) + echo "$CONFIG_STATUS generated by autoconf version 2.13" + exit 0 ;; + -help | --help | --hel | --he | --h) + echo "\$ac_cs_usage"; exit 0 ;; + *) echo "\$ac_cs_usage"; exit 1 ;; + esac +done + +ac_given_srcdir=$srcdir +ac_given_INSTALL="$INSTALL" + +trap 'rm -fr `echo "Makefile recordsadmin" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15 +EOF +cat >> $CONFIG_STATUS < conftest.subs <<\\CEOF +$ac_vpsub +$extrasub +s%@SHELL@%$SHELL%g +s%@CFLAGS@%$CFLAGS%g +s%@CPPFLAGS@%$CPPFLAGS%g +s%@CXXFLAGS@%$CXXFLAGS%g +s%@FFLAGS@%$FFLAGS%g +s%@DEFS@%$DEFS%g +s%@LDFLAGS@%$LDFLAGS%g +s%@LIBS@%$LIBS%g +s%@exec_prefix@%$exec_prefix%g +s%@prefix@%$prefix%g +s%@program_transform_name@%$program_transform_name%g +s%@bindir@%$bindir%g +s%@sbindir@%$sbindir%g +s%@libexecdir@%$libexecdir%g +s%@datadir@%$datadir%g +s%@sysconfdir@%$sysconfdir%g +s%@sharedstatedir@%$sharedstatedir%g +s%@localstatedir@%$localstatedir%g +s%@libdir@%$libdir%g +s%@includedir@%$includedir%g +s%@oldincludedir@%$oldincludedir%g +s%@infodir@%$infodir%g +s%@mandir@%$mandir%g +s%@EMACS_BIN@%$EMACS_BIN%g +s%@PERL@%$PERL%g +s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g +s%@INSTALL_SCRIPT@%$INSTALL_SCRIPT%g +s%@INSTALL_DATA@%$INSTALL_DATA%g +s%@lispdir@%$lispdir%g +s%@srcdirpath@%$srcdirpath%g + +CEOF +EOF + +cat >> $CONFIG_STATUS <<\EOF + +# Split the substitutions into bite-sized pieces for seds with +# small command number limits, like on Digital OSF/1 and HP-UX. +ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script. +ac_file=1 # Number of current file. +ac_beg=1 # First line for current file. +ac_end=$ac_max_sed_cmds # Line after last line for current file. +ac_more_lines=: +ac_sed_cmds="" +while $ac_more_lines; do + if test $ac_beg -gt 1; then + sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file + else + sed "${ac_end}q" conftest.subs > conftest.s$ac_file + fi + if test ! -s conftest.s$ac_file; then + ac_more_lines=false + rm -f conftest.s$ac_file + else + if test -z "$ac_sed_cmds"; then + ac_sed_cmds="sed -f conftest.s$ac_file" + else + ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file" + fi + ac_file=`expr $ac_file + 1` + ac_beg=$ac_end + ac_end=`expr $ac_end + $ac_max_sed_cmds` + fi +done +if test -z "$ac_sed_cmds"; then + ac_sed_cmds=cat +fi +EOF + +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories. + + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`" + # A "../" for each directory in $ac_dir_suffix. + ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'` + else + ac_dir_suffix= ac_dots= + fi + + case "$ac_given_srcdir" in + .) srcdir=. + if test -z "$ac_dots"; then top_srcdir=. + else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;; + /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;; + *) # Relative path. + srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix" + top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + + case "$ac_given_INSTALL" in + [/$]*) INSTALL="$ac_given_INSTALL" ;; + *) INSTALL="$ac_dots$ac_given_INSTALL" ;; + esac + + echo creating "$ac_file" + rm -f "$ac_file" + configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure." + case "$ac_file" in + *Makefile*) ac_comsub="1i\\ +# $configure_input" ;; + *) ac_comsub= ;; + esac + + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + sed -e "$ac_comsub +s%@configure_input@%$configure_input%g +s%@srcdir@%$srcdir%g +s%@top_srcdir@%$top_srcdir%g +s%@INSTALL@%$INSTALL%g +" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file +fi; done +rm -f conftest.s* + +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +chmod +x recordsadmin +exit 0 +EOF +chmod +x $CONFIG_STATUS +rm -fr confdefs* $ac_clean_files +test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1 + diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..cfad08d --- /dev/null +++ b/configure.in @@ -0,0 +1,18 @@ +dnl Process this file with autoconf to produce a configure script. +AC_INIT(records.el) + +AC_CHECK_PROGS(EMACS_BIN, emacs xemacs) + +AC_PATH_PROGS(PERL, perl perl5) + +AC_PROG_INSTALL() + +lispdir='${datadir}/emacs/site-lisp' + +AC_SUBST(lispdir) + +srcdirpath=`pwd` + +AC_SUBST(srcdirpath) + +AC_OUTPUT(Makefile recordsadmin, chmod +x recordsadmin) diff --git a/install-sh b/install-sh new file mode 100644 index 0000000..5871924 --- /dev/null +++ b/install-sh @@ -0,0 +1,238 @@ +#! /bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. +# + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/mailcrypt/mailcrypt.el b/mailcrypt/mailcrypt.el new file mode 100644 index 0000000..47ceaee --- /dev/null +++ b/mailcrypt/mailcrypt.el @@ -0,0 +1,463 @@ +;; mailcrypt.el v3.3, mail encryption with PGP +;; Copyright (C) 1995 Jin Choi +;; Patrick LoPresti +;; Any comments or suggestions welcome. +;; Inspired by pgp.el, by Gray Watson . + +;;{{{ Licensing +;; This file is intended to be used with GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +;;}}} + +;;{{{ Load some required packages + +(eval-when-compile + ;; Quiet warnings + (autoload 'start-itimer "itimer") + (autoload 'cancel-itimer "itimer") + (autoload 'delete-itimer "itimer")) + +(require 'easymenu) +(require 'comint) + +(eval-and-compile + (condition-case nil (require 'itimer) (error nil)) + (if (not (featurep 'itimer)) + (condition-case nil (require 'timer) (error nil))) + + (if (not (fboundp 'buffer-substring-no-properties)) + (fset 'buffer-substring-no-properties 'buffer-substring))) + +(defconst mc-xemacs-p (string-match "Xemacs" emacs-version)) + +(autoload 'mc-decrypt "mc-toplev" nil t) +(autoload 'mc-verify "mc-toplev" nil t) +(autoload 'mc-snarf "mc-toplev" nil t) +(autoload 'mc-pgp-fetch-key "mc-pgp" nil t) +(autoload 'mc-encrypt "mc-toplev" nil t) +(autoload 'mc-sign "mc-toplev" nil t) +(autoload 'mc-insert-public-key "mc-toplev" nil t) +(autoload 'mc-remailer-encrypt-for-chain "mc-remail" nil t) +(autoload 'mc-remailer-insert-response-block "mc-remail" nil t) +(autoload 'mc-remailer-insert-pseudonym "mc-remail" nil t) + +;;}}} + +;;{{{ Minor mode variables and functions + +(defvar mc-read-mode nil + "Non-nil means Mailcrypt read mode key bindings are available.") + +(defvar mc-write-mode nil + "Non-nil means Mailcrypt write mode key bindings are available.") + +(make-variable-buffer-local 'mc-read-mode) +(make-variable-buffer-local 'mc-write-mode) + +(defvar mc-read-mode-string " MC-r" + "*String to put in mode line when Mailcrypt read mode is active.") + +(defvar mc-write-mode-string " MC-w" + "*String to put in mode line when Mailcrypt write mode is active.") + +(defvar mc-read-mode-map nil + "Keymap for Mailcrypt read mode bindings.") + +(defvar mc-write-mode-map nil + "Keymap for Mailcrypt write mode bindings.") + +(or mc-read-mode-map + (progn + (setq mc-read-mode-map (make-sparse-keymap)) + (define-key mc-read-mode-map "\C-c/f" 'mc-deactivate-passwd) + (define-key mc-read-mode-map "\C-c/d" 'mc-decrypt) + (define-key mc-read-mode-map "\C-c/v" 'mc-verify) + (define-key mc-read-mode-map "\C-c/a" 'mc-snarf) + (define-key mc-read-mode-map "\C-c/k" 'mc-pgp-fetch-key))) + +(or mc-write-mode-map + (progn + (setq mc-write-mode-map (make-sparse-keymap)) + (define-key mc-write-mode-map "\C-c/f" 'mc-deactivate-passwd) + (define-key mc-write-mode-map "\C-c/e" 'mc-encrypt) + (define-key mc-write-mode-map "\C-c/s" 'mc-sign) + (define-key mc-write-mode-map "\C-c/x" 'mc-insert-public-key) + (define-key mc-write-mode-map "\C-c/k" 'mc-pgp-fetch-key) + (define-key mc-write-mode-map "\C-c/r" + 'mc-remailer-encrypt-for-chain) + (define-key mc-write-mode-map "\C-c/b" + 'mc-remailer-insert-response-block) + (define-key mc-write-mode-map "\C-c/p" + 'mc-remailer-insert-pseudonym))) + +(easy-menu-define + mc-read-mode-menu (if mc-xemacs-p nil (list mc-read-mode-map)) + "Mailcrypt read mode menu." + '("Mailcrypt" + ["Decrypt Message" mc-decrypt t] + ["Verify Signature" mc-verify t] + ["Snarf Keys" mc-snarf t] + ["Fetch Key" mc-pgp-fetch-key t] + ["Forget Passphrase(s)" mc-deactivate-passwd t])) + +(easy-menu-define + mc-write-mode-menu (if mc-xemacs-p nil (list mc-write-mode-map)) + "Mailcrypt write mode menu." + '("Mailcrypt" + ["Encrypt Message" mc-encrypt t] + ["Sign Message" mc-sign t] + ["Insert Public Key" mc-insert-public-key t] + ["Fetch Key" mc-pgp-fetch-key t] + ["Encrypt for Remailer(s)" mc-remailer-encrypt-for-chain t] + ["Insert Pseudonym" mc-remailer-insert-pseudonym t] + ["Insert Response Block" mc-remailer-insert-response-block t] + ["Forget Passphrase(s)" mc-deactivate-passwd t])) + +(or (assq 'mc-read-mode minor-mode-map-alist) + (setq minor-mode-map-alist + (cons (cons 'mc-read-mode mc-read-mode-map) + minor-mode-map-alist))) + +(or (assq 'mc-write-mode minor-mode-map-alist) + (setq minor-mode-map-alist + (cons (cons 'mc-write-mode mc-write-mode-map) + minor-mode-map-alist))) + +(or (assq 'mc-read-mode minor-mode-alist) + (setq minor-mode-alist + (cons '(mc-read-mode mc-read-mode-string) minor-mode-alist))) + +(or (assq 'mc-write-mode minor-mode-alist) + (setq minor-mode-alist + (cons '(mc-write-mode mc-write-mode-string) minor-mode-alist))) + +(defun mc-read-mode (&optional arg) + "\nMinor mode for interfacing with cryptographic functions. + +\\[mc-decrypt]\t\tDecrypt an encrypted message +\\[mc-verify]\t\tVerify signature on a clearsigned message +\\[mc-snarf]\t\tAdd public key(s) to keyring +\\[mc-pgp-fetch-key]\t\tFetch a PGP key via finger or HTTP +\\[mc-deactivate-passwd]\t\tForget passphrase(s)\n" + (interactive) + (setq mc-read-mode + (if (null arg) (not mc-read-mode) + (> (prefix-numeric-value arg) 0))) + (and mc-read-mode mc-write-mode (mc-write-mode nil)) + (if mc-read-mode + (easy-menu-add mc-read-mode-menu) + (easy-menu-remove mc-read-mode-menu))) + +(defun mc-write-mode (&optional arg) + "\nMinor mode for interfacing with cryptographic functions. + +\\[mc-encrypt]\t\tEncrypt (and optionally sign) message +\\[mc-sign]\t\tClearsign message +\\[mc-insert-public-key]\t\tExtract public key from keyring and insert into message +\\[mc-pgp-fetch-key]\t\tFetch a PGP key via finger or HTTP +\\[mc-remailer-encrypt-for-chain]\t\tEncrypt message for remailing +\\[mc-remailer-insert-pseudonym]\t\tInsert a pseudonym (for remailing) +\\[mc-remailer-insert-response-block]\t\tInsert a response block (for remailing) +\\[mc-deactivate-passwd]\t\tForget passphrase(s)\n" + (interactive) + (setq mc-write-mode + (if (null arg) (not mc-write-mode) + (> (prefix-numeric-value arg) 0))) + (and mc-write-mode mc-read-mode (mc-read-mode nil)) + (if mc-write-mode + (easy-menu-add mc-write-mode-menu) + (easy-menu-remove mc-write-mode-menu))) + +(defun mc-install-read-mode () + (interactive) + (mc-read-mode 1)) + +(defun mc-install-write-mode () + (interactive) + (mc-write-mode 1)) + +;;}}} + +;;{{{ Note: +;; The funny triple braces you see are used by `folding-mode', a minor +;; mode by Jamie Lokier, available from the elisp archive. +;;}}} + +;;{{{ User variables. +(defconst mc-version "3.3") +(defvar mc-default-scheme 'mc-scheme-pgp "*Default encryption scheme to use.") +(defvar mc-passwd-timeout 60 + "*Time to deactivate password in seconds after a use. +nil or 0 means deactivate immediately. If the only timer package available +is the 'timer' package, then this can be a string in timer format.") + +(defvar mc-ripem-user-id (or (getenv "RIPEM_USER_NAME") + (user-full-name) "*Your RIPEM user ID.")) + +(defvar mc-always-replace nil + "*If t, decrypt mail messages in place without prompting. + +If 'never, always use a viewer instead of replacing.") + +(defvar mc-use-default-recipients nil "*Assume that the message should + be encoded for everyone listed in the To, Cc, and Bcc fields.") + +(defvar mc-encrypt-for-me nil "*Encrypt all outgoing messages with + user's public key.") + +(defvar mc-pre-signature-hook nil + "*List of hook functions to run immediately before signing.") +(defvar mc-post-signature-hook nil + "*List of hook functions to run immediately after signing.") +(defvar mc-pre-encryption-hook nil + "*List of hook functions to run immediately before encrypting.") +(defvar mc-post-encryption-hook nil + "*List of hook functions to run after encrypting.") +(defvar mc-pre-decryption-hook nil + "*List of hook functions to run immediately before decrypting.") +(defvar mc-post-decryption-hook nil + "*List of hook functions to run after decrypting.") + +(defconst mc-buffer-name "*MailCrypt*" + "Name of temporary buffer for mailcrypt") + +(defvar mc-modes-alist + '((rmail-mode (decrypt . mc-rmail-decrypt-message) + (verify . mc-rmail-verify-signature)) + (rmail-summary-mode (decrypt . mc-rmail-summary-decrypt-message) + (verify . mc-rmail-summary-verify-signature) + (snarf . mc-rmail-summary-snarf-keys)) + (vm-mode (decrypt . mc-vm-decrypt-message) + (verify . mc-vm-verify-signature) + (snarf . mc-vm-snarf-keys)) + (vm-summary-mode (decrypt . mc-vm-decrypt-message) + (verify . mc-vm-verify-signature) + (snarf . mc-vm-snarf-keys)) + (mh-folder-mode (decrypt . mc-mh-decrypt-message) + (verify . mc-mh-verify-signature) + (snarf . mc-mh-snarf-keys)) + (gnus-summary-mode (decrypt . mc-gnus-summary-decrypt-message) + (verify . mc-gnus-summary-verify-signature) + (snarf . mc-gnus-summary-snarf-keys)) + (mail-mode (encrypt . mc-encrypt-message) + (sign . mc-sign-message)) + (vm-mail-mode (encrypt . mc-encrypt-message) + (sign . mc-sign-message)) + (mh-letter-mode (encrypt . mc-encrypt-message) + (sign . mc-sign-message)) + (news-reply-mode (encrypt . mc-encrypt-message) + (sign . mc-sign-message))) + + "Association list (indexed by major mode) of association lists +(indexed by operation) of functions to call for each major mode.") + +;;}}} +;;{{{ Program variables and constants. + +(defvar mc-timer nil "Timer object for password deactivation.") + +(defvar mc-passwd-cache nil "Cache for passphrases.") + +(defvar mc-schemes '(("pgp" . mc-scheme-pgp))) + +;;}}} + +;;{{{ Utility functions. + +(defun mc-message-delimiter-positions (start-re end-re &optional begin) + ;; Returns pair of integers (START . END) that delimit message marked off + ;; by the regular expressions start-re and end-re. Optional argument BEGIN + ;; determines where we should start looking from. + (setq begin (or begin (point-min))) + (let (start) + (save-excursion + (goto-char begin) + (and (re-search-forward start-re nil t) + (setq start (match-beginning 0)) + (re-search-forward end-re nil t) + (cons start (point)))))) + + +(defun mc-split (regexp str) + "Splits STR into a list of elements which were separated by REGEXP, +stripping initial and trailing whitespace." + (let ((data (match-data)) + (retval '()) + beg end) + (unwind-protect + (progn + (string-match "[ \t\n]*" str) ; Will always match at 0 + (setq beg (match-end 0)) + (setq end (string-match "[ \t\n]*\\'" str)) + (while (string-match regexp str beg) + (setq retval + (cons (substring str beg (match-beginning 0)) + retval)) + (setq beg (match-end 0))) + (if (not (= (length str) beg)) ; Not end + (setq retval (cons (substring str beg end) retval))) + (nreverse retval)) + (store-match-data data)))) + +;;; FIXME - Function never called? +(defun mc-temp-display (beg end &optional name) + (let (tmp) + (if (not name) + (setq name mc-buffer-name)) + (if (string-match name "*ERROR*") + (progn + (message "mailcrypt: An error occured! See *ERROR* buffer.") + (beep))) + (setq tmp (buffer-substring beg end)) + (delete-region beg end) + (save-excursion + (save-window-excursion + (with-output-to-temp-buffer name + (princ tmp)))))) + +(defun mc-display-buffer (buffer) + "Like display-buffer, but always display top of the buffer." + (save-excursion + (set-buffer buffer) + (goto-char (point-min)) + (display-buffer buffer))) + +(defun mc-message (msg &optional buffer default) + ;; returns t if we used msg, nil if we used default + (let ((retval t)) + (if buffer + (setq msg + (save-excursion + (set-buffer buffer) + (goto-char (point-min)) + (if (re-search-forward msg nil t) + (buffer-substring (match-beginning 0) (match-end 0)) + (setq retval nil) + default)))) + (if msg (message "%s" msg)) + retval)) + +(defun mc-process-region (beg end passwd program args parser &optional buffer) + (let ((obuf (current-buffer)) + (process-connection-type nil) + mybuf result rgn proc) + (unwind-protect + (progn + (setq mybuf (or buffer (generate-new-buffer " *mailcrypt temp"))) + (set-buffer mybuf) + (erase-buffer) + (buffer-disable-undo mybuf) + (setq proc + (apply 'start-process "*PGP*" mybuf program args)) + (if passwd + (progn + (process-send-string proc (concat passwd "\n")) + (or mc-passwd-timeout (mc-deactivate-passwd)))) + (set-buffer obuf) + (process-send-region proc beg end) + (process-send-eof proc) + (while (eq 'run (process-status proc)) + (accept-process-output proc 5)) + (setq result (process-exit-status proc)) + (set-buffer mybuf) + (goto-char (point-max)) + (if (re-search-backward "\nProcess .* finished\n\\'" nil t) + (delete-region (match-beginning 0) (match-end 0))) + (goto-char (point-min)) + ;; CRNL -> NL + (while (search-forward "\r\n" nil t) + (replace-match "\n")) + ;; Hurm. FIXME; must get better result codes. + (if (stringp result) + (error "%s exited abnormally: '%s'" program result) + (setq rgn (funcall parser result)) + ;; If the parser found something, migrate it + (if (consp rgn) + (progn + (set-buffer obuf) + (delete-region beg end) + (goto-char beg) + (insert-buffer-substring mybuf (car rgn) (cdr rgn)) + (set-buffer mybuf) + (delete-region (car rgn) (cdr rgn))))) + ;; Return nil on failure and exit code on success + (if rgn result)) + ;; Cleanup even on nonlocal exit + (if (and proc (eq 'run (process-status proc))) + (interrupt-process proc)) + (set-buffer obuf) + (or buffer (null mybuf) (kill-buffer mybuf))))) + +;;}}} + +;;{{{ Passphrase management +(defun mc-activate-passwd (id &optional prompt) + "Activate the passphrase matching ID, using PROMPT for a prompt. +Return the passphrase. If PROMPT is nil, only return value if cached." + (cond ((featurep 'itimer) + (if mc-timer (delete-itimer mc-timer)) + (setq mc-timer (if mc-passwd-timeout + (start-itimer "mc-itimer" + 'mc-deactivate-passwd + mc-passwd-timeout) + nil))) + ((featurep 'timer) + (let ((string-time (if (integerp mc-passwd-timeout) + (format "%d sec" mc-passwd-timeout) + mc-passwd-timeout))) + (if mc-timer (cancel-timer mc-timer)) + (setq mc-timer (if string-time + (run-at-time string-time + nil 'mc-deactivate-passwd) + nil))))) + (let ((cell (assoc id mc-passwd-cache)) + passwd) + (setq passwd (cdr-safe cell)) + (if (and (not passwd) prompt) + (setq passwd (comint-read-noecho prompt))) + (if cell + (setcdr cell passwd) + (setq mc-passwd-cache (cons (cons id passwd) mc-passwd-cache))) + passwd)) + +(defun mc-deactivate-passwd (&optional inhibit-message) + "*Deactivate the passphrase cache." + (interactive) + (if mc-timer + (cond ((featurep 'itimer) (delete-itimer mc-timer)) + ((featurep 'timer) (cancel-timer mc-timer)))) + (mapcar + (function + (lambda (cell) + (if (stringp (cdr-safe cell)) (fillarray (cdr cell) 0)) + (setcdr cell nil))) + mc-passwd-cache) + (or inhibit-message + (message "Passphrase%s deactivated" + (if (> (length mc-passwd-cache) 1) "s" "")))) + +;;}}} + +;;{{{ Define several aliases so that an apropos on `mailcrypt' will +;; return something. +(defalias 'mailcrypt-encrypt 'mc-encrypt) +(defalias 'mailcrypt-decrypt 'mc-decrypt) +(defalias 'mailcrypt-sign 'mc-sign) +(defalias 'mailcrypt-verify 'mc-verify) +(defalias 'mailcrypt-insert-public-key 'mc-insert-public-key) +(defalias 'mailcrypt-snarf 'mc-snarf) +;;}}} +(provide 'mailcrypt) diff --git a/mailcrypt/mc-pgp.el b/mailcrypt/mc-pgp.el new file mode 100644 index 0000000..59ba147 --- /dev/null +++ b/mailcrypt/mc-pgp.el @@ -0,0 +1,603 @@ +;; mc-pgp.el, PGP support for Mailcrypt +;; Copyright (C) 1995 Jin Choi +;; Patrick LoPresti + +;;{{{ Licensing +;; This file is intended to be used with GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +;;}}} +(require 'mailcrypt) + +(defvar mc-pgp-user-id (user-login-name) + "*PGP ID of your default identity.") +(defvar mc-pgp-always-sign nil + "*If t, always sign encrypted PGP messages, or never sign if 'never.") +(defvar mc-pgp-path "pgp" "*The PGP executable.") +(defvar mc-pgp-display-snarf-output nil + "*If t, pop up the PGP output window when snarfing keys.") +(defvar mc-pgp-alternate-keyring nil + "*Public keyring to use instead of default.") +(defvar mc-pgp-comment + (format "Processed by Mailcrypt %s, an Emacs/PGP interface" mc-version) + "*Comment field to appear in ASCII armor output. If nil, let PGP +use its default.") + +(defconst mc-pgp-msg-begin-line "-----BEGIN PGP MESSAGE-----" + "Text for start of PGP message delimiter.") +(defconst mc-pgp-msg-end-line "-----END PGP MESSAGE-----" + "Text for end of PGP message delimiter.") +(defconst mc-pgp-signed-begin-line "-----BEGIN PGP SIGNED MESSAGE-----" + "Text for start of PGP signed messages.") +(defconst mc-pgp-signed-end-line "-----END PGP SIGNATURE-----" + "Text for end of PGP signed messages.") +(defconst mc-pgp-key-begin-line "^-----BEGIN PGP PUBLIC KEY BLOCK-----\r?$" + "Text for start of PGP public key.") +(defconst mc-pgp-key-end-line "^-----END PGP PUBLIC KEY BLOCK-----\r?$" + "Text for end of PGP public key.") +(defconst mc-pgp-error-re "^\\(ERROR:\\|WARNING:\\).*" + "Regular expression matching an error from PGP") +(defconst mc-pgp-sigok-re "^.*Good signature.*" + "Regular expression matching a PGP signature validation message") +(defconst mc-pgp-newkey-re "^[ \t]*\\(No\\|[0-9]+\\) +new [ku].*" + "Regular expression matching a PGP key snarf message") +(defconst mc-pgp-nokey-re + "Cannot find the public key matching userid '\\(.+\\)'$" + "Regular expression matching a PGP missing-key messsage") +(defconst mc-pgp-key-expected-re + "Key matching expected Key ID \\(\\S +\\) not found") + +(defvar mc-pgp-keydir nil + "Directory in which keyrings are stored.") + +(defun mc-get-pgp-keydir () + (if (null mc-pgp-keydir) + (let ((buffer (generate-new-buffer " *mailcrypt temp*")) + (obuf (current-buffer))) + (unwind-protect + (progn + (call-process mc-pgp-path nil buffer nil "+verbose=1" + "+language=en" "-kv" "XXXXXXXXXX") + (set-buffer buffer) + (goto-char (point-min)) + (re-search-forward "^Key ring:\\s *'\\(.*\\)'") + (setq mc-pgp-keydir + (file-name-directory + (buffer-substring-no-properties + (match-beginning 1) (match-end 1))))) + (set-buffer obuf) + (kill-buffer buffer)))) + mc-pgp-keydir) + +(defvar mc-pgp-key-cache nil + "Association list mapping PGP IDs to canonical \"keys\". A \"key\" +is a pair (USER-ID . KEY-ID) which identifies the canonical IDs of the +PGP ID.") + +(defun mc-pgp-lookup-key (str) + ;; Look up the string STR in the user's secret key ring. Return a + ;; pair of strings (USER-ID . KEY-ID) which uniquely identifies the + ;; matching key, or nil if no key matches. + (if (equal str "***** CONVENTIONAL *****") nil + (let ((keyring (concat (mc-get-pgp-keydir) "secring")) + (result (cdr-safe (assoc str mc-pgp-key-cache))) + (key-regexp + "^\\(pub\\|sec\\)\\s +[^/]+/\\(\\S *\\)\\s +\\S +\\s +\\(.*\\)$") + (obuf (current-buffer)) + buffer) + (if (null result) + (unwind-protect + (progn + (setq buffer (generate-new-buffer " *mailcrypt temp")) + (call-process mc-pgp-path nil buffer nil + "+language=en" "-kv" str keyring) + (set-buffer buffer) + (goto-char (point-min)) + (if (re-search-forward key-regexp nil t) + (progn + (setq result + (cons (buffer-substring-no-properties + (match-beginning 3) (match-end 3)) + (concat + "0x" + (buffer-substring-no-properties + (match-beginning 2) (match-end 2))))) + (setq mc-pgp-key-cache (cons (cons str result) + mc-pgp-key-cache))))) + (if buffer (kill-buffer buffer)) + (set-buffer obuf))) + (if (null result) + (error "No PGP secret key for %s" str)) + result))) + +(defun mc-pgp-generic-parser (result) + (let (start) + (goto-char (point-min)) + (cond ((not (eq result 0)) + (prog1 + nil + (if (mc-message "^\aError: +Bad pass phrase\\.$" (current-buffer)) + (mc-deactivate-passwd t) + (mc-message mc-pgp-error-re (current-buffer) + (format "PGP exited with status %d" result))))) + ((re-search-forward mc-pgp-nokey-re nil t) + nil) + (t + (and + (goto-char (point-min)) + (re-search-forward "-----BEGIN PGP.*-----$" nil t) + (setq start (match-beginning 0)) + (goto-char (point-max)) + (re-search-backward "^-----END PGP.*-----\n" nil t) + (cons start (match-end 0))))))) + +(defun mc-pgp-encrypt-region (recipients start end &optional id sign) + (let ((process-environment process-environment) + (buffer (get-buffer-create mc-buffer-name)) + (msg "Encrypting...") + ;; Crock. Rewrite someday. + (mc-pgp-always-sign mc-pgp-always-sign) + (obuf (current-buffer)) + args key passwd result pgp-id) + (setq args (list "+encrypttoself=off +verbose=1" "+batchmode" + "+language=en" "-feat")) + (if mc-pgp-comment + (setq args (cons (format "+comment=%s" mc-pgp-comment) args))) + (if mc-pgp-alternate-keyring + (setq args (append args (list (format "+pubring=%s" + mc-pgp-alternate-keyring))))) + (if (and (not (eq mc-pgp-always-sign 'never)) + (or mc-pgp-always-sign sign (y-or-n-p "Sign the message? "))) + (progn + (setq mc-pgp-always-sign t) + (setq key (mc-pgp-lookup-key (or id mc-pgp-user-id))) + (setq passwd + (mc-activate-passwd + (cdr key) + (format "PGP passphrase for %s (%s): " (car key) (cdr key)))) + (setq args + (nconc args (list "-s" "-u" (cdr key)))) + (setenv "PGPPASSFD" "0") + (setq msg (format "Encrypting+signing as %s ..." (car key)))) + (setq mc-pgp-always-sign 'never)) + + (or key + (setq key (mc-pgp-lookup-key mc-pgp-user-id))) + + (if mc-encrypt-for-me + (setq recipients (cons (cdr key) recipients))) + + (setq args (append args recipients)) + + (message "%s" msg) + (setq result (mc-process-region start end passwd mc-pgp-path args + 'mc-pgp-generic-parser buffer)) + (save-excursion + (set-buffer buffer) + (goto-char (point-min)) + (if (re-search-forward mc-pgp-nokey-re nil t) + (progn + (if result (error "This should never happen.")) + (setq pgp-id (buffer-substring-no-properties + (match-beginning 1) (match-end 1))) + (and + (y-or-n-p + (format "Key for '%s' not found; try to fetch? " pgp-id)) + (mc-pgp-fetch-key (cons pgp-id nil)) + (set-buffer obuf) + (mc-pgp-encrypt-region recipients start end id))) + (if (not result) + nil + (message "%s Done." msg) + t))))) + +(defun mc-pgp-decrypt-parser (result) + (goto-char (point-min)) + (cond ((eq result 0) + ;; Valid signature + (re-search-forward "^Signature made.*\n") + (if (looking-at + "\a\nWARNING: Because this public key.*\n.*\n.*\n") + (goto-char (match-end 0))) + (cons (point) (point-max))) + ((eq result 1) + (re-search-forward + "\\(File is conventionally encrypted\\. *\\)?Just a moment\\.+") + (if (eq (match-beginning 1) (match-end 1)) + (if (looking-at + "\nFile has signature.*\\(\n\a.*\n\\)*\nWARNING:.*\n") + (goto-char (match-end 0))) + (if (looking-at "Pass phrase appears good\\. \\.") + (goto-char (match-end 0)))) + (cons (point) (point-max))) + (t nil))) + +(defun mc-pgp-decrypt-region (start end &optional id) + ;; returns a pair (SUCCEEDED . VERIFIED) where SUCCEEDED is t if + ;; the decryption succeeded and verified is t if there was a valid signature + (let ((process-environment process-environment) + (buffer (get-buffer-create mc-buffer-name)) + (obuf (current-buffer)) + args key new-key passwd result pgp-id) + (undo-boundary) + (setq key (mc-pgp-lookup-key (or id mc-pgp-user-id))) + (setq + passwd + (if key + (mc-activate-passwd (cdr key) + (and id + (format "PGP passphrase for %s (%s): " + (car key) (cdr key)))) + (mc-activate-passwd id "PGP passphrase for conventional decryption: "))) + (if passwd + (setenv "PGPPASSFD" "0")) + (setq args '("+verbose=1" "+batchmode" "+language=en" "-f")) + (if mc-pgp-alternate-keyring + (setq args (append args (list (format "+pubring=%s" + mc-pgp-alternate-keyring))))) + (message "Decrypting...") + (setq result + (mc-process-region + start end passwd mc-pgp-path args 'mc-pgp-decrypt-parser buffer)) + (cond + (result + (message "Decrypting... Done.") + ;; If verification failed due to missing key, offer to fetch it. + (save-excursion + (set-buffer buffer) + (goto-char (point-min)) + (if (re-search-forward mc-pgp-key-expected-re nil t) + (setq pgp-id (concat "0x" (buffer-substring-no-properties + (match-beginning 1) + (match-end 1)))))) + (if (and pgp-id + (y-or-n-p + (format "Key %s not found; attempt to fetch? " pgp-id)) + (mc-pgp-fetch-key (cons nil pgp-id))) + (progn + (undo-start) + (undo-more 1) + (mc-pgp-decrypt-region start end id)) + (mc-message mc-pgp-key-expected-re buffer) + (cons t (eq result 0)))) + ;; Decryption failed; maybe we need to use a different user-id + ((and + (set-buffer buffer) + (goto-char (point-min)) + (re-search-forward + "^Key for user ID:.*\n.*Key ID \\([0-9A-F]+\\)" nil t) + (setq new-key + (mc-pgp-lookup-key + (concat "0x" (buffer-substring-no-properties (match-beginning 1) + (match-end 1))))) + (not (and id (equal key new-key)))) + (set-buffer obuf) + (mc-pgp-decrypt-region start end (cdr new-key))) + ;; Or maybe it is conventionally encrypted + ((and + (set-buffer buffer) + (goto-char (point-min)) + (re-search-forward "^File is conventionally encrypted" nil t)) + (set-buffer obuf) + (if (null key) (mc-deactivate-passwd t)) + (mc-pgp-decrypt-region start end "***** CONVENTIONAL *****")) + (t + (mc-display-buffer buffer) + (if (mc-message "^\aError: +Bad pass phrase\\.$" buffer) + (mc-deactivate-passwd t) + (mc-message mc-pgp-error-re buffer "Error decrypting buffer")) + (cons nil nil))))) + +(defun mc-pgp-sign-region (start end &optional id unclear) + (let ((process-environment process-environment) + (buffer (get-buffer-create mc-buffer-name)) + passwd args key) + (setq key (mc-pgp-lookup-key (or id mc-pgp-user-id))) + (setq passwd + (mc-activate-passwd + (cdr key) + (format "PGP passphrase for %s (%s): " (car key) (cdr key)))) + (setenv "PGPPASSFD" "0") + (setq args + (list + "-fast" "+verbose=1" "+language=en" + (format "+clearsig=%s" (if unclear "off" "on")) + "+batchmode" "-u" (cdr key))) + (if mc-pgp-comment + (setq args (cons (format "+comment=%s" mc-pgp-comment) args))) + (message "Signing as %s ..." (car key)) + (if (mc-process-region start end passwd mc-pgp-path args + 'mc-pgp-generic-parser buffer) + (progn + (message "Signing as %s ... Done." (car key)) + t) + nil))) + +(defun mc-pgp-verify-parser (result) + (cond ((eq result 0) + (mc-message mc-pgp-sigok-re (current-buffer) "Good signature") + t) + ((eq result 1) + (mc-message mc-pgp-error-re (current-buffer) "Bad signature") + nil) + (t + (mc-message mc-pgp-error-re (current-buffer) + (format "PGP exited with status %d" result)) + nil))) + +(defun mc-pgp-verify-region (start end &optional no-fetch) + (let ((buffer (get-buffer-create mc-buffer-name)) + (obuf (current-buffer)) + args pgp-id) + (setq args '("+verbose=1" "+batchmode" "+language=en" "-f")) + (if mc-pgp-alternate-keyring + (setq args (append args (list (format "+pubring=%s" + mc-pgp-alternate-keyring))))) + (message "Verifying...") + (if (mc-process-region + start end nil mc-pgp-path args 'mc-pgp-verify-parser buffer) + t + (save-excursion + (set-buffer buffer) + (goto-char (point-min)) + (if (and + (not no-fetch) + (re-search-forward mc-pgp-key-expected-re nil t) + (setq pgp-id + (concat "0x" (buffer-substring-no-properties + (match-beginning 1) + (match-end 1)))) + (y-or-n-p + (format "Key %s not found; attempt to fetch? " pgp-id)) + (mc-pgp-fetch-key (cons nil pgp-id)) + (set-buffer obuf)) + (mc-pgp-verify-region start end t) + (mc-message mc-pgp-error-re buffer) + nil))))) + +(defun mc-pgp-insert-public-key (&optional id) + (let ((buffer (get-buffer-create mc-buffer-name)) + args) + (setq id (or id mc-pgp-user-id)) + (setq args (list "+verbose=1" "+batchmode" "+language=en" "-kxaf" id)) + (if mc-pgp-comment + (setq args (cons (format "+comment=%s" mc-pgp-comment) args))) + (if mc-pgp-alternate-keyring + (setq args (append args (list (format "+pubring=%s" + mc-pgp-alternate-keyring))))) + + (if (mc-process-region (point) (point) nil mc-pgp-path + args 'mc-pgp-generic-parser buffer) + (progn + (mc-message "Key for user ID: .*" buffer) + t)))) + +(defun mc-pgp-snarf-parser (result) + (eq result 0)) + +(defun mc-pgp-snarf-keys (start end) + ;; Returns number of keys found. + (let ((buffer (get-buffer-create mc-buffer-name)) tmpstr args) + (setq args '("+verbose=1" "+batchmode" "+language=en" "-kaf")) + (if mc-pgp-alternate-keyring + (setq args (append args (list (format "+pubring=%s" + mc-pgp-alternate-keyring))))) + (message "Snarfing...") + (if (mc-process-region start end nil mc-pgp-path args + 'mc-pgp-snarf-parser buffer) + (save-excursion + (set-buffer buffer) + (goto-char (point-min)) + (if (re-search-forward mc-pgp-newkey-re nil t) + (progn + (if mc-pgp-display-snarf-output (mc-display-buffer buffer)) + (setq tmpstr (buffer-substring-no-properties + (match-beginning 1) + (match-end 1))) + (if (equal tmpstr "No") + 0 + (car (read-from-string tmpstr)))))) + (mc-display-buffer buffer) + (mc-message mc-pgp-error-re buffer "Error snarfing PGP keys") + 0))) + +(defun mc-scheme-pgp () + (list + (cons 'encryption-func 'mc-pgp-encrypt-region) + (cons 'decryption-func 'mc-pgp-decrypt-region) + (cons 'signing-func 'mc-pgp-sign-region) + (cons 'verification-func 'mc-pgp-verify-region) + (cons 'key-insertion-func 'mc-pgp-insert-public-key) + (cons 'snarf-func 'mc-pgp-snarf-keys) + (cons 'msg-begin-line mc-pgp-msg-begin-line) + (cons 'msg-end-line mc-pgp-msg-end-line) + (cons 'signed-begin-line mc-pgp-signed-begin-line) + (cons 'signed-end-line mc-pgp-signed-end-line) + (cons 'key-begin-line mc-pgp-key-begin-line) + (cons 'key-end-line mc-pgp-key-end-line) + (cons 'user-id mc-pgp-user-id))) + +;;{{{ Key fetching + +(defvar mc-pgp-keyserver-url-template + "/htbin/pks-extract-key.pl?op=get&search=%s" + "The URL to pass to the keyserver") + +(defvar mc-pgp-keyserver-address "pgp.ai.mit.edu" + "Host name of keyserver") + +(defvar mc-pgp-keyserver-port 80 + "The port on which the keyserver's HTTP daemon lives") + +(defvar mc-pgp-fetch-timeout 20 + "*Timeout, in seconds, for any particular key fetch operation.") + +(defvar mc-pgp-fetch-keyring-list nil + "*List of strings which are filenames of public keyrings to search +when fetching keys.") + +(defsubst mc-pgp-buffer-get-key (buf) + "Return the first key block in BUF as a string, or nil if none found." + (save-excursion + (let (start) + (set-buffer buf) + (goto-char (point-min)) + (and (re-search-forward mc-pgp-key-begin-line nil t) + (setq start (match-beginning 0)) + (re-search-forward mc-pgp-key-end-line nil t) + (buffer-substring-no-properties start (match-end 0)))))) + +(defun mc-pgp-fetch-from-keyrings (id) + (let ((keyring-list mc-pgp-fetch-keyring-list) + buf proc key) + (unwind-protect + (progn + (message "Fetching %s from keyrings..." (or (cdr id) (car id))) + (while (and (not key) keyring-list) + (setq buf (generate-new-buffer " *mailcrypt temp*")) + (setq proc + (start-process "*PGP*" buf mc-pgp-path "-kxaf" + "+verbose=0" "+batchmode" + (format "+pubring=%s" (car keyring-list)) + (or (cdr id) (car id)))) + ;; Because PGPPASSFD might be set + (process-send-string proc "\r\n") + (while (eq 'run (process-status proc)) + (accept-process-output proc 5)) + (setq key (mc-pgp-buffer-get-key buf)) + (setq keyring-list (cdr keyring-list))) + key) + (if buf (kill-buffer buf)) + (if (and proc (eq 'run (process-status proc))) + (interrupt-process proc))))) + +(defun mc-pgp-fetch-from-http (id) + (let (buf connection) + (unwind-protect + (progn + (message "Fetching %s via HTTP to %s..." + (or (cdr id) (car id)) mc-pgp-keyserver-address) + (setq buf (generate-new-buffer " *mailcrypt temp*")) + (setq connection + (open-network-stream "*key fetch*" buf mc-pgp-keyserver-address + mc-pgp-keyserver-port)) + (process-send-string + connection + (concat "GET " (format mc-pgp-keyserver-url-template + (or (cdr id) (car id))) "\r\n")) + (while (and (eq 'open (process-status connection)) + (accept-process-output connection mc-pgp-fetch-timeout))) + (mc-pgp-buffer-get-key buf)) + (if buf (kill-buffer buf)) + (if connection (delete-process connection))))) + +(defun mc-pgp-fetch-from-finger (id) + (let (buf connection user host) + (unwind-protect + (and (car id) + (string-match "^\\(.+\\)@\\([^@]+\\)$" (car id)) + (progn + (message "Trying finger %s..." (car id)) + (setq user (substring (car id) + (match-beginning 1) (match-end 1))) + (setq host (substring (car id) + (match-beginning 2) (match-end 2))) + (setq buf (generate-new-buffer " *mailcrypt temp*")) + (condition-case nil + (progn + (setq connection + (open-network-stream "*key fetch*" buf host 79)) + (process-send-string connection + (concat "/W " user "\r\n")) + (while + (and (eq 'open (process-status connection)) + (accept-process-output connection + mc-pgp-fetch-timeout))) + (mc-pgp-buffer-get-key buf)) + (error nil)))) + (if buf (kill-buffer buf)) + (if connection (delete-process connection))))) + +(defvar mc-pgp-fetch-methods '(mc-pgp-fetch-from-keyrings + mc-pgp-fetch-from-finger + mc-pgp-fetch-from-http) + "List of methods to try when attempting to fetch a key. Each +element is a function to call with an ID as argument. See the +documentation for the function mc-pgp-fetch-key for a description of +the ID.") + +(defun mc-pgp-fetch-key (&optional id) + "Attempt to fetch a key for addition to PGP keyring. Interactively, +prompt for string matching key to fetch. + +Non-interactively, ID must be a pair. The CAR must be a bare Email +address and the CDR a keyID (with \"0x\" prefix). Either, but not +both, may be nil. + +Return t if we think we were successful; nil otherwise. Note that nil +is not necessarily an error, since we may have merely fired off an Email +request for the key." + (interactive) + (let ((methods mc-pgp-fetch-methods) + (process-connection-type nil) key proc buf args) + (if (null id) + (setq id (cons (read-string "Fetch key for: ") nil))) + (while (and (not key) methods) + (setq key (funcall (car methods) id)) + (setq methods (cdr methods))) + (if (not (stringp key)) + (progn + (message "Key not found.") + nil) + ;; Maybe I'll do this right someday. + (unwind-protect + (save-window-excursion + (setq buf (generate-new-buffer " *PGP Key Info*")) + (pop-to-buffer buf) + (if (< (window-height) (/ (frame-height) 2)) + (enlarge-window (- (/ (frame-height) 2) + (window-height)))) + (setq args '("-f" "+verbose=0" "+batchmode")) + (if mc-pgp-alternate-keyring + (setq args + (append args (list (format "+pubring=%s" + mc-pgp-alternate-keyring))))) + + (setq proc (apply 'start-process "*PGP*" buf mc-pgp-path args)) + ;; Because PGPPASSFD might be set + (process-send-string proc "\r\n") + (process-send-string proc key) + (process-send-string proc "\r\n") + (process-send-eof proc) + (set-buffer buf) + (while (eq 'run (process-status proc)) + (accept-process-output proc 5) + (goto-char (point-min))) + (if (y-or-n-p "Add this key to keyring? ") + (progn + (setq args (append args '("-ka"))) + (setq proc + (apply 'start-process "*PGP*" buf mc-pgp-path args)) + ;; Because PGPPASSFD might be set + (process-send-string proc "\r\n") + (process-send-string proc key) + (process-send-string proc "\r\n") + (process-send-eof proc) + (while (eq 'run (process-status proc)) + (accept-process-output proc 5)) + t))) + (if buf (kill-buffer buf)))))) + +;;}}} diff --git a/mailcrypt/mc-remail.el b/mailcrypt/mc-remail.el new file mode 100644 index 0000000..7667c9c --- /dev/null +++ b/mailcrypt/mc-remail.el @@ -0,0 +1,831 @@ +;; mc-remail.el --- Remailer support for Mailcrypt + +;; Copyright (C) 1995 Patrick LoPresti + +;;{{{ Licensing + +;; This file is intended to be used with GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +;;}}} +;;{{{ Load required packages + +(require 'mail-utils) +(require 'sendmail) +(require 'mailcrypt) + +(eval-and-compile + (autoload 'mc-cleanup-recipient-headers "mc-toplev") + (autoload 'mc-encrypt-message "mc-toplev")) + +(eval-and-compile + (condition-case nil (require 'mailalias) (error nil))) + +;;}}} +;;{{{ Functions dealing with remailer structures + +(defsubst mc-remailer-create (addr id prop pre-encr post-encr) + "Create a remailer structure. + +ADDR is the remailer's Email address, a string. + +ID is the remailer's public key ID (a string) or nil if the same as +ADDR. + +PROP is a list of properties, as strings. + +PRE-ENCR is a list of pre-encryption functions. Its elements will be +called with the remailer structure itself as argument. + +POST-ENCR is similar, but for post-encryption functions." +(list 'remailer addr id prop pre-encr post-encr)) + +(defsubst mc-remailerp (remailer) + "Test whether REMAILER is a valid remailer struct." + (and (listp remailer) (eq 'remailer (car-safe remailer)))) + +(defsubst mc-remailer-address (remailer) + "Return the Email address of REMAILER." + (nth 1 remailer)) + +(defsubst mc-remailer-userid (remailer) + "Return the userid with which to look up the public key for REMAILER." + (or (nth 2 remailer) + (car (cdr (mail-extract-address-components + (mc-remailer-address remailer)))))) + +(defsubst mc-remailer-properties (remailer) + "Return the property list for REMAILER" + (nth 3 remailer)) + +(defsubst mc-remailer-pre-encrypt-hooks (remailer) + "Return the list of pre-encryption hooks for REMAILER." + (nth 4 remailer)) + +(defsubst mc-remailer-post-encrypt-hooks (remailer) + "Return the list of post-encryption hooks for REMAILER." + (nth 5 remailer)) + +;;}}} +;;{{{ User variables + +(defvar mc-response-block-included-headers + '("From" "To" "Newsgroups") + "List of header fields to include in response blocks. + +These will be copied into the deepest layer of the response block to +help you identify it when it is used to Email you.") + + +(defvar mc-remailer-tag "(*REMAILER*)" + "A string which marks an Email address as belonging to a remailer.") + +(defvar mc-levien-file-name "~/.remailers" + "The file containing a Levien format list of remailers. + +The file is read by `mc-read-levien-file' and `mc-reread-levien-file'. + +The file should include lines of the following form (other lines +are ignored): + +$remailer{\"NAME\"} = \" PROPERTIES\"; + +PROPERTIES is a space-separated set of strings. + +This format is named after Raphael Levien, who maintains a list of +active remailers. Do \"finger remailer-list@kiwi.cs.berkeley.edu\" +for the latest copy of his list.") + +(defvar mc-remailer-user-chains nil + "An alist of remailer chains defined by the user. + +Format is + +((NAME . REMAILER-LIST) + (NAME . REMAILER-LIST) + ...) + +NAME must be a string. + +REMAILER-LIST may be an arbitrary sequence, not just a list. Its +elements may be any of the following: + +1) A remailer structure created by `mc-remailer-create'. This is + the base case. + +2) A string naming another remailer chain to be spliced in + at this point. + +3) An arbitrary Lisp form to be evaluated, which should + return another REMAILER-LIST to be recursively processed and + spliced in at this point. + +The complete alist of chains is given by the union of the two lists +`mc-remailer-internal-chains' and `mc-remailer-user-chains'.") + +(defvar mc-remailer-internal-chains nil + "List of \"internal\" remailer chains. + +This variable is normally generated automatically from a human-readable +list of remailers; see, for example, the function `mc-reread-levien-file'. + +To define your own chains, you probably want to use the variable +`mc-remailer-user-chains'. See that variable's documentation for +format information.") + +(defvar mc-remailer-user-response-block + (function + (lambda (addr lines block) + (concat + ";;;\n" + (format + "To reply to this message, take the following %d-line block, remove\n" + lines) + "leading \"- \" constructs (if any), and place it at the top of a\n" + (format "message to %s :\n" addr) + block))) + "A function called to generate response block text. + +Value should be a function taking three arguments (ADDR LINES BLOCK). +ADDR is the address to which the response should be sent. +LINES is the number of lines in the encrypted response block. +BLOCK is the response block itself. +Function should return a string to be inserted into the buffer +by mc-remailer-insert-response-block.") + +(defvar mc-remailer-pseudonyms nil + "*A list of your pseudonyms. + +This is a list of strings. Completion against it will be available +when you are prompted for your pseudonym.") + +(defvar mc-remailer-preserved-headers + '("References" "Followup-to" "In-reply-to") + "*Header fields which are preserved as hashmark headers when rewriting. + +This is a list of strings naming the preserved headers. Note that +\"Subject\", \"Newsgroups\", and \"To\" are handled specially and +should not be included in this list.") + +;;}}} +;;{{{ Functions for handling Levien format remailer lists + +(defun mc-parse-levien-buffer () + ;; Parse a buffer in Levien format. + (goto-char (point-min)) + (let (chains remailer remailer-name) + (while + (re-search-forward + "^\\$remailer{\"\\(.+\\)\"}[ \t]*=[ \t]*\"\\(.*\\)\";" + nil t) + (let ((name (buffer-substring-no-properties + (match-beginning 1) (match-end 1))) + property-list address + (value-start (match-beginning 2)) + (value-end (match-end 2))) + (goto-char value-start) + (while (re-search-forward "[^ \t]+" value-end 'no-error) + (setq property-list + (append + property-list + (list (buffer-substring-no-properties + (match-beginning 0) (match-end 0)))))) + (setq address (car property-list) + property-list (cdr property-list) + remailer-name name) + (if (not + (and (or (member "pgp" property-list) + (member "pgp." property-list)) + (or (member "cpunk" property-list) ; hurm... + (member "eric" property-list)))) ; fixme? + (setq remailer nil) + (setq remailer + (mc-remailer-create + address ; Address + (if (member "pgp." property-list) + name) ; User ID + property-list + '(mc-generic-pre-encrypt-function) ; Pre-encrypt hooks + '(mc-generic-post-encrypt-function) ; Post-encrypt hooks + )))) + (if (not (null remailer)) + (setq chains (cons (list remailer-name remailer) chains)))) + chains)) + +(defun mc-read-levien-file () + "Read the Levien format file specified in `mc-levien-file-name'. +Return an alist of length-1 chains, one for each remailer, named +after the remailer. Only include remailers supporting PGP +encryption." + (save-excursion + (if (file-readable-p mc-levien-file-name) + (prog2 + (find-file-read-only mc-levien-file-name) + (mc-parse-levien-buffer) + (bury-buffer))))) + +(defun mc-reread-levien-file () + "Read the Levien format file specified in `mc-levien-file-name'. + +Place result in `mc-remailer-internal-chains'. + +See the documentation for the variable `mc-levien-file-name' for +a description of Levien file format." + (interactive) + (setq mc-remailer-internal-chains (mc-read-levien-file))) + +;;}}} +;;{{{ Canonicalization function + +(defun mc-remailer-canonicalize-elmt (elmt chains-alist) + (cond + ((mc-remailerp elmt) (list elmt)) + ((stringp elmt) + (mc-remailer-canonicalize-chain (cdr (assoc elmt chains-alist)) + chains-alist)) + (t (mc-remailer-canonicalize-chain (eval elmt) chains-alist)))) + +(defun mc-remailer-canonicalize-chain (chain chains-alist) + ;; Canonicalize a remailer chain with respect to CHAINS-ALIST. + ;; That is, use CHAINS-ALIST to resolve strings. + ;; Here is where we implement the functionality described in + ;; the documentation for the variable `mc-remailer-user-chains'. + (cond + ((null chain) nil) + ;; Handle case where chain is actually a string or a single + ;; remailer. + ((or (stringp chain) (mc-remailerp chain)) + (mc-remailer-canonicalize-elmt chain chains-alist)) + (t + (let ((first (elt chain 0)) + (rest (cdr (append chain nil)))) + (append + (mc-remailer-canonicalize-elmt first chains-alist) + (mc-remailer-canonicalize-chain rest chains-alist)))))) + +;;}}} +;;{{{ Auxiliaries for mail header munging + +;; In case I ever decide to do this right. +(defconst mc-field-name-regexp "^\\(.+\\)") +(defconst mc-field-body-regexp "\\(.*\\(\n[ \t].*\\)*\n\\)") + +(defun mc-get-fields (&optional matching bounds nuke) + "Get all header fields within BOUNDS. Return as an +alist ((FIELD-NAME . FIELD-BODY) (FIELD-NAME . FIELD-BODY) ...). + +Argument MATCHING, if present, is a regexp which each FIELD-NAME +must match exactly. Matching is case-insensitive. + +Optional arg NUKE, if non-nil, means eliminate all fields returned." + (save-excursion + (save-restriction + (let ((case-fold-search t) + (header-field-regexp + (concat mc-field-name-regexp ":" mc-field-body-regexp)) + ret name body field-start field-end) + ;; Ensure exact match + (if matching + (setq matching (concat "^\\(" matching "\\)$"))) + + (if bounds + (narrow-to-region (car bounds) (cdr bounds))) + + (goto-char (point-max)) + + (while (re-search-backward header-field-regexp nil 'move) + (setq field-start (match-beginning 0)) + (setq field-end (match-end 0)) + (setq name (buffer-substring-no-properties + (match-beginning 1) (match-end 1))) + (setq body (buffer-substring (match-beginning 2) (match-end 2))) + (if (or (null matching) (string-match matching name)) + (progn + (setq ret (cons (cons name body) ret)) + (if nuke + (delete-region field-start field-end))))) + ret)))) + + +(defsubst mc-nuke-field (field &optional bounds) + ;; Delete all fields exactly matching regexp FIELD from header, + ;; bounded by BOUNDS. Default is entire visible region of buffer. + (mc-get-fields field bounds t)) + +(defun mc-replace-field (field-name replacement header) + (save-excursion + (save-restriction + (if (not (string-match "^[ \t]" replacement)) + (setq replacement (concat " " replacement))) + (if (not (string-match "\n$" replacement)) + (setq replacement (concat replacement "\n"))) + (let ((case-fold-search t) + (field-regexp (regexp-quote field-name))) + (narrow-to-region (car header) (cdr header)) + (goto-char (point-min)) + (re-search-forward + (concat "^" field-regexp ":" mc-field-body-regexp) + nil t) + (mc-nuke-field field-regexp header) + (insert field-name ":" replacement))))) + +(defun mc-find-main-header (&optional ignored) + ;; Find the main header of the mail message; return as a pair of + ;; markers (START . END). + (save-excursion + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "\n")) + (forward-line -1) + (cons (copy-marker (point-min)) (copy-marker (point))))) + +(defun mc-find-colon-header (&optional insert) + ;; Find the header with a "::" immediately after the + ;; mail-header-separator. Return region enclosing header. Optional + ;; arg INSERT means insert the header if it does not exist already. + (save-excursion + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "\n")) + (if (or (and (looking-at "::\n") (forward-line 1)) + (and insert + (progn + (insert-before-markers "::\n\n") + (forward-line -1)))) + (let ((start (point))) + (re-search-forward "^$" nil 'move) + (cons (copy-marker start) (copy-marker (point))))))) + +(defun mc-find-hash-header (&optional insert) + (save-excursion + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "\n")) + (if (or (and (looking-at "##\n") (forward-line 1)) + (and (looking-at "::\n") + (re-search-forward "^\n" nil 'move) + (looking-at "##\n") + (forward-line 1)) + (and insert + (progn + (insert-before-markers "##\n\n") + (forward-line -1)))) + (let ((start (point))) + (re-search-forward "^$" nil 'move) + (cons (copy-marker start) (copy-marker (point))))))) + + +(defsubst mc-replace-main-field (field replacement) + (mc-replace-field field replacement (mc-find-main-header t))) + +(defsubst mc-replace-hash-field (field replacement) + (mc-replace-field field replacement (mc-find-hash-header t))) + +(defsubst mc-replace-colon-field (field replacement) + (mc-replace-field field replacement (mc-find-colon-header t))) + +(defun mc-recipient-is-remailerp () + (let ((to (mc-get-fields "To" (mc-find-main-header)))) + (and to + (string-match (regexp-quote mc-remailer-tag) (cdr (car to)))))) + +;;}}} +;;{{{ Pre-encryption and post-encryption hook defaults + +(defun mc-generic-post-encrypt-function (remailer) + (let ((main-header (mc-find-main-header)) + (colon-header (mc-find-colon-header t))) + (mc-replace-field "Encrypted" "PGP" colon-header) + (mc-replace-field + "To" + (concat (mc-remailer-address remailer) " " mc-remailer-tag) + main-header))) + +(defun mc-generic-pre-encrypt-function (remailer) + (let ((addr (mc-remailer-address remailer)) + (props (mc-remailer-properties remailer)) + (main-header (mc-find-main-header)) + (colon-header (mc-find-colon-header t)) + to to-field preserved-regexp preserved) + + (setq preserved-regexp + (mc-disjunction-regexp mc-remailer-preserved-headers)) + (setq preserved (mc-get-fields preserved-regexp main-header t)) + (if preserved (goto-char (cdr (mc-find-hash-header t)))) + (mapcar (function (lambda (c) (insert (car c) ":" (cdr c)))) + preserved) + + (if (and (mc-find-hash-header) (not (member "hash" props))) + (error "Remailer %s does not support hashmarks" addr)) + + (if (mc-get-fields "Newsgroups" main-header) + (cond ((not (member "post" props)) + (error "Remailer %s does not support posting" addr)) + ((not (member "hash" props)) + (error "Remailer %s does not support hashmarks" addr)) + (t (mc-rewrite-news-to-mail remailer))) + (and (featurep 'mailalias) + mail-aliases + (expand-mail-aliases (car main-header) (cdr main-header))) + (setq to + (mapconcat (function (lambda (c) (cdr c))) + (mc-get-fields "To" main-header) + ", ")) + (if (string-match "," to) + (error "Remailer %s does not support multiple recipients." addr)) + (setq to-field + (if (mc-get-fields "From" colon-header) + "Send-To" + (cond + ((member "eric" props) "Anon-Send-To") + (t "Request-Remailing-To")))) + (mc-replace-field to-field to colon-header) + (mc-nuke-field "Reply-to" main-header)))) + +;;}}} +;;{{{ Misc. random + +(defun mc-disjunction-regexp (regexps) + ;; Take a list of regular expressions and return a single + ;; regular expression which matches anything that any of the + ;; original regexps match. + (concat "\\(" + (mapconcat 'identity regexps "\\)\\|\\(") + "\\)")) + +(defun mc-user-mail-address () + "Figure out the user's Email address as best we can." + (cond ((stringp mail-default-reply-to) + mail-default-reply-to) + ((boundp 'user-mail-address) user-mail-address) + (t (concat (user-login-name) "@" (system-name))))) + +(defsubst mc-remailer-make-chains-alist () + (if (null mc-remailer-internal-chains) + (mc-reread-levien-file)) + (append mc-remailer-internal-chains mc-remailer-user-chains)) + +(defun mc-remailer-insert-pseudonym () + "Insert pseudonym as a From field in the hash-mark header. + +See the documentation for the variable `mc-remailer-pseudonyms' for +more information." + (interactive) + (let ((pseudonym + (cond ((null mc-remailer-pseudonyms) + (read-from-minibuffer "Pseudonym: ")) + (t + (completing-read "Pseudonym: " + (mapcar 'list mc-remailer-pseudonyms)))))) + (if (not (string-match "\\S +@\\S +" pseudonym)) + (setq pseudonym (concat pseudonym " "))) + (mc-replace-colon-field "From" pseudonym))) + +;;}}} +;;{{{ Mixmaster support +(defvar mc-mixmaster-path nil + "*Path to the Mixmaster binary. If defined, Mixmaster chains will +be passed to this program for rewriting.") + +(defvar mc-mixmaster-list-path nil + "*Path to the Mixmaster type2.list file.") + +(defun mc-mixmaster-process (beg end recipients preserved mix-chain) + ;; Run a region through Mixmaster. + (let (ret) + (if (not (markerp end)) + (setq end (copy-marker end))) + (goto-char beg) + (mapcar (function (lambda (x) (insert x ?\n))) recipients) + (insert ?\n) + (mapcar (function (lambda (x) (insert x))) preserved) + (insert ?\n) + (setq mix-chain (mapcar (function (lambda (x) (format "%d" x))) mix-chain)) + ;; Handle case of empty message + (if (< end (point)) (setq end (point))) + + ;; Debug HACK +;;; (read-char-exclusive) + + (setq ret + (apply 'call-process-region beg end mc-mixmaster-path t t nil + "-f" "-o" "stdout" "-l" mix-chain)) + (if (not (eq ret 0)) (error "Mixmaster barfed.")) + (goto-char beg) + (re-search-forward "^::$") + (delete-region beg (match-beginning 0)))) + +(defun mc-mixmaster-build-alist (&optional n) + ;; Construct an alist mapping Mixmaster Email addresses to integers. + ;; FIXME; this is terrible + (let (buf) + (save-excursion + (unwind-protect + (progn + (setq n (or n 1)) + (setq buf (find-file-noselect mc-mixmaster-list-path)) + (set-buffer buf) + (if (re-search-forward "^[^ \t]+[ \t]+\\([^ \t]+\\)" nil t) + (cons (cons (buffer-substring-no-properties + (match-beginning 1) (match-end 1)) + n) + (mc-mixmaster-build-alist (+ n 1))))) + (if buf (kill-buffer buf)))))) + +(defvar mc-mixmaster-alist nil) + +(defsubst mc-mixmaster-alist () + (or mc-mixmaster-alist + (setq mc-mixmaster-alist (mc-mixmaster-build-alist)))) + +(defun mc-mixmaster-translate-chain (chain) + ;; Take a chain of Mixmaster remailers and convert it to the list + ;; of integers which represents them. + (if (or (null chain) + (not (member "mix" (mc-remailer-properties (car chain))))) + nil + (cons (cdr (assoc (car (cdr (mail-extract-address-components + (mc-remailer-address (car chain))))) + (mc-mixmaster-alist))) + (mc-mixmaster-translate-chain (cdr chain))))) + +(defun mc-mixmaster-skip (chain) + ;; Return the largest possible suffix of CHAIN whose first element + ;; is not a Mixmaster. + (cond ((null chain) nil) + ((not (member "mix" (mc-remailer-properties (car chain)))) + chain) + (t (mc-mixmaster-skip (cdr chain))))) + +(defun mc-rewrite-for-mixmaster (chain &optional pause) + ;; Rewrite the current mail buffer for a chain of Mixmasters. + (let ((mix-chain (mc-mixmaster-translate-chain chain)) + (main-header (mc-find-main-header)) + (colon-header (mc-find-colon-header)) + (hash-header (mc-find-hash-header)) + recipients preserved newsgroups first last rest preserved-regexp) + + ;; Figure out FIRST, and LAST. FIRST is the first Mixmaster in the + ;; chain. LAST is the last. + (setq first (car chain) + rest chain) + (while (and rest (member "mix" (mc-remailer-properties (car rest)))) + (setq last (car rest) + rest (cdr rest))) + + ;; If recipient is not a remailer, deal with hashmark and colon + ;; headers and get rid of them. + (if (mc-recipient-is-remailerp) + nil + (if hash-header + (progn + (setq preserved (mc-get-fields nil hash-header)) + (goto-char (car hash-header)) + (forward-line -1) + (delete-region (point) (+ (cdr hash-header) 1)))) + ;; Preserve pseduonym line... + (if colon-header + (progn + (setq preserved + (append (mc-get-fields "From" colon-header) preserved)) + (goto-char (car colon-header)) + (forward-line -1) + (delete-region (point) (+ (cdr colon-header) 1))))) + + ;; Expand aliases and get recipients. + (and (featurep 'mailalias) + mail-aliases + (expand-mail-aliases (car main-header) (cdr main-header))) + (setq recipients + (mc-cleanup-recipient-headers + (mapconcat 'cdr (mc-get-fields "To" main-header t) ", "))) + (setq newsgroups (mc-get-fields "Newsgroups" nil t)) + (if (and newsgroups + (not (member "post" (mc-remailer-properties last)))) + (error "Remailer %s does not support posting" + (mc-remailer-address last))) + (setq + recipients + (append (mapcar + (function (lambda (c) (concat "Post:" (cdr c)))) newsgroups) + recipients)) + + ;; Maybe this should be in a function somewhere. + (setq + preserved-regexp + (mc-disjunction-regexp (cons "Subject" mc-remailer-preserved-headers))) + + (setq preserved + (append (mc-get-fields preserved-regexp main-header t) preserved)) + + ;; Convert preserved header alist to simple list of strings + (setq preserved + (mapcar (function (lambda (c) (concat (car c) ":" (cdr c)))) + preserved)) + + ;; Do the conversion + (goto-char (cdr main-header)) + (forward-line 1) + (mc-mixmaster-process (point) (point-max) recipients preserved + mix-chain) + + (mc-replace-field "To" + (concat (mc-remailer-address first) " " mc-remailer-tag) + main-header))) + + +;;}}} +;;{{{ High level message rewriting + +(defun mc-rewrite-news-to-mail (remailer) + (let ((main-header (mc-find-main-header)) + newsgroups) + (setq newsgroups (mc-get-fields "Newsgroups" main-header t)) + (mc-replace-colon-field "Post-To" (cdr (car newsgroups))) + (mail-mode))) + +(defun mc-rewrite-for-remailer (remailer &optional pause) + ;; Rewrite the current mail buffer for a single remailer. This + ;; includes running the pre-encryption hooks, modifying the To: + ;; field, encrypting with the remailer's public key, and running the + ;; post-encryption hooks. + (let ((addr (mc-remailer-address remailer)) + (main-header (mc-find-main-header))) + ;; If recipient is already a remailer, make sure the "::" and "##" + ;; headers get to it + (if (mc-recipient-is-remailerp) + (progn + (goto-char (cdr main-header)) + (forward-line 1) + (insert "::\n\n"))) + + (mapcar + (function (lambda (hook) (funcall hook remailer))) + (mc-remailer-pre-encrypt-hooks remailer)) + + ;; Move "Subject" lines down. + (goto-char (car (mc-find-colon-header t))) + (mapcar + (function (lambda (f) (insert (car f) ":" (cdr f)))) + (mc-get-fields "Subject" main-header t)) + + (if pause + (let ((cursor-in-echo-area t)) + (message "SPC to encrypt for %s : " addr) + (read-char-exclusive))) + (setq main-header (mc-find-main-header)) + (goto-char (cdr main-header)) + (forward-line 1) + (if (let ((mc-pgp-always-sign 'never) + (mc-encrypt-for-me nil)) + (mc-encrypt-message (mc-remailer-userid remailer) nil (point))) + (progn + (mapcar + (function (lambda (hook) (funcall hook remailer))) + (mc-remailer-post-encrypt-hooks remailer)) + (mc-nuke-field "Comment") + (mc-nuke-field "From")) + (error "Unable to encrypt message to %s" + (mc-remailer-userid remailer))))) + +(defun mc-rewrite-for-chain (chain &optional pause) + ;; Rewrite the current buffer for a chain of remailers. + ;; CHAIN must be in canonical form. + (let (rest) + (if mc-mixmaster-path + (setq rest (mc-mixmaster-skip chain)) + (setq rest chain)) + (if (null chain) nil + (mc-rewrite-for-chain + (if (eq rest chain) (cdr rest) rest) pause) + (if (eq rest chain) + (mc-rewrite-for-remailer (car chain)) + (mc-rewrite-for-mixmaster chain pause))))) + +(defun mc-unparse-chain (chain) + ;; Unparse CHAIN into a string suitable for printing. + (if (null chain) + nil + (concat (mc-remailer-address (car chain)) "\n" + (mc-unparse-chain (cdr chain))))) + +(defun mc-disallow-field (field &optional header) + (let ((case-fold-search t)) + (if (null header) + (setq header (mc-find-main-header))) + (goto-char (car header)) + (if (re-search-forward (concat "^" (regexp-quote field) ":") + (cdr header) t) + + (progn + (goto-char (match-beginning 0)) + (error "Cannot use a %s field." field))))) + +(defun mc-remailer-encrypt-for-chain (&optional pause) + "Encrypt message for a remailer chain, prompting for chain to use. + +With \\[universal-argument], pause before each encryption." + (interactive "P") + (let ((chains (mc-remailer-make-chains-alist)) + (buffer (get-buffer-create mc-buffer-name)) + chain-name chain) + (mc-disallow-field "CC") + (mc-disallow-field "FCC") + (mc-disallow-field "BCC") + (setq chain-name + (completing-read + "Choose a remailer or chain: " chains nil 'strict-match)) + (setq chain + (mc-remailer-canonicalize-chain + (cdr (assoc chain-name chains)) + chains)) + (mc-rewrite-for-chain chain pause) + (if chain + (save-excursion + (set-buffer buffer) + (erase-buffer) + (insert "Rewritten for chain `" chain-name "':\n\n" + (mc-unparse-chain chain)) + (message "Done. See %s buffer for details." mc-buffer-name))))) + +;;}}} +;;{{{ Response block generation + +(defun mc-remailer-insert-response-block (&optional arg) + "Insert response block at point, prompting for chain to use. + +With \\[universal-argument], enter a recursive edit of the innermost +layer of the block before encrypting it." + (interactive "p") + (let (buf main-header to addr block lines) + (save-excursion + (setq buf + (mc-remailer-make-response-block (if (> arg 1) t))) + (set-buffer buf) + (setq main-header (mc-find-main-header)) + (setq to (mc-get-fields "To" main-header)) + (setq + addr + (concat "<" (nth 1 + (mail-extract-address-components (cdr (car to)))) + ">")) + (goto-char (cdr main-header)) + (forward-line 1) + (setq block (buffer-substring (point) (point-max)) + lines (count-lines (point) (point-max))) + (kill-buffer buf)) + (let ((opoint (point))) + (insert (funcall mc-remailer-user-response-block + addr lines block)) + (goto-char opoint)) + (mc-nuke-field "Reply-to" (mc-find-main-header)) + (mc-replace-hash-field "Reply-to" addr))) + +(defun mc-remailer-make-response-block (&optional recurse) + ;; Return a buffer which contains a response block + ;; for the user, and a To: header for the remailer to use. + (let ((buf (generate-new-buffer " *Remailer Response Block*")) + (original-buf (current-buffer)) + (mc-mixmaster-path nil) + all-headers included-regexp included) + (setq all-headers (mc-find-main-header)) + (setcdr all-headers + (max + (cdr all-headers) + (or (cdr-safe (mc-find-colon-header)) 0) + (or (cdr-safe (mc-find-hash-header)) 0))) + (save-excursion + (setq + included-regexp + (mc-disjunction-regexp mc-response-block-included-headers)) + (setq included (mc-get-fields included-regexp all-headers)) + (set-buffer buf) + (insert "To: " (mc-user-mail-address) "\n" mail-header-separator "\n") + (insert ";; Response block created " (current-time-string) "\n") + (mapcar (function (lambda (c) (insert "; " (car c) ":" (cdr c)))) + included) + (if recurse + (progn + (switch-to-buffer buf) + (message "Editing response block ; %s when done." + (substitute-command-keys "\\[exit-recursive-edit]")) + (recursive-edit))) + (set-buffer buf) + (mc-remailer-encrypt-for-chain) + (switch-to-buffer original-buf)) + buf)) + +;;}}} diff --git a/mailcrypt/mc-toplev.el b/mailcrypt/mc-toplev.el new file mode 100644 index 0000000..470a66b --- /dev/null +++ b/mailcrypt/mc-toplev.el @@ -0,0 +1,641 @@ +;; mc-toplev.el, entry point functions for Mailcrypt +;; Copyright (C) 1995 Jin Choi +;; Patrick LoPresti + +;;{{{ Licensing +;; This file is intended to be used with GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +;;}}} +;;{{{ Load some required packages +(require 'mailcrypt) +(require 'mail-utils) + +(eval-when-compile + ;; RMAIL + (condition-case nil (require 'rmail) (error nil)) + (autoload 'rmail-abort-edit "rmailedit") + (autoload 'rmail-cease-edit "rmailedit") + ;; Is this a good idea? + (defvar rmail-buffer nil) + + ;; VM + (condition-case nil (require 'vm) (error nil)) + + ;; GNUS + (condition-case nil (require 'gnus) (error nil)) + + ;; MH-E + (condition-case nil (require 'mh-e) (error nil))) + +(eval-and-compile + (condition-case nil (require 'mailalias) (error nil))) + +(autoload 'mc-scheme-pgp "mc-pgp" nil t) + +;;}}} + +;;{{{ Encryption + +(defun mc-cleanup-recipient-headers (str) + ;; Takes a comma separated string of recipients to encrypt for and, + ;; assuming they were possibly extracted from the headers of a reply, + ;; returns a list of the address components. + (mapcar (function + (lambda (x) + (car (cdr (mail-extract-address-components x))))) + (mc-split "\\([ \t\n]*,[ \t\n]*\\)+" str))) + +(defun mc-find-headers-end () + (save-excursion + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "\n")) + (if (looking-at "^::\n") + (re-search-forward "^\n" nil t)) + (if (looking-at "^##\n") + (re-search-forward "^\n" nil t)) + (point-marker))) + +(defun mc-encrypt (arg) + "*Encrypt the current buffer. + +Exact behavior depends on current major mode. + +With \\[universal-argument], prompt for User ID to sign as. + +With \\[universal-argument] \\[universal-argument], prompt for encryption scheme to use." + (interactive "p") + (mc-encrypt-region arg nil nil)) + +(defun mc-encrypt-region (arg start end) + "*Encrypt the current region." + (interactive "p\nr") + (let* ((mode-alist (cdr-safe (assq major-mode mc-modes-alist))) + (func (or (cdr-safe (assq 'encrypt mode-alist)) + 'mc-encrypt-generic)) + sign scheme from) + (if (>= arg 4) + (setq from (read-string "User ID: ") + sign t)) + (if (>= arg 16) + (setq scheme + (cdr (assoc + (completing-read "Encryption Scheme: " mc-schemes) + mc-schemes)))) + (funcall func nil scheme start end from sign))) + +(defun mc-encrypt-generic (&optional recipients scheme start end from sign) + "*Generic function to encrypt a region of data." + (save-excursion + (or start (setq start (point-min-marker))) + (or (markerp start) (setq start (copy-marker start))) + (or end (setq end (point-max-marker))) + (or (markerp end) (setq end (copy-marker end))) + (run-hooks 'mc-pre-encryption-hook) + (cond ((stringp recipients) + (setq recipients + (mc-split "\\([ \t\n]*,[ \t\n]*\\)+" recipients))) + ((null recipients) + (setq recipients + (mc-cleanup-recipient-headers (read-string "Recipients: ")))) + (t (error "mc-encrypt-generic: recipients not string or nil"))) + (or scheme (setq scheme mc-default-scheme)) + (if (funcall (cdr (assoc 'encryption-func (funcall scheme))) + recipients start end from sign) + (progn + (run-hooks 'mc-post-encryption-hook) + t)))) + +(defun mc-encrypt-message (&optional recipients scheme start end from sign) + "*Encrypt a message for RECIPIENTS using the given encryption SCHEME. +RECIPIENTS is a comma separated string. If SCHEME is nil, use the value +of `mc-default-scheme'. Returns t on success, nil otherwise." + (save-excursion + (let ((headers-end (mc-find-headers-end)) + default-recipients) + + (setq default-recipients + (save-restriction + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$")) + (narrow-to-region (point-min) (point)) + (and (featurep 'mailalias) + mail-aliases + (expand-mail-aliases (point-min) (point-max))) + (mapconcat + 'identity + (append + (mc-cleanup-recipient-headers + (or (mail-fetch-field "to" nil t) "")) + (mc-cleanup-recipient-headers + (or (mail-fetch-field "bcc" nil t) "")) + (mc-cleanup-recipient-headers + (or (mail-fetch-field "cc" nil t) ""))) + ", "))) + + (if (not from) + (save-restriction + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "\n")) + (narrow-to-region (point) headers-end) + (setq from (mail-fetch-field "From")))) + + (if (not recipients) + (setq recipients + (if mc-use-default-recipients + default-recipients + (read-from-minibuffer "Recipients: " default-recipients)))) + + (or recipients (error "No recipients!")) + + (or start (setq start headers-end)) + (or end (setq end (point-max-marker))) + + (mc-encrypt-generic recipients scheme start end from sign)))) + + +;;}}} +;;{{{ Decryption + +(defun mc-decrypt () + "*Decrypt a message in the current buffer. + +Exact behavior depends on current major mode." + (interactive) + (let* ((mode-alist (cdr-safe (assq major-mode mc-modes-alist))) + (func (or (cdr-safe (assq 'decrypt mode-alist)) + 'mc-decrypt-message))) + (funcall func))) + +(defun mc-decrypt-message () + "Decrypt whatever message is in the current buffer. +Returns a pair (SUCCEEDED . VERIFIED) where SUCCEEDED is t if the encryption +succeeded and VERIFIED is t if it had a valid signature." + (save-excursion + (let ((schemes mc-schemes) + limits scheme) + (while (and schemes + (setq scheme (cdr (car schemes))) + (not (setq + limits + (mc-message-delimiter-positions + (cdr (assoc 'msg-begin-line (funcall scheme))) + (cdr (assoc 'msg-end-line (funcall scheme))))))) + (setq schemes (cdr schemes))) + + (if (null limits) + (error "Found no encrypted message in this buffer.") + (run-hooks 'mc-pre-decryption-hook) + (let ((resultval (funcall (cdr (assoc 'decryption-func + (funcall scheme))) + (car limits) (cdr limits)))) + (goto-char (point-min)) + (if (car resultval) ; decryption succeeded + (run-hooks 'mc-post-decryption-hook)) + resultval))))) +;;}}} +;;{{{ Signing +(defun mc-sign (arg) + "*Sign a message in the current buffer. + +Exact behavior depends on current major mode. + +With one prefix arg, prompts for private key to use, with two prefix args, +also prompts for encryption scheme to use. With negative prefix arg, +inhibits clearsigning (pgp)." + (interactive "p") + (mc-sign-region arg nil nil)) + +(defun mc-sign-region (arg start end) + "*Sign the current region." + (interactive "p\nr") + (let* ((mode-alist (cdr-safe (assq major-mode mc-modes-alist))) + (func (or (cdr-safe (assq 'sign mode-alist)) + 'mc-sign-generic)) + from scheme) + (if (>= arg 16) + (setq scheme + (cdr (assoc + (completing-read "Encryption Scheme: " mc-schemes) + mc-schemes)))) + (if (>= arg 4) + (setq from (read-string "User ID: "))) + + (funcall func from scheme start end (< arg 0)))) + +(defun mc-sign-generic (withkey scheme start end unclearsig) + (or scheme (setq scheme mc-default-scheme)) + (or start (setq start (point-min-marker))) + (or (markerp start) (setq start (copy-marker start))) + (or end (setq end (point-max-marker))) + (or (markerp end) (setq end (copy-marker end))) + (run-hooks 'mc-pre-signature-hook) + (funcall (cdr (assoc 'signing-func (funcall scheme))) + start end withkey unclearsig) + (run-hooks 'mc-post-signature-hook)) + +(defun mc-sign-message (&optional withkey scheme start end unclearsig) + "Clear sign the message." + (save-excursion + (let ((headers-end (mc-find-headers-end))) + (or withkey + (progn + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "\n")) + (save-restriction + (narrow-to-region (point) headers-end) + (setq withkey (mail-fetch-field "From"))))) + (or start (setq start headers-end)) + (or end (setq end (point-max-marker))) + (mc-sign-generic withkey scheme start end unclearsig)))) + +;;}}} +;;{{{ Signature verification + +(defun mc-verify () + "*Verify a message in the current buffer. + +Exact behavior depends on current major mode." + (interactive) + (let* ((mode-alist (cdr-safe (assq major-mode mc-modes-alist))) + (func (or (cdr-safe (assq 'verify mode-alist)) + 'mc-verify-signature))) + (funcall func))) + +(defun mc-verify-signature () + "*Verify the signature of the signed message in the current buffer. +Show the result as a message in the minibuffer. Returns t if the signature +is verified." + (save-excursion + (let ((schemes mc-schemes) + limits scheme) + (while (and schemes + (setq scheme (cdr (car schemes))) + (not + (setq + limits + (mc-message-delimiter-positions + (cdr (assoc 'signed-begin-line (funcall scheme))) + (cdr (assoc 'signed-end-line (funcall scheme))))))) + (setq schemes (cdr schemes))) + + (if (null limits) + (error "Found no signed message in this buffer.") + (funcall (cdr (assoc 'verification-func (funcall scheme))) + (car limits) (cdr limits)))))) + + +;;}}} +;;{{{ Key management + +;;{{{ mc-insert-public-key + +(defun mc-insert-public-key (&optional userid scheme) + "*Insert your public key at point. +With one prefix arg, prompts for user id to use. With two prefix +args, prompts for encryption scheme." + (interactive + (let (arglist) + (if (not (and (listp current-prefix-arg) + (numberp (car current-prefix-arg)))) + nil + (if (>= (car current-prefix-arg) 16) + (setq arglist + (cons (cdr (assoc (completing-read "Encryption Scheme: " + mc-schemes) + mc-schemes)) + arglist))) + (if (>= (car current-prefix-arg) 4) + (setq arglist (cons (read-string "User ID: ") arglist)))) + arglist)) + +; (if (< (point) (mc-find-headers-end)) +; (error "Can't insert key inside message header")) + (or scheme (setq scheme mc-default-scheme)) + (or userid (setq userid (cdr (assoc 'user-id (funcall scheme))))) + + ;; (goto-char (point-max)) + (if (not (bolp)) + (insert "\n")) + (funcall (cdr (assoc 'key-insertion-func (funcall scheme))) userid)) + +;;}}} +;;{{{ mc-snarf-keys + +(defun mc-snarf () + "*Add all public keys in the buffer to your keyring. + +Exact behavior depends on current major mode." + (interactive) + (let* ((mode-alist (cdr-safe (assq major-mode mc-modes-alist))) + (func (or (cdr-safe (assq 'snarf mode-alist)) + 'mc-snarf-keys))) + (funcall func))) + +(defun mc-snarf-keys () + "*Add all public keys in the buffer to your keyring." + (interactive) + (let ((schemes mc-schemes) + (start (point-min)) + (found 0) + limits scheme) + (save-excursion + (catch 'done + (while t + (while (and schemes + (setq scheme (cdr (car schemes))) + (not + (setq + limits + (mc-message-delimiter-positions + (cdr (assoc 'key-begin-line (funcall scheme))) + (cdr (assoc 'key-end-line (funcall scheme))) + start)))) + (setq schemes (cdr schemes))) + (if (null limits) + (throw 'done found) + (setq start (cdr limits)) + (setq found (+ found (funcall (cdr (assoc 'snarf-func + (funcall scheme))) + (car limits) (cdr limits))))))) + (message (format "%d new key%s found" found + (if (eq 1 found) "" "s")))))) +;;}}} +;;}}} +;;{{{ Mode specific functions + +;;{{{ RMAIL +(defun mc-rmail-summary-verify-signature () + "*Verify the signature in the current message." + (interactive) + (if (not (eq major-mode 'rmail-summary-mode)) + (error + "mc-rmail-summary-verify-signature called in inappropriate buffer")) + (save-excursion + (set-buffer rmail-buffer) + (mc-verify))) + +(defun mc-rmail-summary-decrypt-message () + "*Decrypt the contents of this message" + (interactive) + (if (not (eq major-mode 'rmail-summary-mode)) + (error + "mc-rmail-summary-decrypt-message called in inappropriate buffer")) + (save-excursion + (set-buffer rmail-buffer) + (mc-decrypt))) + +(defun mc-rmail-summary-snarf-keys () + "*Adds keys from current message to public key ring" + (interactive) + (if (not (eq major-mode 'rmail-summary-mode)) + (error + "mc-rmail-summary-snarf-keys called in inappropriate buffer")) + (save-excursion + (set-buffer rmail-buffer) + (mc-snarf))) + +(defun mc-rmail-verify-signature () + "*Verify the signature in the current message." + (interactive) + (if (not (equal mode-name "RMAIL")) + (error "mc-rmail-verify-signature called in a non-RMAIL buffer")) + ;; Hack to load rmailkwd before verifying sig + (rmail-add-label "verified") + (rmail-kill-label "verified") + (if (mc-verify-signature) + (rmail-add-label "verified"))) + +(defun mc-rmail-decrypt-message () + "*Decrypt the contents of this message" + (interactive) + (let (decryption-result) + (if (not (equal mode-name "RMAIL")) + (error "mc-rmail-decrypt-message called in a non-RMAIL buffer")) + (unwind-protect + (progn + (rmail-edit-current-message) + (setq decryption-result (mc-decrypt-message)) + (cond ((not (car decryption-result)) + (rmail-abort-edit)) + ((and (not (eq mc-always-replace 'never)) + (or mc-always-replace + (y-or-n-p + "Replace encrypted message with decrypted? "))) + (rmail-cease-edit) + (rmail-kill-label "edited") + (rmail-add-label "decrypted") + (if (cdr decryption-result) + (rmail-add-label "verified"))) + (t + (let ((tmp (generate-new-buffer "*Mailcrypt Viewing*"))) + (copy-to-buffer tmp (point-min) (point-max)) + (rmail-abort-edit) + (switch-to-buffer tmp t) + (goto-char (point-min)) + (insert "From Mailcrypt-" mc-version " " + (current-time-string) "\n") + (rmail-convert-file) + (rmail-mode) + (use-local-map (copy-keymap (current-local-map))) + (local-set-key "q" 'mc-rmail-view-quit) + (set-buffer-modified-p nil))))) + (if (eq major-mode 'rmail-edit-mode) + (rmail-abort-edit))))) + +(defun mc-rmail-view-quit () + (interactive) + (let ((buf (current-buffer))) + (set-buffer-modified-p nil) + (rmail-quit) + (kill-buffer buf))) + +;;}}} +;;{{{ VM +(defun mc-vm-verify-signature () + "*Verify the signature in the current VM message" + (interactive) + (if (interactive-p) + (vm-follow-summary-cursor)) + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-folder-empty) + (save-restriction + (vm-widen-page) + (mc-verify-signature))) + +(defun mc-vm-decrypt-message () + "*Decrypt the contents of the current VM message" + (interactive) + (let (from-line) + (if (interactive-p) + (vm-follow-summary-cursor)) + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-folder-read-only) + (vm-error-if-folder-empty) + + ;; store away a valid "From " line for possible later use. + (setq from-line (vm-leading-message-separator)) + (vm-edit-message) + (cond ((not (condition-case condition-data + (car (mc-decrypt-message)) + (error + (vm-edit-message-abort) + (error (message "Decryption failed: %s" + (car (cdr condition-data))))))) + (vm-edit-message-abort) + (error "Decryption failed.")) + ((and (not (eq mc-always-replace 'never)) + (or mc-always-replace + (y-or-n-p "Replace encrypted message with decrypted? "))) + (vm-edit-message-end)) + (t + (let ((tmp (generate-new-buffer "*Mailcrypt Viewing*"))) + (copy-to-buffer tmp (point-min) (point-max)) + (vm-edit-message-abort) + (switch-to-buffer tmp t) + (goto-char (point-min)) + (insert from-line) + (set-buffer-modified-p nil) + (vm-mode t)))))) + +(defun mc-vm-snarf-keys () + "*Snarf public key from the contents of the current VM message" + (interactive) + (if (interactive-p) + (vm-follow-summary-cursor)) + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-folder-empty) + (save-restriction + (vm-widen-page) + (mc-snarf-keys))) + +;;}}} +;;{{{ GNUS + +(defun mc-gnus-summary-verify-signature () + (interactive) + (gnus-summary-select-article) + (gnus-eval-in-buffer-window gnus-article-buffer + (save-restriction (widen) (mc-verify-signature)))) + +(defun mc-gnus-summary-snarf-keys () + (interactive) + (gnus-summary-select-article) + (gnus-eval-in-buffer-window gnus-article-buffer + (save-restriction (widen) (mc-snarf-keys)))) + +(defun mc-gnus-summary-decrypt-message () + (interactive) + (gnus-summary-select-article) + (if (or (not (boundp 'gnus-version)) + (not (stringp gnus-version)) + (not (string-match "(ding)" gnus-version))) + (gnus-eval-in-buffer-window gnus-article-buffer + (save-restriction (widen) (mc-decrypt-message))) + ;; (ding) Gnus allows editing of articles in mail groups. + (gnus-eval-in-buffer-window gnus-article-buffer + (gnus-summary-edit-article) + (save-restriction + (widen) + (cond ((not + (condition-case condition-data + (car (mc-decrypt-message)) + (error + (gnus-article-show-summary) + (gnus-summary-show-article) + (error (message "Decryption failed: %s" + (car (cdr condition-data))))))) + (message "Decryption failed.") + (gnus-article-show-summary) + (gnus-summary-show-article)) + + ((and (not (eq mc-always-replace 'never)) + (or mc-always-replace + (y-or-n-p + "Replace encrypted message on disk? "))) + (gnus-summary-edit-article-done)) + + (t + (gnus-article-show-summary))))))) + +;;}}} +;;{{{ MH + +(defun mc-mh-decrypt-message () + "Decrypt the contents of the current MH message in the show buffer." + (interactive "P") + (let* ((msg (mh-get-msg-num t)) + (msg-filename (mh-msg-filename msg)) + (show-buffer (get-buffer mh-show-buffer)) + decrypt-okay decrypt-on-disk) + (setq + decrypt-on-disk + (and (not (eq mc-always-replace 'never)) + (or mc-always-replace + (y-or-n-p "Replace encrypted message on disk? ")))) + (if decrypt-on-disk + (progn + (save-excursion + (set-buffer (create-file-buffer msg-filename)) + (insert-file-contents msg-filename t) + (if (setq decrypt-okay (car (mc-decrypt-message))) + (save-buffer) + (message "Decryption failed.") + (set-buffer-modified-p nil)) + (kill-buffer nil)) + (if decrypt-okay + (if (and show-buffer + (equal msg-filename (buffer-file-name show-buffer))) + (save-excursion + (save-window-excursion + (mh-invalidate-show-buffer))))) + (mh-show msg)) + (mh-show msg) + (save-excursion + (set-buffer mh-show-buffer) + (if (setq decrypt-okay (car (mc-decrypt-message))) + (progn + (goto-char (point-min)) + (set-buffer-modified-p nil)) + (message "Decryption failed."))) + (if (not decrypt-okay) + (progn + (mh-invalidate-show-buffer) + (mh-show msg)))))) + +(defun mc-mh-verify-signature () + "*Verify the signature in the current MH message." + (interactive) + (mh-show) + (mh-in-show-buffer (mh-show-buffer) + (mc-verify-signature))) + + +(defun mc-mh-snarf-keys () + (interactive) + (mh-show) + (mh-in-show-buffer (mh-show-buffer) + (mc-snarf-keys))) + +;;}}} + +;;}}} diff --git a/mkinstalldirs b/mkinstalldirs new file mode 100644 index 0000000..0801ec2 --- /dev/null +++ b/mkinstalldirs @@ -0,0 +1,32 @@ +#! /bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman +# Created: 1993-05-16 +# Last modified: 1994-03-25 +# Public domain + +errstatus=0 + +for file in ${1+"$@"} ; do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d in ${1+"$@"} ; do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" 1>&2 + mkdir "$pathcomp" || errstatus=$? + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus + +# mkinstalldirs ends here diff --git a/records-dindex.el b/records-dindex.el new file mode 100644 index 0000000..09ff21e --- /dev/null +++ b/records-dindex.el @@ -0,0 +1,147 @@ +;;; +;;; records-dindex.el +;;; +;;; $Id: records-dindex.el,v 1.3 1999/04/14 17:16:51 ashvin Exp $ +;;; +;;; Copyright (C) 1996 by Ashvin Goel +;;; +;;; This file is under the Gnu Public License. + +(defvar records-dindex-buffer nil + "The name of the date-index buffer. +Initialized when the records date index file is loaded.") + +(defun records-dindex-buffer (&optional modified) + "Initialize the records date-index buffer from the date-index file. +If needed, create the date-index file. +If modified is t, check the file modification time since being visited." + (if (and records-dindex-buffer + (get-buffer records-dindex-buffer)) + (if (and modified + (not (verify-visited-file-modtime + (get-buffer records-dindex-buffer)))) + ;; revert buffer since it has been modified under you. + (save-excursion + (set-buffer records-dindex-buffer) + (revert-buffer))) + ;; get the dindex file + (setq records-dindex-buffer (buffer-name + (find-file-noselect records-dindex-file))) + (save-excursion + (set-buffer records-dindex-buffer) + (setq buffer-read-only t)))) + +(defun records-dindex-save-buffer () + "Save the records date-index buffer." + (records-index-save-buffer records-dindex-buffer)) + +(defun records-dindex-goto-date (date &optional no-error modified) + "Goto the date in the date-index file. +If no-error is nil, raise error if date doesn't exist. +if no-error is t, return nil if (date, tag) doesn't exist and +place point on the smallest date greater than the argument date." + (records-dindex-buffer modified) + (set-buffer records-dindex-buffer) + (goto-char (point-max)) + ;; first check if date exists + ;; start from end since we assume that + ;; the last dates are most frequently used. + (if (re-search-backward (records-date-count-regexp date) (point-min) t) + ;; found + (progn + (goto-char (match-beginning 0)) + (list + ;; date + (buffer-substring-no-properties (match-beginning 1) (match-end 1)) + ;; count + (buffer-substring-no-properties (match-beginning 2) (match-end 2))) + ) + ;; date not found + (if (null no-error) + (error "records-dindex-goto-date: date " date " not found.")) + ;; search linearly and place point on next date + ;; search is done from end because we assume that + ;; the last dates are most frequently used. + (let ((ndate (records-normalize-date date))) + (while ;; a do-while loop + (let* ((curr-date-count (records-dindex-goto-prev-date)) + (curr-ndate + (if curr-date-count + (records-normalize-date (nth 0 curr-date-count))))) + (and curr-date-count + (records-ndate-lessp ndate curr-ndate)))) + (records-dindex-goto-next-date) + nil))) + +(defun records-dindex-goto-prev-date (&optional arg) + "Goto the arg^th previous date in the date-index buffer. +Return the previous date or nil if the previous date doesn't exist." + (if (re-search-backward (records-date-count-regexp) (point-min) t arg) + ;; found + (progn + (list + ;; date + (buffer-substring-no-properties (match-beginning 1) (match-end 1)) + ;; count + (buffer-substring-no-properties (match-beginning 2) (match-end 2)))))) + +(defun records-dindex-goto-next-date (&optional arg) + "Goto the arg^th next date in the date-index buffer. +Return the next date or nil if the next date doesn't exist." + ;; or else go to the next date + (if (looking-at (records-date-count-regexp)) + (goto-char (match-end 0))) + (if (re-search-forward (records-date-count-regexp) (point-max) t arg) + (progn + (goto-char (match-beginning 0)) + (list + ;; date + (buffer-substring-no-properties (match-beginning 1) (match-end 1)) + ;; count + (buffer-substring-no-properties (match-beginning 2) (match-end 2)))))) + +(defun records-dindex-goto-relative-date(&optional arg date) + "Invoke records-dindex-goto-prev-date or records-dindex-goto-next-date +depending on whether arg is negative or positive." + (let ((date-count (if date (records-dindex-goto-date date t)))) + (cond ((null arg) date-count) + ((= arg 0) date-count) + ((< arg 0) + (records-dindex-goto-prev-date (- arg))) + ((> arg 0) + (records-dindex-goto-next-date arg))))) + +(defun records-dindex-insert-record (date) + "Insert a date into the records date-index." + (save-excursion + (let ((date-count (records-dindex-goto-date date t t))) + (setq buffer-read-only nil) + (if date-count + ;; date already exists, bump count + (progn + (goto-char (match-beginning 2)) + (delete-region (match-beginning 2) (match-end 2)) + (insert (int-to-string (1+ (string-to-int (nth 1 date-count)))))) + ;; insert date + zero count + (insert (concat date "#1 "))) + (records-dindex-save-buffer) + (setq buffer-read-only t)))) + +(defun records-dindex-delete-record (date) + "Delete a date from the records date-index." + (save-excursion + (let* ((date-count (records-dindex-goto-date date nil t)) + (count (string-to-int (nth 1 date-count)))) + (setq buffer-read-only nil) + (if (> count 1) + ;; decrement count + (progn + (goto-char (match-beginning 2)) + (delete-region (match-beginning 2) (match-end 2)) + (insert (int-to-string (1- count)))) + ;; remove date + (delete-region (match-beginning 0) (match-end 0))) + (records-dindex-save-buffer) + (setq buffer-read-only t)))) + +(provide 'records-dindex) diff --git a/records-index.el b/records-index.el new file mode 100644 index 0000000..6db453d --- /dev/null +++ b/records-index.el @@ -0,0 +1,306 @@ +;;; +;;; records-index.el +;;; +;;; $Id: records-index.el,v 1.12 1999/04/14 17:16:51 ashvin Exp $ +;;; +;;; Copyright (C) 1996 by Ashvin Goel +;;; +;;; This file is under the Gnu Public License. + +(defvar records-index-use-font-lock t + "* Enable records index fontification.") + +(defvar records-index-font-lock-keywords + '(("^\\(.*\\): " 1 records-bold-face)) + "* Font-lock keywords for records index mode.") + +(defvar records-index-buffer nil + "The name of the index buffer. +Initialized when the records index file is loaded.") + +(defvar records-index-mode-map nil + "Key map for records index mode.") + +(if records-index-mode-map + nil + (setq records-index-mode-map (make-sparse-keymap)) + (define-key records-index-mode-map "\r" 'records-index-goto-link) + (define-key records-index-mode-map "\C-c\C-j" 'records-index-goto-link) + (define-key records-index-mode-map [mouse-2] + 'records-index-goto-mouse-link)) + +;;;###autoload +(defun records-index-mode () + "Records-index-mode with mouse support. +Key bindings are: +\\{records-index-mode-map}" + (interactive) + ;; make read only for user + (setq buffer-read-only t) + (records-index-parse-buffer) + (setq major-mode 'records-index-mode mode-name "records-index") + (use-local-map records-index-mode-map) + (if records-index-use-font-lock + (progn + (eval-when-compile (require 'font-lock)) + ;; (make-local-variable 'font-lock-no-comments) + ;; (setq font-lock-no-comments t) + (make-local-variable 'font-lock-keywords) + (setq font-lock-keywords records-index-font-lock-keywords) + (font-lock-mode 1))) + (run-hooks 'records-index-mode-hooks) + ) + +(defmacro records-index-subject-regexp (&optional subject) + "Regexp matching a subject in the records index file." + ( `(if (, subject) + (concat "^\\(" (, subject) "\\): ") + "^\\(.*\\): "))) + +(defun records-index-parse-buffer () + "Parses the index buffer for records subject completion." + (if (null records-index-buffer) + (setq records-index-buffer (buffer-name (current-buffer)))) + (goto-char (point-min)) + ;; really a 'do' loop + (while (progn + (forward-line 1) + (beginning-of-line) + (and (looking-at (records-index-subject-regexp)) + (intern (buffer-substring-no-properties + (match-beginning 1) (match-end 1)) + records-subject-table) + )))) + +(defun records-index-buffer (&optional modified) + "Initialize the records index buffer from the index file. +If needed, create the records directory and index file. +If modified is t, check the file modification time since being visited." + (if (and records-index-buffer + (get-buffer records-index-buffer)) + (if (and modified + (not (verify-visited-file-modtime + (get-buffer records-index-buffer)))) + ;; revert buffer since it has been modified under you. + (save-excursion + (set-buffer records-index-buffer) + (revert-buffer))) + ;; get the index file + (if (file-exists-p (expand-file-name records-index-file)) + () + ;; see if records directory exists + (if (not (file-directory-p (expand-file-name records-directory))) + ;; create records directory + (make-directory (expand-file-name records-directory) t)) + ;; initialize records index file + (write-region "-*- records-index -*-" nil records-index-file)) + ;; now get the index file + (setq records-index-buffer (buffer-name + (find-file-noselect records-index-file))))) + +(defun records-index-save-buffer (&optional buf) + "Save the records index buffer." + ;; TODO: if this function is a no-op, things will still work. + ;; the index buffers will be read-only, but not saved: modeline "--%*-" + ;; If indexes are changed frequently enough, this function ought to just + ;; mark the index buffers so that they are eventually saved ... + (let ((buf (if buf buf records-index-buffer))) + (if (and buf + (get-buffer buf)) + (save-excursion + (set-buffer buf) + ;; no message would be better + (save-buffer))))) + +(defun records-index-goto-subject (subject &optional switch no-error modified) + "Goto the beginning of subject in the index buffer. +If switch is t, switch to the records index buffer. +If no-error is t and the subject is not found, then +place point at the beginning of the next subject." + (records-index-buffer modified) + (if switch + (switch-to-buffer records-index-buffer t) + (set-buffer records-index-buffer)) + (goto-char (point-min)) + (if (re-search-forward (records-index-subject-regexp subject) (point-max) t) + (goto-char (match-beginning 0)) + (if (null no-error) + (error + (concat "records-index-goto-subject: subject " subject " not found."))) + ;; search linearly until we get the next subject + (while (let (match) ;; a do-while loop + (forward-line 1) + (beginning-of-line) + (setq match (looking-at (records-index-subject-regexp))) + (and match + (string-lessp + (buffer-substring-no-properties + (match-beginning 1) + (match-end 1)) subject)))))) + +(defun records-index-goto-date-tag (date tag &optional no-error) + "Goto the (date, tag) in the index file. +Function assumes that point is at the beginning of the records index subject. +If no-error is nil, raise error if (date, tag) doesn't exist. +if no-error is t, return nil if (date, tag) doesn't exist and +place point on the smallest (date, tag) pair greater than (date, tag)." + ;; first check if (date, tag) exists + (if (re-search-forward (concat date (records-tag tag)) (point-eoln) t) + (progn ;; found + (goto-char (match-beginning 0)) + (list date tag)) + ;; (date, tag) not found + (if (null no-error) + (error "records-index-goto-date-tag: " date " " tag " not found.")) + ;; search linearly and place point on next date + (let ((ndate (records-normalize-date date))) + (while ;; a do-while loop + (let* ((curr-date-tag (records-index-goto-next-date-tag)) + (curr-ndate + (if curr-date-tag + (records-normalize-date (nth 0 curr-date-tag))))) + (and curr-date-tag + (or (records-ndate-lessp curr-ndate ndate) + (and (records-ndate-equalp curr-ndate ndate) + (string-lessp (nth 1 curr-date-tag) tag)))) + ))))) + +(defun records-index-goto-prev-date-tag (&optional arg) + "Goto the arg^th previous (date, tag) in the index buffer. +Return the previous (date, tag) +or nil if the previous (date, tag) doesn't exist." + (if (re-search-backward records-date-tag-regexp (point-boln) t arg) + (progn + (list + ;; date + (buffer-substring-no-properties (match-beginning 1) (match-end 1)) + (if (match-beginning 3) + ;; tag + (buffer-substring-no-properties (match-beginning 3) + (match-end 3)) + ;; empty tag + "")) + ))) + +(defun records-index-goto-next-date-tag (&optional arg) + "Goto the arg^th next (date, tag) in the index buffer. +Return the next (date, tag), +or nil if the next (date, tag) doesn't exist." + (if (looking-at (records-index-subject-regexp)) + ;; go to the front of the subject + (goto-char (match-end 0)) + ;; or else go to the next date + (if (looking-at records-date-tag-regexp) + (goto-char (match-end 0)))) + (if (re-search-forward records-date-tag-regexp (point-eoln) t arg) + (progn + (goto-char (match-beginning 0)) + (list + ;; date + (buffer-substring-no-properties (match-beginning 1) (match-end 1)) + (if (match-beginning 3) + ;; tag + (buffer-substring-no-properties (match-beginning 3) + (match-end 3)) + ;; empty tag + ""))))) + +(defun records-index-goto-relative-date-tag(&optional arg date tag) + "Invoke records-index-goto-prev-date-tag or records-index-goto-next-date-tag +depending on whether arg is negative or positive. +Returns the new (date, tag)." + (let ((date-tag (if date (records-index-goto-date-tag date tag t)))) + (cond ((null arg) date-tag) + ((= arg 0) date-tag) + ((< arg 0) + (records-index-goto-prev-date-tag (- arg))) + ((> arg 0) + (records-index-goto-next-date-tag arg))))) + +(defun records-index-goto-link() + "Go to the link under point in the records index file." + (interactive) + (save-excursion + (skip-chars-backward "0-9#" (point-boln)) + (let (subject date tag) + (if (not (looking-at records-date-tag-regexp)) + ;; didn't find a date, tag + (error "records-index-goto-link: invalid link.") + ;; found date and tag. get subject + (setq date (buffer-substring-no-properties (match-beginning 1) + (match-end 1))) + (setq tag + (if (match-beginning 3) + (buffer-substring-no-properties (match-beginning 3) + (match-end 3)) + "")) + (beginning-of-line) + (if (not (looking-at (records-index-subject-regexp))) + (error "records-index-goto-link: subject not found.")) + (setq subject (buffer-substring-no-properties (match-beginning 1) + (match-end 1))) + (records-goto-record subject date tag t) + )))) + +(defun records-index-goto-mouse-link(e) + (interactive "e") + (mouse-set-point e) + (records-index-goto-link)) + +(defun records-index-insert-subject (subject) + "Insert a record subject into the records index." + (if (looking-at (records-index-subject-regexp subject)) + ;; no insertion required + () + (save-excursion (insert (concat subject ": \n")) + (intern subject records-subject-table)))) + +(defun records-index-delete-subject (subject) + (beginning-of-line) + (if (records-index-goto-next-date-tag) + ;; other guys exist. don't do anything + () + ;; make sure that we are removing the correct subject + (beginning-of-line) + (if (not (looking-at (records-index-subject-regexp subject))) + (error "records-index-delete-subject: invalide subject.") + ;; ask for confirmation + (if (y-or-n-p (concat "Delete subject: " subject " ")) + ;; the 1+ is for the newline + (progn + (delete-region (point) (1+ (point-eoln))) + (unintern subject records-subject-table)))))) + +(defun records-index-insert-record (subject date tag) + "Insert a record into the records index." + (save-excursion + (records-index-goto-subject subject nil t t) + (setq buffer-read-only nil) + (records-index-insert-subject subject) + (if (records-index-goto-date-tag date tag t) + (error (concat "records-index-insert-record: Record " date " " tag + " exists already.")) + ;; now insert + (insert (concat date (records-tag tag) " ")) + (records-index-save-buffer) + (setq buffer-read-only t) + ))) + +(defun records-index-delete-record (subject date tag) + "Delete a record from the records index." + (save-excursion + (records-index-goto-subject subject nil nil t) + (records-index-goto-date-tag date tag) + ;; the 1+ is for the extra space + (setq buffer-read-only nil) + (delete-region (match-beginning 0) (1+ (match-end 0))) + (records-index-delete-subject subject) + (records-index-save-buffer) + (setq buffer-read-only t) + )) + +(put 'records-index 'mode-class 'special) + +(provide 'records-index) +(if (not (featurep 'records)) + (require 'records)) diff --git a/records-load.el b/records-load.el new file mode 100644 index 0000000..e76c9aa --- /dev/null +++ b/records-load.el @@ -0,0 +1,7 @@ +(defmacro cons-unique (mem list) + "Add mem to list if it does not exist in it." + (` (setq (, list) + (if (member (, mem) (, list)) list (cons (, mem) (, list)))))) + +(eval-when-compile (cons-unique (expand-file-name ".") load-path)) +(eval-when-compile (cons-unique (expand-file-name "./encrypt") load-path)) \ No newline at end of file diff --git a/records-util.el b/records-util.el new file mode 100644 index 0000000..dfd13f9 --- /dev/null +++ b/records-util.el @@ -0,0 +1,283 @@ +;;; +;;; records-util.el +;;; +;;; $Id: records-util.el,v 1.5 1999/04/14 17:16:51 ashvin Exp $ +;;; +;;; Copyright (C) 1996 by Ashvin Goel +;;; +;;; This file is under the Gnu Public License. + +(defun records-todo (&optional date) + "Insert the previous record files todo's into the date file. +See the records-todo-.*day variables on when it is automatically invoked." + (interactive) + (if (null date) + (setq date (records-file-to-date))) + (save-excursion + (let* ((date-buf (current-buffer)) + (prev-date (records-goto-prev-record-file 1 t t)) + (cur-buf (current-buffer))) + (if (null prev-date) + () ;; nothing to do + (goto-char (point-min)) + (while (records-goto-down-record nil t) ;; record exists + ;; start the magic + (let* ((subject (nth 0 (records-subject-tag t))) ;; first record + (point-pair (records-record-region)) + (bon-point (first point-pair)) + (eon-point (second point-pair)) + bt-point et-point move subject-inserted) + (goto-char bon-point) + ;; process all the todo's in the current record + (while (re-search-forward records-todo-begin-regexp eon-point 'end) + ;; do the copy/move thing for the current todo + (setq bt-point (match-beginning 0)) + (setq move (match-beginning 2)) + ;; goto end of todo + (if (re-search-forward records-todo-end-regexp eon-point 'end) + (setq et-point (match-end 0)) + (setq et-point (point))) + ;; for move, save the regions in the old file + (if move (setq records-todo-move-regions + (cons (list bt-point et-point) + records-todo-move-regions))) + ;; now copy the region to the new file + (save-excursion + (set-buffer date-buf) + (goto-char (point-max)) + (if (not subject-inserted) + (progn (records-insert-record subject) + (setq subject-inserted t))) + (insert-buffer-substring cur-buf bt-point et-point) + ;; insert an extra newline - this is useful for empty records + (insert "\n"))))) + ;; end of record processing. for todo-move, remove regions from old file + (let ((modified (buffer-modified-p))) + (while records-todo-move-regions + (goto-char (car (car records-todo-move-regions))) + (apply 'delete-region (car records-todo-move-regions)) + ;; do the records-todo-delete-empty-record + (if (and records-todo-delete-empty-record (records-body-empty-p)) + (records-delete-record nil t)) + (setq records-todo-move-regions + (cdr records-todo-move-regions))) + (and (not modified) (buffer-modified-p) (save-buffer))) + )))) + +(defun records-user-name () + "The user name of the records user." + (eval-when-compile (load "mc-pgp")) + (cond ((boundp 'mc-ripem-user-id) + mc-ripem-user-id) + ((boundp 'mc-pgp-user-id) + mc-pgp-user-id) + (t (user-full-name)))) + +(defun records-encrypt-record (arg) + "Encrypt the current record for the current user. +With prefix arg, start the encryption from point to the end of record. +Records encryption requires the mailcrypt and mc-pgp packages." + (interactive "P") + (if (not (fboundp 'mc-pgp-encrypt-region)) + (load "mc-pgp")) + (save-excursion + (let ((point-pair (records-record-region t)) + start) + (if arg (setq start (point)) + (setq start (first point-pair))) + (goto-char start) + ;; sanity check + (if (or (looking-at mc-pgp-msg-begin-line) + (looking-at mc-pgp-signed-begin-line)) + (error "records-encrypt-record: record is already encrypted.")) + (mc-pgp-encrypt-region (list (records-user-name)) + start + (second point-pair) + (records-user-name) nil)))) + +(defun records-decrypt-record () + "Decrypt the current record. +Records decryption requires the mailcrypt and mc-pgp packages." + (interactive) + (if (not (fboundp 'mc-pgp-decrypt-region)) + (load "mc-pgp")) + (save-excursion + (let ((point-pair (records-record-region t))) + (goto-char (first point-pair)) + (if (not (re-search-forward + (concat "\\(" mc-pgp-msg-begin-line "\\|" + mc-pgp-signed-begin-line "\\)") (mark) t)) + (error "records-decrypt-record: record is not encrypted.")) + (mc-pgp-decrypt-region (match-beginning 0) + (second point-pair) + )))) + +(defun records-concatenate-records (num) + "Concatenate the current record with the records on the same subject written +in the last NUM days. Output these records in the records output buffer (see +records-output-buffer). Without prefix arg, prompts for number of days. +An empty string will output the current record only. A negative number +will output all the past records on the subject!!" + (interactive + (list + (if current-prefix-arg (int-to-string current-prefix-arg) + (read-from-minibuffer "Concat records in last N days (default 1): ")))) + (let* ((date (records-file-to-date)) + (subject-tag (records-subject-tag t)) + (subject (nth 0 subject-tag)) + (tag (nth 1 subject-tag)) + (arg (string-to-int num)) + (first-ndate (records-add-date (records-normalize-date date) + (if (= arg 0) -1 (- arg)))) + cur-buf point-pair bon-point eon-point prev-date-tag) + + (if (< arg 0) + (setq first-ndate '(0 0 0))) + ;; erase output buffer if needed + ;; print subject + (save-excursion + (set-buffer (get-buffer-create records-output-buffer)) + (if records-erase-output-buffer + (erase-buffer) + (goto-char (point-max))) + (insert (records-subject-on-concat subject))) + ;; start with current record + (save-excursion + (while ;; do-while loop + (progn + ;; get the current records's buffer, beg-point and end-point. + (setq point-pair (records-record-region t)) + (setq cur-buf (buffer-name)) + (setq bon-point (first point-pair)) + (setq eon-point (second point-pair)) + (goto-char bon-point) + ;; insert the current record into records-output-buffer + (save-excursion + (set-buffer (get-buffer records-output-buffer)) + (goto-char (point-max)) + (insert (records-date-on-concat (concat date (records-tag tag)))) + (insert-buffer-substring cur-buf bon-point eon-point)) + ;; goto the previous record + (setq prev-date-tag (records-goto-prev-record 1 subject date tag t t)) + (setq date (nth 0 prev-date-tag)) + (setq tag (nth 1 prev-date-tag)) + ;; check if this record should be copied + (and prev-date-tag + (records-ndate-lessp first-ndate + (records-normalize-date date)))))) + ;; display/select + (if records-select-buffer-on-concat + (pop-to-buffer (get-buffer records-output-buffer)) + (display-buffer (get-buffer records-output-buffer))))) + +(defun records-concatenate-record-files (num) + "Concatenate all the records in the records files of the last NUM days. +All the records of a subject are collected together. Output these records in the +records output buffer (see records-output-buffer). Without prefix arg, prompts +for number of days. An empty string will output the records of the current file." + (interactive + (list + (if current-prefix-arg (int-to-string current-prefix-arg) + (read-from-minibuffer "Concat records files in last N days (default 1): " + )))) + (let* ((date (records-file-to-date)) + (arg (string-to-int num)) + (first-ndate (records-add-date (records-normalize-date date) + (if (= arg 0) -1 (- arg)))) + records-subject-list) + ;; erase output buffer if needed + (save-excursion + (set-buffer (get-buffer-create records-output-buffer)) + (if records-erase-output-buffer + (erase-buffer) + (goto-char (point-max)))) + (save-excursion + (while ;; loop thru. all files + (progn ;; do-while loop + ;; goto the beginning of the file + (goto-char (point-min)) + ;; loop thru. all records in a file + (while (records-goto-down-record nil t) + (let* ((subject (nth 0 (records-subject-tag t))) + (tag (nth 1 (records-subject-tag t))) + (point-pair (records-record-region t)) + (bon-point (first point-pair)) + (eon-point (second point-pair)) + subject-mark omark record) + ;; get subject-mark + (setq subject-mark (assoc subject records-subject-list)) + (if subject-mark + () + (save-excursion + (set-buffer (get-buffer records-output-buffer)) + ;; make a new marker + (setq omark (point-max-marker)) + (goto-char omark) + ;; insert subject header + (insert-before-markers (records-subject-on-concat subject)) + (goto-char omark) + (insert "\n")) ;; this does a lot of the trick for markers + ;; add subject and new marker to list + (setq subject-mark (list subject omark)) + (setq records-subject-list + (append records-subject-list (list subject-mark)))) + (setq record (buffer-substring bon-point eon-point)) + (save-excursion + (set-buffer (get-buffer records-output-buffer)) + (goto-char (nth 1 subject-mark)) + (insert-before-markers + (records-date-on-concat (concat date (records-tag tag)))) + (insert-before-markers record)) + (goto-char eon-point))) + (setq date (records-goto-prev-record-file 1 t)) + ;; check if this record should be copied + (and date (records-ndate-lessp first-ndate + (records-normalize-date date)))))) + ;; clean up records-subject-list + (while records-subject-list + (set-marker (nth 1 (car records-subject-list)) nil) + (setq records-subject-list (cdr records-subject-list))) + ;; display/select + (if records-select-buffer-on-concat + (pop-to-buffer (get-buffer records-output-buffer)) + (display-buffer (get-buffer records-output-buffer))))) + +(defun records-goto-calendar () + "Goto the calendar date in the current record file." + (interactive) + (let* ((date (records-file-to-date)) + (ndate (records-normalize-date date)) + ;; convert normalized date to calendar date + ;; the day and month are interchanged + (cdate (list (nth 1 ndate) (nth 0 ndate) (nth 2 ndate)))) + (eval-when-compile (require 'calendar)) + (calendar) + (calendar-goto-date cdate))) + +;;;###autoload +(defun records-calendar-to-record () + "Goto the record file corresponding to the calendar date." + (interactive) + (let* ((cdate (calendar-cursor-to-date)) + (ndate (list (nth 1 cdate) (nth 0 cdate) (nth 2 cdate))) + (date (records-denormalize-date ndate))) + (records-goto-record nil date nil nil 'other))) + +;;;###autoload +(defun records-insert-record-region (beg end) + "Insert the region in the current buffer into today's record. +Prompts for subject." + (interactive "r") + (let ((record-body (buffer-substring beg end))) + (records-goto-today) + (goto-char (point-max)) + (records-insert-record nil record-body))) + +;;;###autoload +(defun records-insert-record-buffer () + "Insert the current buffer into today's record. +Prompts for subject." + (interactive) + (records-insert-record-region (point-min) (point-max))) + +(provide 'records-util) diff --git a/records-vars.el b/records-vars.el new file mode 100644 index 0000000..7fbdca8 --- /dev/null +++ b/records-vars.el @@ -0,0 +1,144 @@ +;;; +;;; records-vars.el +;;; +;;; $Id: records-vars.el,v 1.5 1999/04/14 17:16:51 ashvin Exp $ +;;; +;;; Copyright (C) 1996 by Ashvin Goel +;;; +;;; This file is under the Gnu Public License. + +;;; +;;; The next set of variables are accessed by recordsadmin. +;;; Do not set them explicitly since they are set in your +;;; records initilization file (see records-init-file) when recordsadmin +;;; is run. Beware! +;;; + +(defvar records-init-file (concat (getenv "HOME") "/.emacs-records") + "* All the records initialization should be put in this file. +This file is also read by the perl index generator. +If you change this variable, you must change the perl scripts also.") + +(defvar records-directory (concat (getenv "HOME") "/records") + "* Directory under which all records are stored.") + +(defvar records-index-file (concat records-directory "/index") + "* File name in which records subject index is stored.") + +(defvar records-dindex-file (concat records-directory "/dindex") + "* File name in which records date index is stored.") + +(defvar records-directory-structure 1 + "* The directory structure for records files. Its values can be +0 => all records are stored in the variable records-directory. +1 => records are stored by year. +2 => records are stored by year and month.") + +(defvar records-day-order 0 + "* A records file name is composed of a day, month and year. +This variable determines the order of the day in date. +Valid values are 0, 1 or 2 only.") + +(defvar records-month-order 1 + "* A records file name is composed of a day, month and year. +This variable determines the order of the month in date. +Valid values are 0, 1 or 2 only.") + +(defvar records-year-order 2 + "* A records file name is composed of a day, month and year. +This variable determines the order of the month in date. +Valid values are 0, 1 or 2 only.") + +(defvar records-year-length 4 + "* The length of a records file year. Valid values are 2 or 4 only.") + +;;; +;;; You are free to play with these variables. +;;; Use M-x set-variable records-.* (records- followed by completion) +;;; to see all these variables. +;;; +(defvar records-fontify t + "* Enable records fontification.") + +(defvar records-subject-read-only t + "* If t, records subjects are made read-only. +This disables any accidental updates to a records subject. +The down side is that if any part of the subject is copied to a record body, +it is read-only and does not allow editing of that part.") + +(defvar records-bold-face (copy-face 'bold 'records-bold-face) + "* Face to use for records-index-mode and records-mode subjects. +The name of the face and the name of the variable are both the same. +The default face is copied from 'bold.") + +;; todo variables +(defvar records-todo-prev-day nil + "* If t, records-todo is invoked for a new file from records-goto-prev-day. +A file is new if it does not have any records in it. +If nil, records-todo is not invoked. +If not nil and not t, user is asked whether records-todo should be invoked.") +(defvar records-todo-next-day nil + "* If t, records-todo is invoked for a new file from records-goto-next-day. +If nil, records-todo is not invoked. +If not nil and not t, user is asked whether records-todo should be invoked.") +(defvar records-todo-today t + "* If t, records-todo is invoked for a new file from records-goto-today. +If nil, records-todo is not invoked. +If not nil and not t, user is asked whether records-todo should be invoked.") + +(defvar records-todo-begin-copy-regexp "^CTODO: " + "* The beginning of the copy todo is recognized by this regexp.") +(defvar records-todo-begin-move-regexp "^TODO: " + "* The beginning of the move todo is recognized by this regexp.") +(defvar records-todo-end-regexp "^\n\n" + "* The end of both the copy and move todo is recognized by this regexp.") + +(defvar records-todo-delete-empty-record t + "* If t, delete record if it is empty after a todo move. +If nil, don't delete record. +If not nil and not t, ask user about deleting the record.") + +(defvar records-history-length 10 + "* The number of records that are stored in records-history.") + +(defvar records-output-buffer "*RECORDS-OUTPUT*" + "* Contains the output of concatenating records.") + +(defvar records-subject-prefix-on-concat "--- " + "* Prefix prepended to each subject on records concatenation. +See \\[records-concatenate-records\], and \\[records-concatenate-record-files\].") + +(defvar records-subject-suffix-on-concat " ---" + "* Suffix appended to each subject on records concatenation. +See \\[records-concatenate-records\], and \\[records-concatenate-record-files\].") + +(defvar records-date-prefix-on-concat "* " + "* Prefix prepended to each date on records concatenation. +See \\[records-concatenate-records\], and \\[records-concatenate-record-files\].") + +(defvar records-date-suffix-on-concat "" + "* Suffix appended to each date on records concatenation. +See \\[records-concatenate-records\], and \\[records-concatenate-record-files\].") + +(defvar records-select-buffer-on-concat nil + "* If non nil, the records-output-buffer is selected after records are +concatenated by \\[records-concatenate-records\]. +If nil, the records-output-buffer is just displayed.") + +(defvar records-erase-output-buffer nil + "* If non nil, the records-output-buffer is erased, +every time \\[records-concatenate-records\] is invoked. +If nil, the output is appended.") + +(if (boundp 'running-xemacs) + () + (if (string-match "Lucid" (emacs-version)) + (setq running-xemacs t) + (setq running-xemacs nil) + )) + +(provide 'records-vars) + +;;; Local Variables: +;;; generated-autoload-file:"records-load.el" +;;; End: diff --git a/records.el b/records.el new file mode 100644 index 0000000..1852d26 --- /dev/null +++ b/records.el @@ -0,0 +1,919 @@ +;;; +;;; records.el +;;; +;;; $Id: records.el,v 1.21 1999/05/25 02:06:33 ashvin Exp $ +;;; +;;; Copyright (C) 1996 by Ashvin Goel +;;; +;;; This file is under the Gnu Public License. + +(require 'records-vars) +(require 'records-index) +(require 'records-dindex) +(require 'records-util) + +;;; +;;; Internal variables - users shouldn't change +;;; The defvar is for internal documentation. +;;; +(defconst records-version "1.3") + +(defvar records-mode-menu-map nil + "Records Menu Map. Internal variable.") + +(defvar records-subject-table (make-vector 127 0) + "List of subjects for records subject completion. +Reloaded by loading the records-index file.") + +(defvar records-date-regexp "" + "Regexp matching a records date. Internal variable.") + +(defvar records-tag-regexp "" + "Regexp matching a records tag. Internal variable.") + +(defvar records-date-tag-regexp "" + "Regexp matching links in a record index. Internal variable.") + +(defvar records-day-length 0 + "The length of a records file day. Internal variable.") + +(defvar records-month-length 0 + "The length of a records file month. Internal variable.") + +(defvar records-date-length 0 + "The length of a records file date. Internal variable.") + +(defvar records-date-order '(() () ()) + "The order of a records date. Internal variable.") + +(defvar records-date '((day 0 0) (month 0 0) (year 0 0)) + "The second and third values in each sublist are the start point +and the length of each component in a date. Internal variable.") + +(defvar records-todo-begin-regexp "" + "Either the todo copy or move regexp. Internal variable.") + +(defvar records-todo-move-regions nil + "All the regions that have to be removed from the preivous records file. +Internal variable.") + +(defvar records-history nil + "List of records a user has visited. Elements of the list consist of args +to records-goto-record. Internal variable.") + +(defvar records-initialize nil + "Has function records-initialize been invoked atleast once. +Internal variable.") + +;;;###autoload +(defun records-initialize () + "Reads the records init file and sets the records internal variables +like records-date, records-date-length, etc." + (interactive) + (if (file-exists-p records-init-file) + (load records-init-file)) + (setq records-day-length 2) + (setq records-month-length 2) + (setq records-date-length 0) + + ;; set records-date-order + (setcar (nthcdr records-day-order records-date-order) 'day) + (setcar (nthcdr records-month-order records-date-order) 'month) + (setcar (nthcdr records-year-order records-date-order) 'year) + + ;; set records-date + (let ((i 0)) + (mapcar + '(lambda (x) + (let ((len (symbol-value + (intern (concat "records-" (symbol-name x) "-length"))))) + (setcdr (assq x records-date) + (list records-date-length len)) + (setq records-date-length + (+ records-date-length len)) + (setq i (1+ i)))) + records-date-order)) + + (setq records-date-regexp + (concat "\\(" (let ((i 0) regexp) + (while (< i records-date-length) + (setq regexp (concat regexp "[0-9]")) + (setq i (1+ i))) regexp) + "\\)")) + (setq records-tag-regexp "#\\([0-9]+\\|\\)") + (setq records-date-tag-regexp + (concat records-date-regexp "\\(\\|" records-tag-regexp "\\)\\s-")) + + (setq records-todo-begin-regexp + (concat "\\(" records-todo-begin-copy-regexp "\\)\\|\\(" + records-todo-begin-move-regexp "\\)")) + ;; do some cleaning up + (if (and (boundp 'records-dindex-buffer) + records-dindex-buffer + (get-buffer records-dindex-buffer)) + (kill-buffer records-dindex-buffer)) + (if (and (boundp 'records-index-buffer) + records-index-buffer + (get-buffer records-index-buffer)) + (kill-buffer records-index-buffer)) + ) + +;; load when interactive +(if (null noninteractive) + (records-initialize)) + +(defmacro records-date-count-regexp (&optional date) + "Regexp matching a date in the records date-index file." + ( `(if (, date) + (concat "\\(" (, date) "\\)#\\([0-9]+\\) ") + (concat records-date-regexp "#\\([0-9]+\\) ")))) + +(defmacro records-subject-regexp (&optional subject) + "Regexp matching the beginning of a record." + ;; TODO: the underline should be of length(subject) + 2 + ;; not easy to do when subject is nil + (` (if (, subject) + (concat "^\\* \\(" (, subject) "\\)\n\\-\\-\\-+$") + ;; "^\\* \\(.*\\)\n\\-+$" + "^\\* \\(.*\\)\n\\-\\-\\-+$" + ))) + +(defmacro records-subject-on-concat (subject) + "Make subject for records concatenation." + (` (let ((sub (concat records-subject-prefix-on-concat (, subject) + records-subject-suffix-on-concat))) + (concat sub "\n" (make-string (length sub) ?-) "\n")))) + +(defmacro records-date-on-concat (date) + "Make date for records concatenation." + (` (let ((d (concat records-date-prefix-on-concat (, date) + records-date-suffix-on-concat))) + (concat d "\n" (make-string (length d) ?-) "\n")))) + +(defun point-boln () + "Return the boln as a position." + (save-excursion + (beginning-of-line) + (point))) + +(defun point-eoln () + "Return the eoln as a position." + (save-excursion + (end-of-line) + (point))) + +(defun records-date-equalp (date-a date-b) + "Are two dates equal?" + (equal date-a date-b)) + +(defun records-ndate-equalp (ndate-a ndate-b) + "Are two normalized dates equal?" + (equal ndate-a ndate-b)) + +(defun records-ndate-lessp (ndate-a ndate-b) + "Returns T if ndate-a is less than ndate-b." + (or (< (nth 2 ndate-a) (nth 2 ndate-b)) + (and (= (nth 2 ndate-a) (nth 2 ndate-b)) + (< (nth 1 ndate-a) (nth 1 ndate-b))) + (and (= (nth 2 ndate-a) (nth 2 ndate-b)) + (= (nth 1 ndate-a) (nth 1 ndate-b)) + (< (nth 0 ndate-a) (nth 0 ndate-b))))) + +(defun records-add-date (ndate arg) + "Adds (positive or negative) arg days to ndate and +returns new normalized date." + (let ((tmp-ndate ndate) + new-ndate) + ;; bump tmp-ndate + (setcar tmp-ndate (+ arg (car tmp-ndate))) + ;; encode and decode tmp-ndate + (setq new-ndate (nthcdr 3 (decode-time + (apply 'encode-time 0 0 0 tmp-ndate)))) + (setcdr (nthcdr 2 new-ndate) nil) + ;; return new date + new-ndate)) + +(defun records-file-to-date (&optional file-name) + "Get the date associated with file-name. +If file-name is not specified, the current buffers file-name is used." + (if file-name + () + ;; get the file-name of the current buffer + (if (null buffer-file-name) + (error "records-file-to-date: buffer has no associated file.")) + (setq file-name (file-name-nondirectory buffer-file-name))) + ;; check that length of file name is meaningful + (if (= (length file-name) records-date-length) + file-name + (error (concat "records-file-to-date: bad file-name: " file-name)))) + +(defun records-denormalize-date (ndate) + "Get the file name associated with date. +The ndate is normalized and in (day month year) format." + (let ((cdate ndate) + (date (make-string records-date-length ? ))) + (if (= records-year-length 2) + ;; convert to 2 digit year + (if (> (nth 2 cdate) 90) + (setcar (nthcdr 2 cdate) (- (nth 2 cdate) 1900)) + (setcar (nthcdr 2 cdate) (- (nth 2 cdate) 2000)))) + ;; now denormalize + (let ((i 0)) + (mapcar + '(lambda (x) + ;; this is kinda gross + (let ((start (nth 1 x)) + (len (nth 2 x))) + (setq date (concat + (substring date 0 start) + (format (concat "%0" len "d") (nth i ndate)) + (substring date (+ start len)))) + (setq i (1+ i)))) + records-date)) + date)) + +(defun records-normalize-date (date) + "Returns date in (day month year) format with year in four digits" + (let ((ndate '(0 0 0)) + (i 0)) + (mapcar + '(lambda (x) + (setcar (nthcdr i ndate) (string-to-int + (substring date (nth 1 x) + (+ (nth 2 x) (nth 1 x))))) + (setq i (1+ i))) + records-date) + (if (= records-year-length 2) + ;; convert to four digit year + (if (> (nth 2 ndate) 90) + (setcar (nthcdr 2 ndate) (+ (nth 2 ndate) 1900)) + (setcar (nthcdr 2 ndate) (+ (nth 2 ndate) 2000)))) + (copy-sequence ndate))) + +(defun records-directory-path (date &optional absolute) + "Get the relative directory path to a records file. +With absolute set, get the absolute path." + (cond ((= records-directory-structure 0) (if absolute records-directory "")) + ((= records-directory-structure 1) + (concat (if absolute records-directory "..") "/" + (substring date (nth 1 (nth 2 records-date)) + (+ (nth 2 (nth 2 records-date)) + (nth 1 (nth 2 records-date)))))) + ((= records-directory-structure 2) + (concat (if absolute records-directory "../..") "/" + (substring date (nth 1 (nth 2 records-date)) + (+ (nth 2 (nth 2 records-date)) + (nth 1 (nth 2 records-date)))) + "/" + (substring date (nth 1 (nth 1 records-date)) + (+ (nth 2 (nth 1 records-date)) + (nth 1 (nth 1 records-date)))))) + (t + (error "records-directory-path: bad records-directory-structure value")))) + +(defun records-read-subject (&optional subject) + "Read the records subject to be inserted from the minibuffer. +Completion is possible." + (interactive + (progn (records-index-buffer); initializes records-subject-table if required + (list (completing-read "Records subject: " records-subject-table)))) + subject) + +(defun records-add-text-properties (beg end) + "Fontify a records region, make read-only etc. +Look at variables records-fontify and records-subject-read-only. +This function is currently only invoked for a records subject. + +Although the region is read-only, it is possible to edit at the beginning of +the subject. This can mess up a records subject if anything but a newline is +inserted. We could close the beginning of the region (see start-close), but +then users would not be able to add newlines before a subject, and it screws up +records-encrypt-record and records-decrypt-record. What we need is that +insertion of any character automatically inserts a newline also. TODO" + (if records-fontify + (progn + (add-text-properties beg end '(face bold start-open t)) + (if records-subject-read-only + (add-text-properties beg end '(read-only records-subject)))))) + +(defun records-remove-text-properties (s) + "Remove the text properties of string in a record. +Called when killing a region in records mode." + ;; length is probably going to be slow + (remove-text-properties 0 (length s) '(face nil read-only nil) s)) + +(defun records-parse-buffer () + "Parses the records buffer and fontifies record subjects etc." + (save-excursion + (goto-char (point-min)) + ;; goto first record + (if (records-goto-down-record nil t) + (let ((modified (buffer-modified-p)) ;; should always be false + point-pair) + (while (progn;; a do-while loop + (setq point-pair (records-subject-region)) + ;; fontify region, make read-only etc. + (records-add-text-properties (first point-pair) + (second point-pair)) + ;; goto next record - returns nil when no more exist + (records-goto-down-record))) + (and (not modified) (buffer-modified-p) + (set-buffer-modified-p nil)))))) + +(defun records-make-link (subject date tag) + "Make a records link." + (concat "link: <" + (records-directory-path date) "/" + date "#" tag "* " subject ">")) + +(defun records-goto-subject () + "Goto the subject on the current record and return the subject." + (beginning-of-line) + (if (looking-at "^\\s-*-+\\s-*$") + (progn + (previous-line 1) + (beginning-of-line))) + (if (looking-at (records-subject-regexp)) + () + (if (records-goto-up-record) + () + (error "records-goto-subject: no subject found."))) + (buffer-substring-no-properties (match-beginning 1) (match-end 1))) + +(defmacro records-tag (tag) + (` (if (> (length (, tag)) 0) (concat "#" (, tag)) ""))) + +(defun records-subject-tag (&optional no-str) + "Returns subject#tag of the record where point is located. +If no-str is t, return (subject, tag)." + (save-excursion + (let ((subject (records-goto-subject)) + tag) + (next-line 2) + (if (re-search-forward records-tag-regexp (point-eoln) t) + (setq tag (buffer-substring-no-properties + (match-beginning 1) (match-end 1)))) + (if no-str (list subject tag) + (concat subject (records-tag tag)))))) + +(defun records-mark-record (&optional arg) + "Put mark at the end of the current record and point at the beginning +of the record subject. With non-null prefix arg, the point is placed +at the beginning of the record body." + (interactive "P") + (let ((point-pair (records-record-region arg))) + (push-mark (second point-pair) nil t) + (goto-char (first point-pair)))) + +(defun records-record-region (&optional arg) + "Return a pair of points marking the beginning and the end of the current +record. The record marked is the one that contains point or follows +point. With non-null prefix arg, the point is placed at the beginning of the +record body. Note, that the point and the mark in the buffer are not +affected." + (interactive "P") + (save-excursion + (let (begin end) + (if arg (setq begin (second (records-subject-region)))) + (records-goto-down-record) + (setq end (point)) + (if begin t + (records-goto-up-record) + (setq begin (point))) + (list begin end)))) + +(defun records-subject-region () + "Return a pair of points marking the beginning and the end of the current +subject. The record subject marked is the one that contains point or follows +point. Note, that the point and the mark in the buffer are not affected." + (save-excursion + (if (null (records-goto-subject)) + (error "records-subject-region: no subject found.")) + (let ((pt (point))) + (next-line 2) + (beginning-of-line) + (if (looking-at "link: <.*>") + (progn + (next-line 1) + (beginning-of-line))) + ;; return beginning and end of subject + (list pt (point))))) + +(defun records-body-empty-p () + "Is the body of the record under point empty?" + (let ((point-pair (records-record-region t))) + (save-excursion + (goto-char (first point-pair)) + (and (looking-at "\\s-*") + (eq (match-end 0) (second point-pair)))))) + +(defun records-link () + "Returns the records link of the record around the current point." + (save-excursion + (if (null (records-goto-subject)) + (error "records-link: no subject found.")) + (next-line 2) + (beginning-of-line) + (if (looking-at "link: \\(<.*>\\)") + (buffer-substring-no-properties (match-beginning 1) (match-end 1))))) + +(defun records-link-as-kill () + "Put the records link of the record around the current point in the kill +ring." + (interactive) + (kill-new (records-link))) + +(defun records-make-record (subject date tag &optional record-body) + "Make a basic record with it's link name." + (if (not (bolp)) + (insert "\n")) + (let ((opoint (point))) + (insert "* " subject "\n") + (insert-char ?- (+ (length subject) 2)) + (insert (concat "\n" (records-make-link subject date tag) "\n")) + (records-add-text-properties opoint (point)) + (if record-body + (insert record-body)))) + +(defun records-free-record (&optional keep-body) + "Remove the current record. +With arg., keep the body and remove the subject only." + (save-excursion + (let ((inhibit-read-only '(records-subject)) + point-pair) + (if keep-body + (setq point-pair (records-subject-region)) + (setq point-pair (records-record-region))) + (delete-region (first point-pair) (second point-pair)) + (pop-mark)))) + +;;;###autoload +(defun records-underline-line () + "Underline the current line to the length of the line." + ;; check to see if current line is already underlined + ;; remove that underlining first. + (interactive) + (save-excursion + (forward-line 1) + (beginning-of-line) + (if (looking-at "^\\s-*-+\\s-*$") + (kill-line 1))) + ;; now underline the line + (save-excursion + (let ((bol (progn (beginning-of-line) (point))) + (bospaces (progn (skip-chars-forward " \t") (point))) + (eol (progn (end-of-line) (point)))) + (insert "\n" (buffer-substring bol bospaces)) + (insert-char ?- (- eol bospaces))))) + +;; Thanks to Kaarthik Sivakumar, 04/13/99 for the http, ftp, mailto +;; and gopher handling code +(defun records-goto-link () + "Goto the link around point in the records file. +A link can be any of the following. They must be enclosed in <>. +A tag is a number. +1. : a (relative or absolute) pathname +2. +3. +4. file:// or file://localhost prepended any of the three above +5. http:// or mailto:// or ftp:// gopher:// + The last case is handled by browse-url-browser-function. + Refer to Options/\"Open URL with\" in XEmacs. + Spaces and other funky characters in the url can break this code." + (interactive) + (save-excursion + (if (not (or (looking-at "<") + (re-search-backward "<" (point-boln) t))) + ;; not a link I know about + (error "records-goto-link: invalid link under point.")) + ;; try to figure out a link + (cond + ((looking-at (concat "<\\(.*\\)/\\([^/#]+\\)\\(" records-tag-regexp + "\\* \\(.*\\)\\|\\)>")) + ;; found a link + (let ((dir (buffer-substring-no-properties (match-beginning 1) + (match-end 1))) + (date (buffer-substring-no-properties (match-beginning 2) + (match-end 2))) + (tag (if (match-beginning 4) + (buffer-substring-no-properties (match-beginning 4) + (match-end 4)))) + (subject (if (match-beginning 5) + (buffer-substring-no-properties (match-beginning 5) + (match-end 5))))) + (cond + ((string-match "^http:\\|^mailto:\\|^ftp:\\|^gopher:" dir) + (funcall browse-url-browser-function (concat dir "/" date))) + ;; if "file://" or "file://localhost" is present + ;; at the beginning of dir, strip it ... guess why? + (t (if (string-match "^file://\\(localhost\\|\\)" dir) + (setq dir (substring dir (match-end 0)))) + (records-goto-record subject date tag nil nil nil nil dir))))) + ((looking-at "<\\(\\(http\\|mailto\\|ftp\\|gopher\\):[^>]+\\)>") + (funcall browse-url-browser-function + (buffer-substring-no-properties (match-beginning 1) + (match-end 1)))) + (t (error "records-goto-link: invalid link under point."))))) + +(defun records-goto-mouse-link (e) + "Goto the link where mouse is clicked." + (interactive "e") + (mouse-set-point e) + (records-goto-link)) + +(defun records-goto-record (subject date tag + &optional no-hist no-switch todo no-error dir) + "Goto the record on date with subject and tag. +If subject is nil, goto date only. +If no-hist is t, then don't add this link to the records-history list. +If no-switch is t, then do not switch to the new records buffer. +Instead, the buffer is made ready for editing (via set-buffer). +If no-switch is 'other, then switch to the new records buffer in another +window. If todo is t, then invoke records-todo when a record-less file is +being visited. If todo is not nil and not t, ask user whether records-todo +should be called. If no-error is t, do not signal error, if the record is +not found. If dir is specified, then the file is assumed to be \"dir/date\"." + (if (null dir) + (setq dir (records-directory-path date t))) + (let ((file (concat dir "/" date)) + found) + (if (not (file-directory-p dir)) + ;; ask the user if they want to create the directory + (if (y-or-n-p (concat "Make directory: " dir " ")) + (make-directory (expand-file-name dir) t) + (if no-error nil + (error (concat "record: " file " not found."))))) + (cond ((null no-switch) (find-file file)) + ((eq no-switch 'other) (find-file-other-window file)) + (t (set-buffer (find-file-noselect file)))) + ;; handler for new records files + (if (and todo (null (save-excursion (records-dindex-goto-date date t)))) + (if (or (eq todo t) + (y-or-n-p "Invoke records-todo (default n): ")) + (records-todo date))) + + (if (null subject) + ;; this is for going to a specific day and not a record + (setq found t) + (goto-char (point-min)) + ;; TODO: this search forward will fail to get to the right spot + ;; if a string matching this regexp has been added to a + ;; previous subject in the file. We should check for the records subject. + (if (re-search-forward + (concat "^link: <.*" date "#" tag "\\* " subject ">") + (point-max) t) + ;; found + (progn (setq found t) (records-goto-subject)) + (if no-error + nil + (error + (concat "Records subject: " subject + (if (> (length tag) 0) (concat ", tag: " tag)) + " not found."))))) + + ;; support for goto last record + (if (and found (null no-hist)) + (let ((hist (list subject date tag t nil nil nil dir)) + hist-last) + (if (equal hist (car records-history)) + () ;; don't add identical records. + (setq records-history (cons hist records-history)) + (setq hist-last + (nthcdr (- records-history-length 1) records-history)) + (if hist-last + (setcdr hist-last nil))))))) + +(defun records-goto-up-record (&optional subject) + "Go to the beginning of the current record. +If the point is currently at the beginning of a record, go to the record above. +If subject is specified, go up to the beginning of a record with subject." + (interactive) + (re-search-backward (records-subject-regexp subject) (point-min) 'start)) + +(defun records-goto-down-record (&optional subject on-next) + "Go to the beginning of the next record. +If subject is specified, go down to the beginning of a record with subject. +If on-next is t, then don't move if we are at the beginning of a subject." + (interactive) + (let ((regexp (records-subject-regexp subject))) + (if (and (null on-next) + (looking-at regexp)) + (goto-char (match-end 0))) + ;; find next record and leave cursor at section beginning + (if (re-search-forward regexp (point-max) 'end) + (goto-char (match-beginning 0))) + )) + +;;;###autoload +(defun records-goto-index(&optional arg subject date tag no-error) + "If arg is nil or zero, goto the index on date and tag. +With positive arg, go to the index arg-times next to date and tag. +With negative arg, go to the index arg-times previous to date and tag. +Returns the new (date, tag) if found." + (interactive "P") + (if (not (and subject date)) + ;; initialize subject date and tag + (let ((subject-tag (records-subject-tag t))) + (setq subject (nth 0 subject-tag)) + (setq tag (nth 1 subject-tag)) + (setq date (records-file-to-date)))) + (if (records-index-goto-subject subject (interactive-p) no-error) + (records-index-goto-relative-date-tag arg date tag))) + +(defun records-goto-relative-day(&optional arg no-switch todo) + "With positive arg, go arg days ahead of current record's date. +With negative arg, go arg days behind current record's date. +The no-switch and todo arguments are passed to records-goto-record." + (interactive "P") + (let* ((ndate (records-normalize-date (records-file-to-date))) + (new-ndate (records-add-date ndate arg)) + (new-date (records-denormalize-date new-ndate))) + (records-goto-record nil new-date "" nil no-switch todo))) + +(defun records-goto-prev-day(&optional arg no-switch) + "Go to the records file of the previous day. +With numeric prefix arg. go that many days back. +See also records-goto-prev-record-file." + (interactive "P") + (records-goto-relative-day (if arg (- arg) -1) no-switch + records-todo-prev-day)) + +(defun records-goto-next-day(&optional arg no-switch) + "Go to the records file of the next day. +With numeric prefix arg. go that many days forward. +See also records-goto-next-record-file." + (interactive "P") + (records-goto-relative-day (if arg arg 1) no-switch records-todo-next-day)) + +;;;###autoload +(defun records-goto-today () + "Go to the records file of today." + (interactive) + (let ((ndate (nthcdr 3 (decode-time))) + date) + (setcdr (nthcdr 2 ndate) nil) + ;; denormalize the date to get the file name + (setq date (records-denormalize-date ndate)) + (records-goto-record nil date "" nil nil records-todo-today))) + +(defun records-goto-relative-record-file(&optional arg no-switch no-error) + "With positive arg, go arg files ahead of current records file. +With negative arg, go arg files behind of current records file. +Returns the new date." + (interactive "P") + (let ((new-date + (save-excursion + (nth 0 + (records-dindex-goto-relative-date arg + (records-file-to-date)))))) + (if (null new-date) + (if (null no-error) + (error + (concat "records-goto-relative-record-file: " + (if (> arg 0) "next" "previous") + " record file not found."))) + (records-goto-record nil new-date "" nil no-switch)) + new-date)) + +(defun records-goto-prev-record-file(&optional arg no-switch no-error) + "Go to the previous records file. With arg. go that many records files back. +Returns the new date. See also records-goto-prev-day." + (interactive "P") + (records-goto-relative-record-file (if arg (- arg) -1) no-switch no-error)) + +(defun records-goto-next-record-file(&optional arg no-switch no-error) + "Go to the next records file. With arg. go that many records files forward. +Returns the new date. See also records-goto-next-day." + (interactive "P") + (records-goto-relative-record-file (if arg arg 1) no-switch no-error)) + +(defun records-goto-relative-record (&optional arg subject date tag no-switch + no-error) + "If arg is nil or zero, goto the record on subject date and tag. +With positive arg, goto the record arg-times next to date and tag. +With negative arg, goto the record arg-times previous to date and tag. +Returns the new (date, tag) if found." + (interactive "P") + (if (not (and subject date)) + ;; initialize subject date and tag + (let ((subject-tag (records-subject-tag t))) + (setq subject (nth 0 subject-tag)) + (setq tag (nth 1 subject-tag)) + (setq date (records-file-to-date)))) + (let ((date-tag + (save-excursion + (records-goto-index arg subject date tag no-error)))) + (if date-tag + ;; goto the record + (records-goto-record subject (nth 0 date-tag) (nth 1 date-tag) nil + no-switch nil no-error) + (if (null no-error) + (error (concat "records-goto-relative-record: " + (if (> arg 0) "next" "previous") + " record not found.")))) + ;; return value + date-tag)) + +(defun records-goto-prev-record (&optional arg subject date tag + no-switch no-error) + "Find the previous record on subject starting from date and tag. +Returns the new (date, tag) if found." + (interactive "P") + (records-goto-relative-record (if arg (- arg) -1) subject date tag no-switch + no-error)) + +(defun records-goto-next-record (&optional arg subject date tag + no-switch no-error) + "Find the next record on subject starting from date and tag. +Returns the new (date, tag) if found." + (interactive "P") + (records-goto-relative-record (if arg arg 1) subject date tag no-switch + no-error)) + +(defun records-goto-last-record () + "Go back to the last record file visited. +Identical record files are not put in the history consecutively." + (interactive) + (let ((link (car (cdr records-history)))) + (or link + (error "records-goto-last-record: this is the first record.")) + (setq records-history (cdr records-history)) + (apply 'records-goto-record link))) + +(defun records-insert-record(&optional subject record-body) + "Insert a new record for the current date. Asks for the subject." + (interactive) + (let* ((subject (if subject + subject (call-interactively 'records-read-subject))) + (date (records-file-to-date)) + (tag "")) + ;; we don't currently allow a record insertion + ;; if another record with the same subject exists below this record. + (save-excursion + (if (records-goto-down-record subject) + (error + (concat "records-insert-record: can't insert out-of-order record: " + subject)))) + ;; check if another record with same subject exists above + ;; to get a new tag value + (save-excursion + (if (records-goto-up-record subject) + ;; get tag + (setq tag (int-to-string (1+ (string-to-int + (nth 1 (records-subject-tag t)))))))) + + ;; add a records index entry + (records-index-insert-record subject date tag) + + ;; add the date to the date-index + (records-dindex-insert-record date) + + ;; now make the record body + (records-make-record subject date tag record-body))) + +(defun records-delete-record (&optional keep-body no-prompt) + "Delete the current record for the current date. +With arg, removes the subject only." + (interactive "P") + (let* ((date (records-file-to-date)) + (subject-tag (records-subject-tag t)) + (subject (nth 0 subject-tag)) + (tag (nth 1 subject-tag))) + + (if (if no-prompt ;; prompt? + t (y-or-n-p (concat "Delete record: " subject " "))) + (progn + ;; remove the record subject and optionally the body + (records-free-record keep-body) + ;; remove the date from the date-index + (records-dindex-delete-record date) + ;; remove the records index entry + (records-index-delete-record subject date tag))) + )) + +(defun records-rename-record () + "Renames the subject of the current record for the current date." + (interactive) + (records-delete-record 'keep-body) + (records-insert-record)) + +(define-derived-mode records-mode text-mode "Records" + "Enable records-mode for a buffer. Currently, the documentation of this +mode exists in three places: the INSTALL and README files and the menubar! +The install automates most of the things you need to do to use records. Please +use it! After that, look at the file records-vars.el for the customization +variables. + +The key-bindings of this mode are: +\\{records-mode-map}" + + ;; key-bindings + + (define-key records-mode-map "\C-c\C-i" 'records-insert-record) + (define-key records-mode-map "\C-c\C-d" 'records-delete-record) + (define-key records-mode-map "\C-c\C-r" 'records-rename-record) + + (define-key records-mode-map "\M-\C-a" 'records-goto-up-record) + (define-key records-mode-map "\M-\C-e" 'records-goto-down-record) + + (define-key records-mode-map "\C-c\C-p" 'records-goto-prev-record) + (define-key records-mode-map "\C-c\C-n" 'records-goto-next-record) + + (define-key records-mode-map "\C-c\C-y" 'records-goto-prev-day); yesterday + (define-key records-mode-map "\C-c\C-t" 'records-goto-next-day); tomorrow + (define-key records-mode-map "\C-c\C-b" 'records-goto-prev-record-file) + ; back file + (define-key records-mode-map "\C-c\C-f" 'records-goto-next-record-file) + ; front file + + (define-key records-mode-map "\C-c\C-g" 'records-goto-link) + (define-key records-mode-map "\C-c\C-l" 'records-goto-last-record) + (define-key records-mode-map "\C-c\C-j" 'records-goto-index); jump!! + + ;; (define-key records-mode-map [M-S-mouse-1] 'records-goto-mouse-link) + + ;; utility functions have C-c/ prefix keys + (define-key records-mode-map "\C-c/e" 'records-encrypt-record) + (define-key records-mode-map "\C-c/d" 'records-decrypt-record) + + (define-key records-mode-map "\C-c/t" 'records-todo) + (define-key records-mode-map "\C-c/c" 'records-concatenate-records) + (define-key records-mode-map "\C-c/f" 'records-concatenate-record-files) + + + (define-key records-mode-map "\C-c\C-c" 'records-goto-calendar) + (define-key records-mode-map "\C-c\C-k" 'records-link-as-kill) + (define-key records-mode-map [?\C-c ?\C--] 'records-underline-line) + (define-key records-mode-map "\M-\C-h" 'records-mark-record) + (define-key records-mode-map "\C-c\C-z" 'records-initialize);; zap it in + (eval-when-compile (require 'easymenu)) + (if records-mode-menu-map + () + (setq records-mode-menu-map + '(["Today's Record" records-goto-today t] + "--" + ["Up Record" records-goto-up-record t] + ["Down Record" records-goto-down-record t] + "--" + ["Prev Record" records-goto-prev-record t] + ["Next Record" records-goto-next-record t] + "--" + ["Prev Record File" records-goto-prev-record-file t] + ["Next Record File" records-goto-next-record-file t] + "--" + ["Prev Day" records-goto-prev-day t] + ["Next Day" records-goto-next-day t] + "--" + ["Goto Records Link" records-goto-link t] + ["Goto Last Record" records-goto-last-record t] + ["Goto Index" records-goto-index t] + "--" + ["Insert Record" records-insert-record t] + ["Delete Record" records-delete-record t] + ["Rename Record" records-rename-record t] + "--" + ["Get TODO's" records-todo t] + ["Decrypt Record" records-decrypt-record t] + ["Encrypt Record" records-encrypt-record t] + ["Concat Records" records-concatenate-records t] + ["Concat Record Files" records-concatenate-record-files t] + "--" + ["Goto Calendar" records-goto-calendar t] + ["Mark Record" records-mark-record t] + ["Copy Records Link" records-link-as-kill t] + ["Underline Line" records-underline-line t] + "--" + ["Re-Init Records" records-initialize t] + )) + (if running-xemacs + () + (easy-menu-define records-mode-menu-map records-mode-map "Records" + (cons "Records" records-mode-menu-map))) + ) + ;; This code should be run everytime a new records buffer is initialized + (if running-xemacs + (progn + (set-buffer-menubar current-menubar) + (add-submenu nil (cons "Records" records-mode-menu-map)))) + + ;; imenu stuff + (if (locate-library "imenu") + (progn + (require 'imenu) + (make-variable-buffer-local 'imenu-prev-index-position-function) + (make-variable-buffer-local 'imenu-extract-index-name-function) + (setq imenu-prev-index-position-function 'records-goto-up-record) + (setq imenu-extract-index-name-function 'records-subject-tag))) + + (records-parse-buffer) + (make-local-hook 'kill-hooks) + (add-hook 'kill-hooks 'records-remove-text-properties nil t) + (if records-initialize + () + (records-initialize) + (setq records-initialize t)) + (run-hooks 'records-mode-hooks) + ) + +(run-hooks 'records-load-hooks) +(provide 'records) diff --git a/records.info b/records.info new file mode 100644 index 0000000..a2fc50b --- /dev/null +++ b/records.info @@ -0,0 +1,777 @@ +This is Info file records.info, produced by Makeinfo version 1.68 from +the input file records.texi. + +START-INFO-DIR-ENTRY +* Records: (records). Records: personal diary software +END-INFO-DIR-ENTRY + + Records Copyright (C) 1995-1999 Ashvin Goel. + + Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. + + Permission is granted to copy and distribute modified versions of +this manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one, and provided that the privacy +of any reader of the resulting derived work is respected. In particular +is it strictly forbidden to make this documentation available on a World +Wide Web server which deals cookies. However, keeping access statistics +is allowed. + + Permission is granted to copy and distribute translations of this +manual into another language, under the above conditions for modified +versions, except that this permission notice may be stated in a +translation approved by the Free Software Foundation. + + +File: records.info, Node: Top, Next: Location, Prev: (dir), Up: (dir) + +Introduction +************ + + The *records-mode* for emacs allows creation of an online diary. +Users can create records in their diary for different subjects each +day. Records provides linking and indexing facilities so that users can +easily collect their thoughts on a subject over a period of time. For +instance, it is easy to find out whether you were thinking of quitting +school two months back! Records provides a reminder service by +collecting your list of 'to do' things. A simple integration with the +emacs calendar is also available. + +* Menu: + +* Location:: Where to find records. +* Installation:: Installing records. +* Tutorial:: A real short tutorial to get you started. +* Basic Use:: Simple commands for creating your diary. +* Advanced Use:: The more esoteric commands. +* Conclusion:: Conclusions and acknowledgments. +* Variable Index:: Variable Index. +* Concept Index:: Concept and Function Index. +* Key Index:: Key Index. + + +File: records.info, Node: Location, Next: Installation, Prev: Top, Up: Top + +Where to get records +******************** + + You can get the latest version of records from + +`http://www.cse.ogi.edu/~ashvin/software.html' + + +File: records.info, Node: Installation, Next: Tutorial, Prev: Location, Up: Top + +Installation +************ + + The steps below will help you install records. The installation of +records is also documented in the README and the INSTALL file available +with the distribution. The number of steps in the install (or the amount +of text below) seems to be ungodly! Panic not, almost everything in the +install is automated. Much of the text below is only explaining what +the install is doing (or not doing) to your computer! + + The install procedure makes the byte compiled elisp files, reads in +the user settings for records and installs these settings into the +records initialization file `~/.emacs-records'. This initialization +file is read by emacs and the records administration program +(`recordsadmin'). Install also adds the loading of `~/.emacs-records' +into your `~/.emacs'. When you start emacs, all the records defaults +will be automatically loaded. + + 1. Since you are reading this file, you have untarred the gzip'ed + file. The elisp files in the current directory (in which you are + right now) will *not* be installed in any other directory, and + installation will automatically add the current directory to the + load path in emacs. So if you wish to move the records software to + some other emacs-related directory, now is the time to do it. + + 2. Run `./configure' + + This program is provided in the source directory (do not run your + own configure). If you want to specify the directory in which + `recordsadmin' (a Perl script) should be installed, you should run + + `./configure --prefix $HOME' + + Make sure `${bindir}' (generally `${prefix}/bin') in the Makefile + exists and is writable by you. + + 3. Run `make install' (or just `make') + + Make compiles the lisp files and then interactively asks for the + user defaults. Note that the lisp files are not installed anywhere. + + 4. The changes to your system that `make install' does are the + following: + + 1. Adds `~/.emacs-records' (if this file already exists, it + converts only specific parts of the file). + + 2. Adds a couple of lines to `~/.emacs' + + 3. Adds `recordsadmin' to `${bindir}' (if you invoked `make + install'). + + 4. Creates your records directory (`~/records' or whatever you + have specified during `make') and adds some indexes there. If + this directory already exists, then it re-indexes your + records and converts them to the new format. If you don't + trust the conversion, I would suggest that you KEEP A COPY OF + YOUR RECORDS BEFORE DOING THE CONVERSION. Check the + conversion by hand to make sure that the conversion is fine. + Specially look for the following: HAVE EMBEDDED LINKS IN YOUR + RECORDS BEEN CONVERTED CORRECTLY. Please tell me if this + conversion is not okay. + + + 5. Copy the next set of lines from `;;;; records-mode' to `;;;; + records-mode end' into your `~/.emacs'. Make sure that these lines + occur after the following line (which has been automatically added + during install) in your `.emacs'. + + `(load "~/.emacs-records")' + + I did not automate this process since users may wish to change the + key settings. Users will probably use the records-goto-today + function most often. Bind the function to a simple key. + + ;;;; records-mode + ; Define key bindings for functions called from outside records mode + + ; The preferred binding for records-goto-today - uncomment to use it + (define-key global-map [?\C-c ?n] 'records-goto-today) + (define-key global-map [?\C-c ?-] 'records-underline-line) + + ; The Ctrl-x n map + (define-key global-map [?\C-x ?n ?t] 'records-goto-today) + (define-key global-map [?\C-x ?n ?r] 'records-insert-record-region) + (define-key global-map [?\C-x ?n ?b] 'records-insert-record-buffer) + + + ; Hook up to the calendar mode + (add-hook 'calendar-load-hook + (function + (lambda () + (define-key calendar-mode-map "n" + 'records-calendar-to-record)))) + + ;;;*** OPTIONAL ***;;; + + ; If you like abbrev mode + (add-hook 'records-mode-hooks + (function + (lambda () + (abbrev-mode 1)))) + + ; If you want to be brought to today's record on startup + (records-goto-today) + + ;;;; records-mode end + + 6. The installation is complete. Restart your emacs (ya, I know that + you hate to restart emacs because it is your OS. Well you don't + have to restart emacs if you know how to evaluate regions in a + buffer. In this case, evaluate the key bindings and all the hooks + defined above and load in `~/.emacs-records'). Now that the + installation is complete, you are ready to start using records. + First, read the short tutorial (*note Tutorial::.) on getting + started. + + + +File: records.info, Node: Tutorial, Next: Basic Use, Prev: Installation, Up: Top + +A Real Quick Tutorial +********************* + + Here is a real short tutorial on getting started with records. + + 1. Type key `C-c n' (for `records-goto-today'). This will get you + today's record file. If you are starting fresh, it will be empty. + + 2. A record file consists of records, each of which has a "subject" + and a "body". To insert a record, type `C-c C-i' + (`records-insert-record') or look for "Insert Record" in the menu. + Type in a subject in the modeline and press return. The record + subject will be added and you can start typing in the record body. + You can have spaces in subject titles. Look at the FAQ in the + distribution. + + 3. Multiple records with different subjects can be added in each day's + record file. In fact the same subject can be added multiple times + in each day's record file. + + 4. For much of the other functionality, look at the records menu. The + menu has been divided so that most of the functionality at the top + of the menu is related to records traversal (up, down, prev, next, + goto etc). The middle section adds, deletes and renames records. + The last section does administration tasks such as encryption, + concatenation of records by subject or days, etc. Make sure you + look at the records TODO facility (*note Organizing your TODOs::.). + + 5. Remember that if you just have one record file (today's) then + records traversal's are not very useful! + + 6. The program `recordsadmin' has been provided in the distribution + in order to administer your records. You should not need to use + `recordsadmin' in normal records use. `recordsadmin' 1) performs + initialization (or re-initialization) of your records software, 2) + allows changing the date format, 3) changing the directory + structure, and 4) recreation of records indexes if they are broken + (for example if your system crashes while emacs is updating your + indexes). + + + +File: records.info, Node: Basic Use, Next: Advanced Use, Prev: Tutorial, Up: Top + +Basic Use +********* + + This section describes the basic functionality of the records mode. + +* Menu: + +* What is a Record:: How is a record represented? +* Record File:: A set of records arranged by date. +* Record Traversal:: Traversing records. +* Records Index File:: Tracking records. +* Manipulating Records:: Adding, deleting records. +* Misc Commands:: Miscellaneous commands. +* Records Administration:: Administering your records. + + +File: records.info, Node: What is a Record, Next: Record File, Prev: Basic Use, Up: Basic Use + +What is a Record? +================= + + A record is a thought, or an idea, or even a manuscript with a name. +In records-mode, the name of a record is called the "record subject". +The contents of the record are called the "record body". Here, within +double quotes, is an example of a record. + +" +* Work +------ +link: <../../99/04/042199#* Work> + +Didn't do my experiments because I continued working on the records +documentation. Hope to start experiments tomorrow. + +" + + The records subject is `Work'. A subject starts with an asterisk and +a space and is always underlined. If the variable `records-fontify' is +set to true (the default), the subject appears in a bold font (user +variable `records-bold-font'). If the variable +`records-subject-read-only' is set to true (the default), the subject +is read-only, which disallows any accidental updates to a records +subject. + + Below the underlining, a link identifies the record. The link above +shows that this record is associated with the date April 21st, 1999. +Note that there are many ways in which dates can be represented in +records (*note Records Administration::.). + + +File: records.info, Node: Record File, Next: Record Traversal, Prev: What is a Record, Up: Basic Use + +What is a Record File? +====================== + + A record file consists of a set of records associated with a given +date. For instance, a record file for April 21st, 1999 might contain a +`Movies' record and two `Work' records. Simple isn't it! + + +File: records.info, Node: Record Traversal, Next: Records Index File, Prev: Record File, Up: Basic Use + +Record Traversal +================ + + One of the promises made up-front about the records-mode is that it +allows easy linking and indexing of your personal ideas and thoughts. +Records allows traversing records within a record file and across files. + +`records-goto-today' + Key `C-c n'. Go to the records file of today. This is probably the + first function you will be using with records. + +`records-goto-up-record' + Key `C-M-a'. Move to the previous record in the file. + +`records-goto-down-record' + Key `C-M-e'. Move to the next record in the file. + +`records-goto-prev-record' + Key `C-c C-p'. Move to the previous record with the same subject. A + previous same-subject record can be on the same date (when there + are two or more records with the same subject on the same date) or + it is associated with a previous date. Think of all the records on + a given subject as a sequence sorted by date. This function moves + back one step in this sequence. + +`records-goto-next-record' + Key `C-c C-n'. Move to the next record with the same subject. See + `records-goto-prev-record' for more details. + +`records-goto-prev-record-file' + Key `C-c C-b'. Go to the previous record file. Think of all the + record files as a sequence sorted by date. This function moves + back one step in this sequence. + +`records-goto-next-record-file' + Key `C-c C-f'. Move to the next record file. See + `records-goto-prev-record-file' for more details. + +`records-goto-prev-day' + Key `C-c C-y'. Go to the record file of the previous day + (yesterday). If a record file does not exist, one will be created. + +`records-goto-next-day' + Key `C-c C-t'. Go to the record file of the next day (tomorrow). If + a record file does not exist, one will be created. + +`records-goto-link' + Key `C-c C-g'. Goto the link around point in the records file. A + link in a records file must be enclosed within `<' and `>'. This + link can be a records link or a web link such as an http, FTP, + gopher link. A records link identifies a record (*note What is a + Record::.). This record identifier can be copied to any other + record and this function used to jump back to the record + associated with the link. For instance, if you copied this link + <../../99/04/042199#* Work> into some other record and then typed + `C-c C-g' within the angle brackets, you would be transported to + the `Work' record of April 21st, 1999. When the link is a web + link, the link is handed off to `browse-url-browser-function'. + + Note that C-g can be typed only after sometime has elapsed after + typing C-c or else the command gets aborted because of the + behavior of C-g. This is either an emacs bug or this binding ought + to be changed. + +`records-goto-last-record' + Key `C-c C-l'. Go back to the last record file visited. Identical + record files are not put in the history consecutively. + + +File: records.info, Node: Records Index File, Next: Manipulating Records, Prev: Record Traversal, Up: Basic Use + +What is the Records Index File? +=============================== + + A records index file allows the records-mode to keep track of your +records by date. The records index file can be accessed from within any +record by typing `C-c C-j' (`records-goto-index'). Typing the same key +sequence or typing will return you back to the current record. In +the records index file, you can find out the dates on which you have +written something about a subject. You can also type on any +subject and date and jump to the appropriate record. TODO: The index +file currently doesn't provide much functionality. Many more things can +be added here (such as deleting records directly from the index file, +greping within a subject, etc). + + +File: records.info, Node: Manipulating Records, Next: Misc Commands, Prev: Records Index File, Up: Basic Use + +Adding, Deleting and Renaming Records +===================================== + + Records are manipulated with the following commands. + +`records-insert-record' + Key `C-c C-i'. Insert a new record for the current date. + Interactively asks for the subject. Subject completion is + available with the key. + +`records-delete-record' + Key `C-c C-d'. Delete the current record for the current date. + With argument, removes the subject only, so the body is preserved. + If this is the last record on a subject, the user is asked if the + subject should be removed from the index file. + +`records-rename-record' + Key `C-c C-r'. Renames the subject of the current record for the + current date. + + +File: records.info, Node: Misc Commands, Next: Records Administration, Prev: Manipulating Records, Up: Basic Use + +Miscellaneous Commands +====================== + +`records-goto-calendar' + Key `C-c C-c'. Goto the emacs calendar with the cursor on the date + in the current record file. + +`records-mark-record' + Key `C-M-h'. Put mark at the end of the current record and point at + the beginning of the record subject. With non-null prefix + argument, the point is placed at the beginning of the record body. + +`records-link-as-kill' + Key `C-c C-k'. Put the link of the record around the current point + in the kill ring. The link can be retrieved by the `yank' command + (`C-y'). This function can be used to copy a record link to some + other record. + +`records-underline-line' + Key `C-c return'. Underline the current line to the length of the + line. This function is not really specific to records. You can use + it to underline any text. + + +File: records.info, Node: Records Administration, Prev: Misc Commands, Up: Basic Use + +Records Administration +====================== + + Administration of records involves initialization and ensuring data +and index consistency. This job is best done with an external (Perl) +program called `recordsadmin'. The `recordsadmin' program performs the +following jobs: + + 1. Installs or reinstalls user defaults. This is done with + `recordsadmin -i'. The `~/.emacs-records' file is created or + updated. + + 2. Converts one date and directory format to another. This is done + with `recordsadmin -c'. Not everybody likes the same date format + or the same directory format. Many different formats, such as 2/4 + year, mm/dd/yy and dd/mm/yy formats are supported. + + 3. Re-indexes all the records. This is sort of a records fsck that + ensures that the elisp indexing code hasn't blown things. Simply + running `recordsadmin' does the indexing. Run `recordsadmin -h' to + find out the usage. + + + +File: records.info, Node: Advanced Use, Next: Conclusion, Prev: Basic Use, Up: Top + +Advanced Use +************ + + This section describes the advanced records commands. + +* Menu: + +* Organizing your TODOs:: Add TODO's to your records. +* Security:: Securing records with encryption. +* Concatenating Records:: Collecting your past records together. + + +File: records.info, Node: Organizing your TODOs, Next: Security, Prev: Advanced Use, Up: Advanced Use + +Organizing your TODOs +===================== + + The records TODO allows you to keep a list of things to be done per +record. You start by creating a TODO such as the following inside a +record. + +* Work +------ +link: <../../99/04/042199#* Work> + +" +TODO: +some cool things to do +rotten things to do + + +" + + The piece of text within the double quotes is called a TODO. This +TODO is written on the 21th of April, 1999. Suppose today is the 22th +of April, 1999 and you open up a new record file today with +`records-goto-today'. Then the TODO will be moved to today from the +previous day. If you didn't create a record file on 22th, but created +one on the 23th, the TODO would be moved from the 21th to the 23th. In +general, a TODO for a subject is moved to today from the last time +(day) you wrote a TODO for the subject. You can explicitly move TODOs +with the function `records-todo'. The following points are important: + + 1. There must be a space character after the TODO:" " + + 2. There are two newlines after the TODO text. + + The match for a todo happens as follows: + + `records-todo-begin-move-regexp' + The todo text + `records-todo-end-regexp' + + You can change the regexps if you like. Use `C-h v' to examine the +values of these variables. The reason for the non-intuitive regexp +values is that records is pretty careful before moving text over. In +particular, a single newline does not stop the todo matching. This also +allows multiple TODOs within a record. If you want a todo to be copied +instead of moved, then use CTODO, instead of TODO. Look at +`records-todo-begin-copy-regexp'. + + The variable `records-todo-today' determines whether +`records-goto-today' will do TODO processing. If set to true (the +default), the TODO processing is done. If set to nil, the TODO +processing is not done. If set to any other value, the user is asked +whether TODOs should be processed. The variables +`records-todo-next-day' and `records-todo-prev-day' are similar to +`records-todo-today' but are invoked when traversing to the next or the +previous day. Their defaults are nil. + + When a TODO is moved from a source to a destination record and, as a +result, the source record becomes empty, then the record is deleted if +the variable `records-todo-delete-empty-record' is set to true (the +default). + + +File: records.info, Node: Security, Next: Concatenating Records, Prev: Organizing your TODOs, Up: Advanced Use + +Securing Records With Encryption +================================ + + Consider the `Work' record of April 21st, 1999 (*note What is a +Record::.). That is a record I do not want my advisor to see! In +records-mode, records can be individually encrypted (and signed) using +Philip Zimmerman's PGP (Pretty Good Privacy) and the PGP mailcrypt mode +available with `xemacs'. Since the mailcrypt mode is not available with +`emacs' by default, it is included in the records distribution under +the directory `mailcrypt'. However, this directory will probably not be +in your load-path (*note (xemacs)Loading::). So if you don't have +these packages by default, copy the files in the `mailcrypt' directory +into the top level records directory. + + If you don't have PGP software then you either don't care about +encryption or need to get it so you can use it with records. + +`records-encrypt-record' + Key `C-c / e'. Encrypt the current record for the current user. By + default this function encrypts the whole record. With a prefix + argument, start the encryption from point to the end of record. + The function interactively asks whether the record should be + signed also. + +`records-decrypt-record' + Key `C-c / d'. Decrypt the current record. The function asks for + your PGP passphrase (and caches the passphrase for a short time + (*note (mailcrypt)Passphrase Cache::). + + +File: records.info, Node: Concatenating Records, Prev: Security, Up: Advanced Use + +Putting Records Together +======================== + + The records-mode allows concatenating several records together. These +records are put together and output in a separate emacs buffer. +Concatenation allows combining your thoughts. For instance, you can +write a weekly status report of your work by combining your records on +the `Work' subject for each of the days in the last week. This +ofcourse, assumes that you write what you do everyday! This assumption +is the basis for this software. + + The records-mode allows concatenation of records and record files. +Both are concatenated in date order. + +`records-concatenate-records' + Key `C-c / c'. Concatenate the current record with the records on + the same subject written in the last NUM days. Output these + records in the records output buffer. The name of this buffer is + contained in the variable `records-output-buffer'. Without prefix + argument, prompts for number of days. An empty string will output + the current record only. A negative number will output all the + past records on the subject! + +`records-concatenate-record-files' + Key `C-c / f'. Concatenate all the records in the records files of + the last NUM days. All the records of a subject are collected + together. Output these records in the records output buffer (see + above). Without prefix argument, the function prompts for the + number of days. An empty string will output the records of the + current file. + + The records-mode concatenation related variables are shown below. + +`records-subject-prefix-on-concat' + Prefix prepended to each subject on records concatenation. By + default, "-- ". + +`records-subject-suffix-on-concat' + Suffix appended to each subject on records concatenation. By + default, " --". + +`records-date-prefix-on-concat' + Prefix prepended to each date on records concatenation. By + default, "* " + +`records-date-suffix-on-concat' + Suffix appended to each date on records concatenation. By default, + "". + +`records-select-buffer-on-concat' + If non-nil, the records-output-buffer is selected after records are + concatenated by the concatenation functions. If nil, the + records-output-buffer is just displayed. By default, nil. + +`records-erase-output-buffer' + If non nil, the records-output-buffer is erased, every time the + concatenation functions are invoked. If nil, the output is + appended. By default, nil. + + +File: records.info, Node: Conclusion, Next: Variable Index, Prev: Advanced Use, Up: Top + +Conclusions and Acknowledgments +******************************* + + As promised, the *records-mode* for emacs allows creation of an +online diary with extensive linking and indexing facilities. This diary +has infact become my major desktop tool. I write most of my documents, +small and large, as records distributed over different days and +records-mode does the tracking for me. If you have noticed, records +provides an editor and a browser all in one. You can browse your records +with automatically generated records links, and you can also edit the +contents of your records. + + This software was originally inspired by John Heidemann's notes-mode + +`http://www.isi.edu/~johnh/SOFTWARE/NOTES_MODE/index.html'. This +version enhances the original notes system by adding several features +that John hasn't had time to add. The main addition is that indexing is +done on the fly so that indexes are current as you add or delete new +records. John's notes mode updates indexes in the background (say, +daily) and I found that inconvenient. + + Kaarthik Sivakumar insisted on +documentation and that forced me to write the info pages. + + Please send me mail at if you find this +software useful. + + +File: records.info, Node: Variable Index, Next: Concept Index, Prev: Conclusion, Up: Top + +Variable Index +************** + +* Menu: + +* records-bold-face: What is a Record. +* records-date-prefix-on-concat: Concatenating Records. +* records-date-suffix-on-concat: Concatenating Records. +* records-erase-output-buffer: Concatenating Records. +* records-fontify: What is a Record. +* records-output-buffer: Concatenating Records. +* records-select-buffer-on-concat: Concatenating Records. +* records-subject-prefix-on-concat: Concatenating Records. +* records-subject-read-only: What is a Record. +* records-subject-suffix-on-concat: Concatenating Records. +* records-todo-begin-copy-regexp: Organizing your TODOs. +* records-todo-begin-move-regexp: Organizing your TODOs. +* records-todo-delete-empty-record: Organizing your TODOs. +* records-todo-end-regexp: Organizing your TODOs. +* records-todo-next-day: Organizing your TODOs. +* records-todo-prev-day: Organizing your TODOs. +* records-todo-today: Organizing your TODOs. + + +File: records.info, Node: Concept Index, Next: Key Index, Prev: Variable Index, Up: Top + +Concept and Function Index +************************** + +* Menu: + +* Download: Location. +* Installation: Installation. +* Location: Location. +* records-concatenate-record-files: Concatenating Records. +* records-concatenate-records: Concatenating Records. +* records-decrypt-record: Security. +* records-delete-record: Manipulating Records. +* records-encrypt-record: Security. +* records-goto-calendar: Misc Commands. +* records-goto-down-record: Record Traversal. +* records-goto-index: Records Index File. +* records-goto-last-record: Record Traversal. +* records-goto-link: Record Traversal. +* records-goto-next-day: Record Traversal. +* records-goto-next-record: Record Traversal. +* records-goto-next-record-file: Record Traversal. +* records-goto-prev-day: Record Traversal. +* records-goto-prev-record: Record Traversal. +* records-goto-prev-record-file: Record Traversal. +* records-goto-today: Record Traversal. +* records-goto-up-record: Record Traversal. +* records-insert-record: Manipulating Records. +* records-link-as-kill: Misc Commands. +* records-mark-record: Misc Commands. +* records-rename-record: Manipulating Records. +* records-todo: Organizing your TODOs. +* records-underline-line: Misc Commands. +* TODO: Organizing your TODOs. +* Tutorial: Tutorial. + + +File: records.info, Node: Key Index, Prev: Concept Index, Up: Top + +Key Index +********* + +* Menu: + +* C-c / c: Concatenating Records. +* C-c / d: Security. +* C-c / e: Security. +* C-c / f: Concatenating Records. +* C-c C-b: Record Traversal. +* C-c C-c: Misc Commands. +* C-c C-d: Manipulating Records. +* C-c C-f: Record Traversal. +* C-c C-g: Record Traversal. +* C-c C-i: Manipulating Records. +* C-c C-j: Records Index File. +* C-c C-k: Misc Commands. +* C-c C-l: Record Traversal. +* C-c C-n: Record Traversal. +* C-c C-p: Record Traversal. +* C-c C-r: Manipulating Records. +* C-c C-t: Record Traversal. +* C-c C-y: Record Traversal. +* C-c n: Record Traversal. +* C-c return: Misc Commands. +* C-M-a: Record Traversal. +* C-M-e: Record Traversal. +* C-M-h: Misc Commands. + + + +Tag Table: +Node: Top1187 +Node: Location2348 +Node: Installation2572 +Node: Tutorial7553 +Node: Basic Use9609 +Node: What is a Record10204 +Node: Record File11427 +Node: Record Traversal11781 +Node: Records Index File14849 +Node: Manipulating Records15698 +Node: Misc Commands16535 +Node: Records Administration17519 +Node: Advanced Use18532 +Node: Organizing your TODOs18914 +Node: Security21324 +Node: Concatenating Records22831 +Node: Conclusion25381 +Node: Variable Index26714 +Node: Concept Index27924 +Node: Key Index29773 + +End Tag Table diff --git a/records.ps b/records.ps new file mode 100644 index 0000000000000000000000000000000000000000..7d7bc000651beda4978fb3b243135e4706581873 GIT binary patch literal 147159 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 SzyCcy+xYdbzkmL0<9`A1ID=RK literal 0 HcmV?d00001 diff --git a/recordsadmin.in b/recordsadmin.in new file mode 100644 index 0000000..e87b1d5 --- /dev/null +++ b/recordsadmin.in @@ -0,0 +1,801 @@ +#! @PERL@ -- -*- perl -*- + +### recordsadmin.in +### +### $Id: recordsadmin.in,v 1.11 1999/04/14 17:16:51 ashvin Exp $ +### +### Copyright (C) 1996 by Ashvin Goel +### +### This file is under the Gnu Public License. + +require 5.000; + +use Getopt::Std; +use File::Basename; + +# global - should be set by makefile +$records_init_file="$ENV{HOME}/.emacs-records"; + +$records_emacs_init_file="$ENV{HOME}/.emacs"; + +# external global variables read from .emacs-records +# with their documentation +# initialized here to sane defaults + +@records_external_vars = ( + "records_directory", 1, 'Directory under which all records are stored.', + "records_index_file", 1, 'File name in which records subject index is stored. +Make sure that the file is within the records directory.', + "records_dindex_file", 1, 'File name in which records date index is stored. +Make sure that the file is within the records directory.', + "records_directory_structure", 0, 'The directory structure for records files. +Its values can be +0 => all records are stored in records_directory. +1 => records are stored by year within records_directory. +2 => records are stored by year and month within records_directory.', + "records_day_order", 0, 'A records file name is composed of a day, month and year. +This variable determines the order of the day in date. +For example, day = 0, if you want a records date to be in (day, month, year) +format and day = 2, if you want a records date to be (year, month, day), etc. +Valid values for day are 0, 1 or 2 only.', + "records_month_order", 0, 'A records file name is composed of a day, month and year. +This variable determines the order of the month in date. +For example, month = 1, if you want a records date to be in (day, month, year) +format and month = 0, if you want a records date to be (month, day, year), etc. +Valid values for month are 0, 1 or 2 only.', + "records_year_order", 0, 'A records file name is composed of a day, month and year. +This variable determines the order of the year in date. +For example, year = 2, if you want a records date to be in (day, month, year) +format and year = 0, if you want a records date to be (year, month, day), etc. +Valid values are 0, 1 or 2 only.', + "records_year_length", 0, 'The length of a records file year. +This can be 4 (example 1996) or 2 (96). +Even if it is 2, records will work correctly after the turn of the century. +Valid values are 2 or 4 only.', +); + +$records_directory = "$ENV{HOME}/records"; +$records_index_file = "${records_directory}/index"; +$records_dindex_file = "${records_directory}/dindex"; +$records_directory_structure = 1; +$records_day_order = 0; +$records_month_order = 1; +$records_year_order = 2; +$records_year_length = 4; + +# internal global variables +# initialized in &records_initialize_vars +$records_date_length = 0; +@records_date_order = (); +@records_date = (); +@records_glob_regexp = (); + +# other global variables +$records_day_length = 0; +$records_month_length = 0; +%records_raw_index = (); +%records_date_index = (); +$alternate = 1; # alternate set of variables + +$records_init_mesg = ";;; AUTOMATICALLY GENERATED: DO NOT ALTER OR DELETE FROM THIS LINE ONWARDS"; + +$records_more_init_mesg = <$file")) { + warn("Could not open $file for writing: $!\n"); + # get back old file + rename("$file~", "$file"); + return; + } + print OUT @records_file; + close (OUT); +} + +# returns 1 if the init file has the generated stuff in it or else 0 +sub records_read_init_file { + local($var); + local($init_msg_seen) = 0; + + if (!open(IN, "< $records_init_file")) { + return 0; + } + while() { + if (!$init_msg_seen) { + next unless /$records_init_mesg/; + $init_msg_seen = 1; + } + next if /^\s*$/; + next if /^\s*;/; + if (/\s*\(\s*setq\s+([A-Za-z-]+)\s+([0-9]+)\s*\)/) { + # numeric quantity found + $var = $1; + $val = $2; + $var =~ s/-/_/g; + ${$var} = $val; + next; + } + if (/\s*\(\s*setq\s+([A-Za-z-]+)\s+\"(.*)\"\s*\)/) { + # string quantity found + $var = $1; + $val = $2; + $var =~ s/-/_/g; + ${$var} = $val; + next; + } + } + close(IN); + return $init_msg_seen; +} + +# will take alternate set of variables +sub records_write_init_file { + local($init, $val) = @_; + local($len) = $#records_external_vars; # length of the array + local($i, $var, $value, $str); + + + # if init file exists + # we have to remove the generated stuff from it. + if ($init) { + if (!rename("$records_init_file", "$records_init_file~")) { + warn("Could not rename $records_init_file: $!\n"); + return; + } + if (!open(IN, "< $records_init_file~")) { + die "Couldn't open $records_init_file~: $!\n"; + } + } + if (!open(AP, ">> $records_init_file")) { + die "Couldn't write to $records_init_file: $!\n"; + } + if ($init) { + # get stuff from $records_init_file~ + while() { + if (!/$records_init_mesg/) { + print AP; + next; + } + last; + } + close(IN); + } + print AP $records_init_mesg; + print AP $records_more_init_mesg; + for ($i = 0; $i < $len; $i += 3) { + $var = $records_external_vars[$i]; + $str = $records_external_vars[$i+1]; + $value = ${$var . $val}; + $var =~ s/_/-/g; + if ($str) { + print AP "(setq ", $var, " ", "\"", $value, "\"", ")\n"; + } else { + print AP "(setq ", $var, " ", $value, ")\n"; + } + } + close(AP); +} + +# will take alternate set of variables +# returns 1 if the validate is unsuccessful or else 0 +sub records_validate_vars { + local($val) = @_; + local($ret) = 0; + + local(*records_directory_structure_a) + = \${"records_directory_structure_a" . $val}; + local(*records_day_order_a) = \${"records_day_order" . $val}; + local(*records_month_order_a) = \${"records_month_order" . $val}; + local(*records_year_order_a) = \${"records_year_order" . $val}; + local(*records_year_length_a) = \${"records_year_length" . $val}; + + if ($records_directory_structure_a != 0 && + $records_directory_structure_a != 1 && + $records_directory_structure_a != 2) { + print "records_directory_structure: should have a value of 0, 1 or 2.\n"; + $ret = 1; + } + if ($records_day_order_a != 0 && + $records_day_order_a != 1 && + $records_day_order_a != 2) { + print "records_day_order: should have a value of 0, 1 or 2.\n"; + $ret = 1; + } + if ($records_month_order_a != 0 && + $records_month_order_a != 1 && + $records_month_order_a != 2) { + print "records_month_order: should have a value of 0, 1 or 2.\n"; + $ret = 1; + } + if ($records_year_order_a != 0 && + $records_year_order_a != 1 && + $records_year_order_a != 2) { + print "records_year_order: should have a value of 0, 1 or 2.\n"; + $ret = 1; + } + if (($records_day_order_a == $records_month_order_a) || + ($records_month_order_a == $records_year_order_a) || + ($records_year_order_a == $records_day_order_a)) { + print "records_{day|month|year}_order: should have different values.\n"; + $ret = 1; + } + if (!($records_year_length_a == 2 || $records_year_length_a == 4)) { + print "records_year_length: should have a value of 2 or 4.\n"; + } + return $ret; +} + +# will take alternate set of variables +sub records_initialize_vars { + local($read_init, $val) = @_; + local($i, $dmy, $dmylen); + local($dir_regexp, $file_regexp); + + local(*records_day_length_a) = \${"records_day_length" . $val}; + local(*records_month_length_a) = \${"records_month_length" . $val}; + local(*records_year_length_a) = \${"records_year_length" . $val}; + + local(*records_day_order_a) = \${"records_day_order" . $val}; + local(*records_month_order_a) = \${"records_month_order" . $val}; + local(*records_year_order_a) = \${"records_year_order" . $val}; + + local(*records_date_length_a) = \${"records_date_length" . $val}; + local(*records_date_order_a) = \@{"records_date_order" . $val}; + local(*records_date_a) = \@{"records_date" . $val}; + + local(*records_directory_structure_a) + = \${"records_directory_structure" . $val}; + local(*records_glob_regexp_a) = \@{"records_glob_regexp" . $val}; + + if ($read_init) { + &records_read_init_file(); + } + + $records_day_length_a = 2; + $records_month_length_a = 2; + $records_date_length_a = 0; + @records_date_a = (['year', 0, 0], ['month', 0, 0], ['day', 0, 0]); + + # initialize records_date_order + $records_date_order_a[$records_day_order_a] = 'day'; + $records_date_order_a[$records_month_order_a] = 'month'; + $records_date_order_a[$records_year_order_a] = 'year'; + # initialize records_date + foreach $dmy (@records_date_order_a) { + for ($i = 0; $i < 3; $i++) { + if ($records_date_a[$i][0] eq $dmy) { + $dmylen = ${"records_" . $dmy . "_length_a"}; + $records_date_a[$i][1] = $records_date_length_a; + $records_date_a[$i][2] = $dmylen; + $records_date_length_a += $dmylen; + } + } + } + + # initialize records_glob_regexp + # directory structure + date length considered + if ($records_directory_structure_a == 0) { + $dir_regexp = ''; + } elsif ($records_directory_structure_a == 1) { + for ($i = 0; $i < $records_year_length_a; $i++) { + $dir_regexp .= "[0-9]"; + } + $dir_regexp .= "/"; + } elsif ($records_directory_structure_a == 2) { + for ($i = 0; $i < $records_year_length_a; $i++) { + $dir_regexp .= "[0-9]"; + } + $dir_regexp .= "/[0-9][0-9]/"; + } + for ($i = 0; $i < $records_date_length_a; $i++) { + $file_regexp .= "[0-9]"; + } + @records_glob_regexp_a = ($dir_regexp, $file_regexp); +} + +# will take alternate set of variables +# query the user for values +sub records_query_vars { + local($val) = @_; + + local($len) = $#records_external_vars; # length of the array + local($i); + local($var, $doc, $value); + local($do_validate) = 1; + + while ($do_validate) { + for ($i = 0; $i < $len; $i += 3) { + $var = $records_external_vars[$i]; + $doc = $records_external_vars[$i+2]; + if ($val) { + ${$var . $val} = ${$var}; + } + print $doc, "\n"; + print $var, " (", ${$var}, "): "; + chop($value = <>); + if ($value ne "") { + ${$var . $val} = $value; + } + print "\n"; + } + # validate input + $do_validate = &records_validate_vars($val); + if ($do_validate) { + print "You have input bad values. Try again.\n\n"; + } + } +} + +sub records_find_records { + local(@files); + local($i); + local($dir_regexp); + local($file_regexp); + local($regexp); + + chdir $records_directory || + die "Can't change directory to $records_directory: $!\n"; + @files = glob($records_glob_regexp[0] . $records_glob_regexp[1]); + return @files; +} + +sub records_normalize_date { + local($date) = @_; + local($i); + local(@date); + + for ($i = 0; $i < 3; $i++) { + $date[$i] = substr($date, $records_date[$i][1], $records_date[$i][2]); + } + if (length($date[0]) == 2) { + # normalize year + substr($date[0], 2, 4) = substr($date[0], 0, 2); + if ($date[0] > 90) { + substr($date[0], 0, 2) = "19"; + } else { + substr($date[0], 0, 2) = "20"; + } + } + # year month day + return $date[0] . $date[1] . $date[2]; +} + +# will take alternate set of variables +sub records_denormalize_date { + local($ndate, $val) = @_; + local(*records_date_length_a) = \${"records_date_length" . $val}; + local(*records_date_a) = \@{"records_date" . $val}; + + local($date) = sprintf("%${records_date_length_a}s", " "); + if ($records_date_a[0][2] == 2) { + # denormalize year + substr($date, $records_date_a[0][1], $records_date_a[0][2]) = + substr($ndate, 2, 2); + } else { + substr($date, $records_date_a[0][1], $records_date_a[0][2]) = + substr($ndate, 0, 4); + } + substr($date, $records_date_a[1][1], $records_date_a[1][2]) + = substr($ndate, 4, 2); + substr($date, $records_date_a[2][1], $records_date_a[2][2]) + = substr($ndate, 6, 2); + return $date; +} + +# will take alternate set of variables +# absolute = 0 => relative path from a records file +# = 1 => full absolute path +# = -1 => relative path from the records directory +sub records_directory_path { + local($date, $absolute, $val) = @_; + local(*records_date_a) = \@{"records_date" . $val}; + local(*records_directory_a) = \${"records_directory" . $val}; + local(*records_directory_structure_a) = + \${"records_directory_structure" . $val}; + + if ($records_directory_structure_a == 0) { + return ($absolute >= 1) ? $records_directory_a : ''; + } elsif ($records_directory_structure_a == 1) { + return (($absolute >= 1) ? $records_directory_a . "/" : + ($absolute <= -1) ? '' : "../") . + substr($date, $records_date_a[0][1], $records_date_a[0][2]); + } elsif ($records_directory_structure_a == 2) { + return (($absolute >= 1) ? $records_directory_a . "/" : + ($absolute <= -1) ? '' : "../../") . + substr($date, $records_date_a[0][1], $records_date_a[0][2]) . + "/" . substr($date, $records_date_a[1][1], + $records_date_a[1][2]); + } else { + die "records_directory_path: bad value\n"; + } +} + +sub records_update_file { + local($file) = @_; + local($date) = basename($file); + local($subject); + local($file_modified); + local(@records_file); + + ## variables in the finite state machine + local($regexp, $a_link_regexp, $link_regexp); + local($a_link_found, $link_found); + local($subject_found, $full_subject_found); + local($tag, $tag_generated); + local($state) = 1; + + # TODO: finite state machine diagram. + + if (!open(IN, "< $file")) { + warn("$0: cannot open $file. Skipped.\n"); + return; + } + + while ($state) { + if ($state == 1) { + if (!($_ = )) { + # eof detected + $state = 0; + next; + } + chop; + if (!$a_link_found) { + $state = 2; + } else { + $state = 3; + } + next; + } elsif ($state == 2) { + # look for subject + if (!$subject_found) { + if (/^\* (.*)\s*$/) { + # partial subject was found + # get potential subject name + $subject = $1; + $subject_found = 1; + } + $state = 4; + next; + } + if (!$full_subject_found) { + $regexp = "^-{" . sprintf("%d", length($subject) + 2) . "}"; + if (/$regexp/) { + $full_subject_found = 1; + } else { + # subject is bogus + $subject_found = 0; + } + $state = 4; + next; + } + # found a records subject + $state = 3; + next; + } elsif ($state == 3) { + if (!$tag_generated) { + $tag = records_add_record_to_rawindex($subject, $date); + $tag_generated = 1; + } + # check loose link match + $a_link_regexp = "^(link|prev|next): <.*>"; + if (/$a_link_regexp/) { + $a_link_found = 1; + $link_regexp = "^link: <.*\/" . $date . "#" . $tag . "\\* " . + $subject . ">"; + if (/$link_regexp/) { + $link_found = 1; + $state = 4; + } else { + $file_modified = 1 unless ($file_modified); + $state = 1; + } + next; + } else { + if (!$link_found) { + $file_modified = 1 unless ($file_modified); + push(@records_file, "link: <" . &records_directory_path($date) + . "/" . $date . "#" . $tag . "* " . $subject . ">\n"); + } + $a_link_found = 0; + $link_found = 0; + $subject_found = 0; + $full_subject_found = 0; + $tag_generated = 0; + $state = 2; + next; + } + } elsif ($state == 4) { + push(@records_file, $_ . "\n"); + $state = 1; + next; + } + } + close (IN); + if ($file_modified) { + &records_write_file($file, *records_file); + } else { + print "$file unchanged.\n" if ($verbose); + } +} + +# TODO: finish writing it +# will take alternate set of variables +# after format conversion, update the links in file. +sub records_update_links { + local($file, $val) = @_; + local(@records_file, $file_modified); + local($newdate, $newdir); + + local($link_regexp) = "<(.*)" . $records_glob_regexp[0] . "(" + . $records_glob_regexp[1] . ")(#[0-9]*\\* .*)>"; + + if (!open(IN, "< $file")) { + warn("$0: cannot open $file. Skipped.\n"); + return; + } + while () { + if (!/$link_regexp/) { + push(@records_file, $_); + } else { + $file_modified = 1 unless ($file_modified); + $newdate = &records_denormalize_date(&records_normalize_date($2), + $val); + $newdir = &records_directory_path($newdate, 0, $val); + # TODO: broken + push(@records_file, $` . "<" . $newdir . "/" . $newdate + . $3 . ">" . $'); + } + } + close(IN); + if ($file_modified) { + &records_write_file($file, *records_file); + } else { + print "$file unchanged.\n" if ($verbose); + } +} + +sub records_add_record_to_rawindex { + local($subject, $date) = @_; + local($ndate) = &records_normalize_date($date); + local($key) = $subject . $ndate; + + # add the date and count of number of subjects to the date index + if (!exists($records_date_index{$ndate})) { + $records_date_index{$ndate} = 1; + } else { + $records_date_index{$ndate}++; + } + if (!exists($records_raw_index{$key})) { + $records_raw_index{$key} = 0; + return ''; + } else { + return sprintf("%d", ++$records_raw_index{$key}); + } +} + +sub records_make_index { + local($subject_ndate); + local($subject, $old_subject); + local($ndate, $date); + local($tags, $i); + + if ( -T "$records_index_file" ) { + rename("$records_index_file", "$records_index_file~") || + die("Could not rename $records_index_file: $!\n"); + } + open(INDEX, ">$records_index_file") || + die("Could not open $records_index_file for writing: $!\n"); + # index start up + print INDEX "-*- records-index -*- "; + foreach $subject_ndate (sort keys(%records_raw_index)) { + $subject = substr($subject_ndate, 0, -8); + $ndate = substr($subject_ndate, -8); + $date = &records_denormalize_date($ndate); + $tags = $records_raw_index{$subject_ndate}; + if ($subject ne $old_subject) { + # new subject. write it out + print INDEX "\n", $subject, ": "; + $old_subject = $subject; + } + # write out the first tag + print INDEX $date . " "; + # write out other tags if they exist + for ($i = 1; $i <= $tags; $i++) { + print INDEX $date . "#" . sprintf("%d", $i) . " "; + } + } + print INDEX "\n"; + close(INDEX); +} + +sub records_make_dindex { + local($date, $ndate); + + if ( -T "$records_dindex_file" ) { + rename("$records_dindex_file", "$records_dindex_file~") || + die("Could not rename $records_dindex_file: $!\n"); + } + open(DINDEX, ">$records_dindex_file") || + die("Could not open $records_dindex_file for writing: $!\n"); + print DINDEX " "; + foreach $ndate (sort keys (%records_date_index)) { + $date = &records_denormalize_date($ndate); + print DINDEX $date, "#", $records_date_index{$ndate}, " "; + } + close (DINDEX); +} + +sub records_install_init_file { + local($init) = &records_read_init_file(); + local($val); + if ($init) { + print "You have a valid $records_init_file. +I will use its values to initialize your records. +Unless you know what you are doing, do not change these values. +If you wish to change the date or directory format, use +$0 -c\n\n"; + print "Do you still want to re-initialize your init file (y or n) "; + chop($val= <>); + if ($val eq "n") { + return; + } + } + + print "The default values of the variables are shown in parenthesis. +If you want the default value, press return.\n\n"; + + # query the user for values + &records_query_vars(); + # write out the init file + &records_write_init_file($init); + # Add stuff to emacs init file + open(EIT, ">>$records_emacs_init_file") || + die "Could not open $records_emacs_init_file for writing: $!\n"; + print EIT "(load \"$records_init_file\")\n"; + close EIT; + &mkdirhier($records_directory, 0700) unless ( -d $records_directory); + +} + +sub records_convert_format { + local($file, @records_files, $newfile, $newdir); + local($date, $ndate, $newdate); + + if (!&records_read_init_file()) { + print "Records init file has not been initialized.\n"; + print "Run $0 -i to initialize the init file before +attempting to convert records formats.\n\n"; + exit(1); + } + # original set of variables has been read and initialized + &records_initialize_vars(1); + # read the new set of vars. + print "You must input the new values of the records variables.\n\n"; + &records_query_vars($alternate); + &records_initialize_vars(0, $alternate); + @records_files = &records_find_records(); + foreach $file (@records_files) { + $date = basename($file); + $ndate = &records_normalize_date($date); + $newdate = &records_denormalize_date($ndate, $alternate); + $newdir = &records_directory_path($newdate, 1, $alternate); + $newfile = $newdir . "/" . $newdate; + if ($file eq $newfile) { + die "The old and new variable settings are the same. +We will not go any further!!\n"; + } + &mkdirhier($newdir, 0700) unless ( -d $newdir); + rename($file, $newfile) || + warn "Couldn't rename $file to $newfile: $!\n"; + &records_update_links($newfile, $alternate); + } + &records_write_init_file(1, $alternate); +} + +sub records_recreate_indexes { + local(@records_files); + + &records_initialize_vars(1); + @records_files = &records_find_records(); + + foreach (@records_files) { + &records_update_file($_); + } + &records_make_index(); + &records_make_dindex(); +} +