time-stamp: Better handling of some edge cases

* lisp/time-stamp.el (time-stamp-count): Require confirmation if large.
(time-stamp-once): Correctly handle a start regexp matching 0 chars.
* test/lisp/time-stamp-tests.el (time-stamp-custom-start): New test.
This commit is contained in:
Stephen Gildea 2025-02-07 09:17:26 -08:00
parent a62b58648a
commit 280b25e009
2 changed files with 43 additions and 13 deletions

View file

@ -114,7 +114,7 @@ limit yourself to the formats recommended by that older version."
(defcustom time-stamp-active t
"Non-nil to enable time-stamping of buffers by \\[time-stamp].
"Non-nil enables time-stamping of buffers by \\[time-stamp].
Can be toggled by \\[time-stamp-toggle-active].
This option does not affect when `time-stamp' is run, only what it
@ -257,7 +257,7 @@ then instead of changing this variable, include a newline (written as
`time-stamp-count' is best changed with a file-local variable.
If you were to change it in your init file, you would be incompatible
with other people's files.")
;;;###autoload(put 'time-stamp-count 'safe-local-variable 'integerp)
;;;###autoload(put 'time-stamp-count 'safe-local-variable (lambda (c) (and (integerp c) (< c 100))))
(defvar time-stamp-pattern nil ;Do not change!
@ -342,12 +342,11 @@ To enable automatic time-stamping for only a specific file, add
this line to a local variables list near the end of the file:
eval: (add-hook \\='before-save-hook \\='time-stamp nil t)
If the file has no time stamp template, this function does nothing.
If the file has no time stamp template or if `time-stamp-active' is nil,
this function does nothing.
You can set `time-stamp-pattern' in a file's local variables list
to customize the information in the time stamp and where it is written.
The time stamp is updated only if `time-stamp-active' is non-nil."
to customize the information in the time stamp and where it is written."
(interactive)
(let ((line-limit time-stamp-line-limit)
(ts-start time-stamp-start)
@ -421,6 +420,7 @@ The time stamp is updated only if `time-stamp-active' is non-nil."
Returns the end point, which is where `time-stamp' begins the next search."
(let ((case-fold-search nil)
(end nil)
(advance-nudge 0)
end-search-start
(end-length nil))
(save-excursion
@ -430,6 +430,9 @@ Returns the end point, which is where `time-stamp' begins the next search."
(while (and (< (goto-char start) search-limit)
(not end)
(re-search-forward ts-start search-limit 'move))
;; Whether or not we find a template, we must
;; advance through the buffer.
(setq advance-nudge (if (> (point) start) 0 1))
(setq start (point))
(if (not time-stamp-inserts-lines)
(forward-line format-lines))
@ -444,7 +447,8 @@ Returns the end point, which is where `time-stamp' begins the next search."
(if (re-search-forward ts-end line-end t)
(progn
(setq end (match-beginning 0))
(setq end-length (- (match-end 0) end))))))))))))
(setq end-length (- (match-end 0) end)))
(setq start (+ start advance-nudge)))))))))))
(if end
(progn
;; do all warnings outside save-excursion
@ -478,7 +482,7 @@ Returns the end point, which is where `time-stamp' begins the next search."
(setq end (point))))))))))))
;; return the location after this time stamp, if there was one
(and end end-length
(+ end end-length))))
(+ end (max advance-nudge end-length)))))
;;;###autoload

View file

@ -138,6 +138,31 @@
(iter-yield-from (time-stamp-test-pattern-sequential))
(iter-yield-from (time-stamp-test-pattern-multiply)))
(ert-deftest time-stamp-custom-start ()
"Test that `time-stamp' isn't stuck by a start matching 0 characters."
(with-time-stamp-test-env
(with-time-stamp-test-time ref-time1
(let ((time-stamp-pattern "^%Y-%m-%d<-TS")) ;start matches 0 chars
(with-temp-buffer
(insert "\n<-TS\n")
;; we should advance to line 2 and find the template
(time-stamp)
(should (equal (buffer-string) "\n2006-01-02<-TS\n"))))
(let ((time-stamp-pattern "\\b%Y-%m-%d\\b") ;start and end match 0 chars
(time-stamp-count 2))
(with-temp-buffer
(insert "..")
;; the two time stamps should be in different places
(time-stamp)
(should (equal (buffer-string) "2006-01-02..2006-01-02"))))
(let ((time-stamp-pattern "::%S\\_>") ;end matches 0 chars
(time-stamp-count 2))
(with-temp-buffer
(insert "::0::0")
;; the second template should be found immediately after the first
(time-stamp)
(should (equal (buffer-string) "::05::05")))))))
(ert-deftest time-stamp-custom-pattern ()
"Test that `time-stamp-pattern' is parsed correctly."
(iter-do (pattern-parts (time-stamp-test-pattern-all))
@ -246,17 +271,17 @@
(let ((time-stamp-start "TS: <")
(time-stamp-format "%Y-%m-%d")
(time-stamp-count 0) ;changed later in the test
(buffer-expected-once "TS: <2006-01-02>\nTS: <>")
(buffer-expected-twice "TS: <2006-01-02>\nTS: <2006-01-02>"))
(buffer-expected-once "TS: <2006-01-02>TS: <>")
(buffer-expected-twice "TS: <2006-01-02>TS: <2006-01-02>"))
(with-time-stamp-test-time ref-time1
(with-temp-buffer
(insert "TS: <>\nTS: <>")
(insert "TS: <>TS: <>")
(time-stamp)
;; even with count = 0, expect one time stamp
(should (equal (buffer-string) buffer-expected-once)))
(with-temp-buffer
(setq time-stamp-count 1)
(insert "TS: <>\nTS: <>")
(insert "TS: <>TS: <>")
(time-stamp)
(should (equal (buffer-string) buffer-expected-once))
@ -698,7 +723,7 @@
(should (equal (time-stamp-string "%5z" ref-time1) "+0000"))
(let ((time-stamp-time-zone "PST8"))
(should (equal (time-stamp-string "%5z" ref-time1) "-0800")))
(let ((time-stamp-time-zone "HST10"))
(let ((time-stamp-time-zone '(-36000 "HST")))
(should (equal (time-stamp-string "%5z" ref-time1) "-1000")))
(let ((time-stamp-time-zone "CET-1"))
(should (equal (time-stamp-string "%5z" ref-time1) "+0100")))
@ -887,6 +912,7 @@
(should (safe-local-variable-p 'time-stamp-inserts-lines t))
(should-not (safe-local-variable-p 'time-stamp-inserts-lines 17))
(should (safe-local-variable-p 'time-stamp-count 2))
(should-not (safe-local-variable-p 'time-stamp-count 100))
(should-not (safe-local-variable-p 'time-stamp-count t))
(should (safe-local-variable-p 'time-stamp-pattern "a string"))
(should-not (safe-local-variable-p 'time-stamp-pattern 17)))