;;; stolfi-time-stamp.el --- Maintain last change time stamps in files edited by Emacs ;;; Copyright 1989, 1993, 1994, 1995 Free Software Foundation, Inc. ;;; See details at end of file. ;;; Created by J. Stolfi, June 1998 ;;; Last edited on 2024-03-27 09:37:03 by stolfi ;;; Adapted from time-stamp.el of 95/09/21 12:32:56 by Stephen Gildea ;;; Commentary: ;;; If you put a time stamp template anywhere in the first 10 lines of a file, ;;; it can be updated every time you save the file. See the top of ;;; stolfi-time-stamp.el for a sample. The template looks like one of the following: ;;; Time-stamp: <> ;;; Time-stamp: " " ;;; The time stamp is written between the brackets or quotes, resulting in ;;; Time-stamp: <95/01/18 10:20:51 gildea> ;;; Here is an example which puts the file name and time stamp in the binary: ;;; static char *time_stamp = "sdmain.c Time-stamp: <>"; ;;; To activate automatic time stamping in GNU Emacs 19, add this code ;;; to your .emacs file: ;;; (add-hook 'write-file-hooks 'stolfi-time-stamp) ;;; See the documentation for the function `stolfi-time-stamp' for more details. (defvar stolfi-time-stamp-active t "*Non-nil to enable time-stamping of buffers by \\[stolfi-time-stamp]. Can be toggled by \\[stolfi-time-stamp-toggle-active]. See also the variable stolfi-time-stamp-warn-inactive.") (make-variable-buffer-local 'stolfi-time-stamp-active) (defvar stolfi-time-stamp-delete nil "*Non-nil to delete any time stamp line on file write by \\[stolfi=time-stamp]. Can be toggled by \\[stolfi-time-stamp-toggle-delete]. See also the variable stolfi-time-stamp-warn-unstamped.") (make-variable-buffer-local 'stolfi-time-stamp-delete) (defvar stolfi-time-stamp-warn-inactive t "*Non-nil to have \\[stolfi-time-stamp] warn if a buffer did not get time-stamped. A warning is printed if `stolfi-time-stamp-active' is nil and the buffer contains a time stamp template that would otherwise have been updated.") (make-variable-buffer-local 'stolfi-time-stamp-warn-inactive) (defvar stolfi-time-stamp-warn-unstamped nil "*Non-nil to have \\[stolfi-time-stamp] warn if a buffer has no time stamp. A warning is printed if `stolfi-time-stamp-active' is t and the buffer does not contain a time stamp template.") (make-variable-buffer-local 'stolfi-time-stamp-warn-unstamped) (defvar stolfi-time-stamp-initial "Last edited on DATE TIME by USER" "*The string to be inserted by \\[stolfi-time-stamp-insert-new]. It includes the text matched by stolfi-time-stamp-start and stolfi-time-stamp-end, and usually a terminating newline.") (make-variable-buffer-local 'stolfi-time-stamp-initial) (defvar stolfi-time-stamp-format "%04y-%02m-%02d %02H:%02M:%02S by %u" "*Template for the string inserted by \\[stolfi-time-stamp]. Value may be a string or a list. (Lists are supported only for backward compatibility.) A string is used verbatim except for character sequences beginning with %: %a weekday name: `Monday'. %A gives uppercase: `MONDAY' %b month name: `January'. %B gives uppercase: `JANUARY' %d day of month %H 24-hour clock hour %I 12-hour clock hour %m month number %M minute %p `am' or `pm'. %P gives uppercase: `AM' or `PM' %S seconds %w day number of week, Sunday is 0 %y year: `1995' %z time zone name: `est'. %Z gives uppercase: `EST' Non-date items: %% a literal percent character: `%' %f file name without directory %F gives absolute pathname %s system name %u user's login name %h mail host name Decimal digits between the % and the type character specify the field width. Strings are truncated on the right; numbers on the left. A leading zero causes numbers to be zero-filled. For example, to get the format used by the `date' command, use \"%3a %3b %2d %02H:%02M:%02S %Z %y\"") (make-variable-buffer-local 'stolfi-time-stamp-format) (defvar stolfi-time-stamp-line-limit 10 ;Do not change! "Number of lines at the beginning/end of a file that are searched. The patterns `stolfi-time-stamp-start' and `stolfi-time-stamp-end' must be found on one of the first or last `stolfi-time-stamp-line-limit' lines of the file for the file to be stolfi-time-stamped by \\[stolfi-time-stamp]. ") (defvar stolfi-time-stamp-start "\\(^[ ]*\\|[%#]\\|[/][/]\\|[/][*]\\|[;]+\\)[ ]*Last edited on " "*Regexp after which the time stamp is written by \\[stolfi-time-stamp]. See also the variables `stolfi-time-stamp-end' and `stolfi-time-stamp-line-limit'.") (make-variable-buffer-local 'stolfi-time-stamp-start) (defvar stolfi-time-stamp-end "[^A-Za-z0-9]*$" "*Regexp marking the text after the time stamp. \\[stolfi-time-stamp] deletes the text between the first match of `stolfi-time-stamp-start' and the following match of `stolfi-time-stamp-end' on the same line, then writes the time stamp specified by `stolfi-time-stamp-format' between them.") (make-variable-buffer-local 'stolfi-time-stamp-end) (defun stolfi-time-stamp-buffer-setup (act del w-inact w-unst start fmt end initial) "Sets the buffer-local variables used by \\[stolfi-time-stamp]: `act' should be non-nil to activate the updating/deletion of time stamps at write time. `del' should be non-nil to delete time stamps at write time (only if active). `w-inact' should be non-nil to warn of inactive file stamp on write. `w-unst' should be non-nil to warn of missing file stamp on write, if active. `start' should be a regular expression that matches the initial part of the time stamp. `fmt' should be the format for the time stamp data as per . `end' should be a regular expression that matches the end of the time stamp. `initial' should be a string to be inserted on files that lack a time stamp. If any of the arguments `start' `fmt', `end', and `initial' are `nil', the corresponding buffer variable is not changed." (make-local-variable 'stolfi-time-stamp-active) (setq stolfi-time-stamp-active (stolfi-time-stamp-to-bool act)) (make-local-variable 'stolfi-time-stamp-delete) (setq stolfi-time-stamp-delete (stolfi-time-stamp-to-bool del)) (make-local-variable 'stolfi-time-stamp-warn-inactive) (setq stolfi-time-stamp-warn-inactive (stolfi-time-stamp-to-bool w-inact)) (make-local-variable 'stolfi-time-stamp-warn-unstamped) (setq stolfi-time-stamp-warn-unstamped (stolfi-time-stamp-to-bool w-unst)) (make-local-variable 'stolfi-time-stamp-initial) (if initial (setq stolfi-time-stamp-initial initial)) (make-local-variable 'stolfi-time-stamp-start) (if start (setq stolfi-time-stamp-start start)) (make-local-variable 'stolfi-time-stamp-format) (if fmt (setq stolfi-time-stamp-format fmt)) (make-local-variable 'stolfi-time-stamp-end) (if end (setq stolfi-time-stamp-end end)) (add-hook 'local-write-file-hooks 'stolfi-time-stamp) nil ) (defun stolfi-time-stamp-to-bool (x) "Returns `nil' if `x' is `nil' or zero, `t' otherwise." (cond ((null x) nil) ((and (numberp x) (zerop x)) nil) (t t) ) ) (defun stolfi-time-stamp-insert-new () "Inserts the string stolfi-time-stamp-initial at point." (interactive) (insert-string stolfi-time-stamp-initial) nil ) (defun stolfi-time-stamp-find () "Looks for a time stamp within the first N and last N lines of the current buffer, where N = stolfi-time-stamp-line-limit. If found, sets point at the beginning of the time stamp, and returns its end position. If not found, returns nil." (and (stringp stolfi-time-stamp-start) (stringp stolfi-time-stamp-end) (numberp stolfi-time-stamp-line-limit) (or (stolfi-time-stamp-find-at-top) (stolfi-time-stamp-find-at-bottom) ) ) ) (defun stolfi-time-stamp-find-at-top () "Looks for a time stamp within the first N lines of the current buffer. See stolfi-time-stamp-find for details." (let (start end) (setq start (point-min)) (goto-char start) (forward-line stolfi-time-stamp-line-limit) (setq end (point)) (stolfi-time-stamp-find-in-range start end) ) ) (defun stolfi-time-stamp-find-at-bottom () "Looks for a time stamp within the last N lines of the current buffer. See stolfi-time-stamp-find for details." (let (start end) (setq end (point-max)) (goto-char end) (forward-line (- stolfi-time-stamp-line-limit)) (setq start (point)) (stolfi-time-stamp-find-in-range start end) ) ) (defun stolfi-time-stamp-find-in-range (start end) "Looks for a time stamp between positions `start' and `end' See stolfi-time-stamp-find for details." (let ( line-end result (case-fold-search nil) ) (goto-char start) (while (and (not result) (< (point) end) (re-search-forward stolfi-time-stamp-start end 'move) ) (setq start (point)) (end-of-line) (setq line-end (point)) (goto-char start) (if (re-search-forward stolfi-time-stamp-end line-end 'move) (progn (setq result (match-beginning 0)) (goto-char start) ) ) ) result ) ) ;;;###autoload (defun stolfi-time-stamp () "Update the time stamp string in the buffer. If you put a time stamp template anywhere in the first 10 lines of a file, it can be updated every time you save the file. See the top of `stolfi-time-stamp.el' for a sample. The template looks like this: /* Last edited on xxx by xxx */ The time stamp is written between the brackets or quotes, resulting in /* Last edited on 1998-06-24 by stolfi */ Only does its thing if the variable `stolfi-time-stamp-active' is non-nil. If active and `stolfi-time-stamp-delete' is non-nil, deletes the time stamp instead. Typically used on write-file-hooks for automatic time-stamping. The format of the time stamp is determined by the variable `stolfi-time-stamp-format'. The variables `stolfi-time-stamp-line-limit', `stolfi-time-stamp-start', and `stolfi-time-stamp-end' control finding the template." (interactive) (if stolfi-time-stamp-active ; Must update or delete the current time stamp: (save-excursion (save-restriction (widen) (let ( (end (stolfi-time-stamp-find)) ) (if end ; Found time stamp - delete, then maybe insert the updated version: (progn (delete-region (point) end) (if (not stolfi-time-stamp-delete) (insert (stolfi-time-stamp-string)) (progn (stolfi-time-stamp-delete-start-end) (message "Deleting the time stamp.") (sit-for 1) ) ) ) (if (and stolfi-time-stamp-warn-unstamped (not stolfi-time-stamp-delete)) (progn (message "Warning: couldn\'t find the time stamp.") (sit-for 1)) ) ) ) ) ) ; If there is a time stamp, must warn that time stamp is inactive. (save-excursion (save-restriction (if (and stolfi-time-stamp-warn-inactive (stolfi-time-stamp-find) ) (progn (message "Warning: stolfi-time-stamp-active is off, timestamp not changed.") (sit-for 1) ) ) ) ) ) ;; be sure to return nil so can be used on write-file-hooks nil ) (defun stolfi-time-stamp-delete-start-end () "Assumes that the date and user have been deleted at point. Deletes also the surrounding strings matched by `stolfi-time-stamp-start' and `stolfi-time-stamp-end'." (let ( line-start line-end stamp-end ) ; Find start and end of current line: (save-excursion (beginning-of-line) (setq line-start (point)) (end-of-line) (setq line-end (point)) ) ; Find the end of the tail of the time stamp in {stamp-end}: (save-excursion (re-search-forward stolfi-time-stamp-end line-end t) (setq stamp-end (point)) ) ; Move to the start of the head of the time stamp: (save-excursion (goto-char line-start) (re-search-forward stolfi-time-stamp-start line-end 'move) (goto-char (match-beginning 0)) (delete-region (point) stamp-end) ) ) ) ;;;###autoload (defun stolfi-time-stamp-toggle-active (&optional arg) "Toggle stolfi-time-stamp-active, setting whether \\[stolfi-time-stamp] updates or deletes the buffer's time stamp. With arg, turn time stamp updating/deleting on if and only if arg is positive." (interactive "P") (setq stolfi-time-stamp-active (if (null arg) (not stolfi-time-stamp-active) (> (prefix-numeric-value arg) 0) ) ) (message "stolfi-time-stamp is now %s." (if stolfi-time-stamp-active "active" "off")) ) ;;;###autoload (defun stolfi-time-stamp-toggle-delete (&optional arg) "Toggle stolfi-time-stamp-delete, setting whether \\[stolfi-time-stamp] deletes a timestamp when active. With arg, sets deletion on if and only if arg is positive." (interactive "P") (setq stolfi-time-stamp-delete (if (null arg) (not stolfi-time-stamp-delete) (> (prefix-numeric-value arg) 0) ) ) (message "stolfi-time-stamp deletion is now %s." (if stolfi-time-stamp-delete "active" "off")) ) (defun stolfi-time-stamp-string () "Generate the new string to be inserted by \\[stolfi-time-stamp]." (stolfi-time-stamp-strftime stolfi-time-stamp-format) ) (defconst stolfi-time-stamp-month-numbers '(("Jan" . 1) ("Feb" . 2) ("Mar" . 3) ("Apr" . 4) ("May" . 5) ("Jun" . 6) ("Jul" . 7) ("Aug" . 8) ("Sep" . 9) ("Oct" . 10) ("Nov" . 11) ("Dec" . 12)) "Alist of months and their number.") (defconst stolfi-time-stamp-month-full-names ["(zero)" "January" "February" "March" "April" "May" "June" "July" "August" "September" "October" "November" "December"]) (defconst stolfi-time-stamp-weekday-numbers '(("Sun" . 0) ("Mon" . 1) ("Tue" . 2) ("Wed" . 3) ("Thu" . 4) ("Fri" . 5) ("Sat" . 6)) "Alist of weekdays and their number.") (defconst stolfi-time-stamp-weekday-full-names ["Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday"]) (defun stolfi-time-stamp-strftime (format &optional time) "Uses a FORMAT to format date, time, file, and user information. Optional second argument TIME will be used instead of the current time. See the description of the variable `stolfi-time-stamp-format' for a description of the format string." (let ((time-string (cond ((stringp time) time) (time (current-time-string time)) (t (current-time-string)))) (fmt-len (length format)) (ind 0) cur-char (prev-char nil) (result "") field-index field-width field-result (paren-level 0)) (while (< ind fmt-len) (setq cur-char (aref format ind)) (setq result (concat result (cond ((eq cur-char ?%) (setq field-index (1+ ind)) (while (progn (setq ind (1+ ind)) (setq cur-char (if (< ind fmt-len) (aref format ind) ?\0)) (and (<= ?0 cur-char) (>= ?9 cur-char)))) (setq field-width (substring format field-index ind)) ;; eat any additional args to allow for future expansion (while (or (and (<= ?0 cur-char) (>= ?9 cur-char)) (eq ?. cur-char) (eq ?, cur-char) (eq ?: cur-char) (eq ?@ cur-char) (eq ?- cur-char) (eq ?+ cur-char) (eq ?\ cur-char) (eq ?# cur-char) (and (eq ?\( cur-char) (not (eq prev-char ?\\)) (setq paren-level (1+ paren-level))) (if (and (eq ?\) cur-char) (not (eq prev-char ?\\)) (> paren-level 0)) (setq paren-level (1- paren-level)) (and (> paren-level 0) (< ind fmt-len)))) (setq ind (1+ ind)) (setq prev-char cur-char) (setq cur-char (if (< ind fmt-len) (aref format ind) ?\0))) (setq field-result (cond ((eq cur-char ?%) "%") ((or (eq cur-char ?a) ;weekday name (eq cur-char ?A)) (let ((name (aref stolfi-time-stamp-weekday-full-names (cdr (assoc (substring time-string 0 3) stolfi-time-stamp-weekday-numbers))))) (if (eq cur-char ?a) name (upcase name)))) ((or (eq cur-char ?b) ;month name (eq cur-char ?B)) (let ((name (aref stolfi-time-stamp-month-full-names (cdr (assoc (substring time-string 4 7) stolfi-time-stamp-month-numbers))))) (if (eq cur-char ?b) name (upcase name)))) ((eq cur-char ?d) ;day of month, 1-31 (string-to-number (substring time-string 8 10))) ((eq cur-char ?H) ;hour, 0-23 (string-to-number (substring time-string 11 13))) ((eq cur-char ?I) ;hour, 1-12 (let ((hour (string-to-number (substring time-string 11 13)))) (cond ((< hour 1) (+ hour 12)) ((> hour 12) (- hour 12)) (t hour)))) ((eq cur-char ?m) ;month number, 1-12 (cdr (assoc (substring time-string 4 7) stolfi-time-stamp-month-numbers))) ((eq cur-char ?M) ;minute, 0-59 (string-to-number (substring time-string 14 16))) ((or (eq cur-char ?p) ;am or pm (eq cur-char ?P)) (let ((name (if (> 12 (string-to-number (substring time-string 11 13))) "am" "pm"))) (if (eq cur-char ?p) name (upcase name)))) ((eq cur-char ?S) ;seconds, 00-60 (string-to-number (substring time-string 17 19))) ((eq cur-char ?w) ;weekday number, Sunday is 0 (cdr (assoc (substring time-string 0 3) stolfi-time-stamp-weekday-numbers))) ((eq cur-char ?y) ;year (string-to-number (substring time-string -4))) ((or (eq cur-char ?z) ;time zone (eq cur-char ?Z)) (let ((name (if (fboundp 'current-time-zone) (car (cdr (current-time-zone time)))))) (or name (setq name "")) (if (eq cur-char ?z) (downcase name) (upcase name)))) ((eq cur-char ?f) ;buffer-file-name, base name only (if buffer-file-name (file-name-nondirectory buffer-file-name) "(no file)")) ((eq cur-char ?F) ;buffer-file-name, full path (or buffer-file-name "(no file)")) ((eq cur-char ?s) ;system name (system-name)) ((eq cur-char ?u) ;user name (user-login-name)) ((eq cur-char ?h) ;mail host name (stolfi-time-stamp-mail-host-name)) )) (if (string-equal field-width "") field-result (let ((padded-result (format (format "%%%s%c" field-width (if (numberp field-result) ?d ?s)) (or field-result "")))) (let ((initial-length (length padded-result)) (desired-length (string-to-number field-width))) (if (> initial-length desired-length) ;; truncate strings on right, numbers on left (if (stringp field-result) (substring padded-result 0 desired-length) (substring padded-result (- desired-length))) padded-result))))) (t (char-to-string cur-char))))) (setq ind (1+ ind))) result)) (defun stolfi-time-stamp-mail-host-name () "Return the name of the host where the user receives mail. This is the value of `mail-host-address' if bound and a string, otherwise the value of `stolfi-time-stamp-mail-host' (for versions of Emacs before 19.29) otherwise the value of the function system-name. This function may be usefully referenced by `stolfi-time-stamp-format'." (or (and (boundp 'mail-host-address) (stringp mail-host-address) mail-host-address) (and (boundp 'stolfi-time-stamp-mail-host) ;for backward compatibility (stringp stolfi-time-stamp-mail-host) stolfi-time-stamp-mail-host) (system-name))) ;;; Some useful functions to use in stolfi-time-stamp-format ;;; Could generate most of a message-id with ;;; '(stolfi-time-stamp-yymmdd "" stolfi-time-stamp-hhmm "@" stolfi-time-stamp-mail-host-name) ;;; pretty form, suitable for a title page (defun stolfi-time-stamp-month-dd-yyyy () "Return the current date as a string in \"Month DD, YYYY\" form." (let ((date (current-time-string))) (format "%s %d, %s" (aref stolfi-time-stamp-month-full-names (cdr (assoc (substring date 4 7) stolfi-time-stamp-month-numbers))) (string-to-number (substring date 8 10)) (substring date -4)))) ;;; same as __DATE__ in ANSI C (defun stolfi-time-stamp-mon-dd-yyyy () "Return the current date as a string in \"Mon DD YYYY\" form. The first character of DD is space if the value is less than 10." (let ((date (current-time-string))) (format "%s %2d %s" (substring date 4 7) (string-to-number (substring date 8 10)) (substring date -4)))) ;;; RFC 822 date (defun stolfi-time-stamp-dd-mon-yy () "Return the current date as a string in \"DD Mon YY\" form." (let ((date (current-time-string))) (format "%02d %s %s" (string-to-number (substring date 8 10)) (substring date 4 7) (substring date -2)))) ;;; RCS 3 date (defun stolfi-time-stamp-yy/mm/dd () "Return the current date as a string in \"YY/MM/DD\" form." (let ((date (current-time-string))) (format "%s/%02d/%02d" (substring date -2) (cdr (assoc (substring date 4 7) stolfi-time-stamp-month-numbers)) (string-to-number (substring date 8 10))))) ;;; RCS 5 date (defun stolfi-time-stamp-yyyy/mm/dd () "Return the current date as a string in \"YYYY/MM/DD\" form." (let ((date (current-time-string))) (format "%s/%02d/%02d" (substring date -4) (cdr (assoc (substring date 4 7) stolfi-time-stamp-month-numbers)) (string-to-number (substring date 8 10))))) ;;; ISO 8601 date (defun stolfi-time-stamp-yyyy-mm-dd () "Return the current date as a string in \"YYYY-MM-DD\" form." (let ((date (current-time-string))) (format "%s-%02d-%02d" (substring date -4) (cdr (assoc (substring date 4 7) stolfi-time-stamp-month-numbers)) (string-to-number (substring date 8 10))))) (defun stolfi-time-stamp-yymmdd () "Return the current date as a string in \"YYMMDD\" form." (let ((date (current-time-string))) (format "%s%02d%02d" (substring date -2) (cdr (assoc (substring date 4 7) stolfi-time-stamp-month-numbers)) (string-to-number (substring date 8 10))))) (defun stolfi-time-stamp-hh:mm:ss () "Return the current time as a string in \"HH:MM:SS\" form." (substring (current-time-string) 11 19)) (defun stolfi-time-stamp-hhmm () "Return the current time as a string in \"HHMM\" form." (let ((date (current-time-string))) (concat (substring date 11 13) (substring date 14 16)))) (provide 'stolfi-time-stamp) ;; This file 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 file 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. ;;; Change Log: ;;; Originally based on the 19 Dec 88 version of ;;; date.el by John Sturdy . ;;; Version 2, January 1995: replaced functions with %-escapes. ;;; Version 2005-01-16 - increased stolfi-time-stamp-line-limit ;;; from 8 to 10 for the sake of HTML docs. ;;; $Id: stolfi-time-stamp.el,v 1.15 1995/10/31 00:01:15 kwzh Exp $ ;;; stolfi-time-stamp.el ends here