From 2a5f559b1126ed7abc16d8cb1474c2da74d07360 Mon Sep 17 00:00:00 2001 From: Phil Hazelden Date: Sun, 17 Mar 2024 17:45:45 +0000 Subject: [PATCH 1/4] Allow `ledger-insert-effective-date` to work with regions. It updates or removes the effective date of all transactions that start in the region (technically, that start on a line in the region), without editing any postings. With my credit card, I use the statement date as the effective date. That means I have a long block of transactions that all have the same effective date. This makes it easy to set them all at once. --- ledger-mode.el | 85 +++++++++++++++++++++++++++++++++------------ ledger-reconcile.el | 3 +- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/ledger-mode.el b/ledger-mode.el index cbebbd74..5b6edbb0 100644 --- a/ledger-mode.el +++ b/ledger-mode.el @@ -187,34 +187,75 @@ the balance into that." nil 'noerr) (replace-match "")))))))) -(defun ledger-insert-effective-date (&optional date) +(defun ledger-insert-effective-date (&optional start end date) "Insert effective date `DATE' to the transaction or posting. -If `DATE' is nil, prompt the user a date. +If `DATE' is nil, prompt the user for a date. Replace the current effective date if there's one in the same line. -With a prefix argument, remove the effective date." - (interactive) - (if (and (listp current-prefix-arg) - (= 4 (prefix-numeric-value current-prefix-arg))) - (ledger-remove-effective-date) - (let* ((context (car (ledger-context-at-point))) - (date-string (or date (ledger-read-date "Effective date: ")))) - (save-restriction - (narrow-to-region (line-beginning-position) (line-end-position)) - (cond - ((eq 'xact context) - (beginning-of-line) - (re-search-forward ledger-iso-date-regexp) - (when (= (char-after) ?=) - (ledger-remove-effective-date)) - (insert "=" date-string)) - ((eq 'acct-transaction context) - (end-of-line) - (ledger-remove-effective-date) - (insert " ; [=" date-string "]"))))))) +With a prefix argument, remove the effective date. + +With an active region (`START' and `END' non-nil), insert or +remove for all transactions starting within the region." + (interactive + (if (use-region-p) + (list (region-beginning) (region-end)) + (list nil nil))) + + (if (and start end) (ledger-insert-effective-date-region start end date) + (if (and (listp current-prefix-arg) + (= 4 (prefix-numeric-value current-prefix-arg))) + (ledger-remove-effective-date) + (let* ((context (car (ledger-context-at-point))) + (date-string (or date (ledger-read-date "Effective date: ")))) + (save-restriction + (narrow-to-region (line-beginning-position) (line-end-position)) + (cond + ((eq 'xact context) + (beginning-of-line) + (re-search-forward ledger-iso-date-regexp) + (when (= (char-after) ?=) + (ledger-remove-effective-date)) + (insert "=" date-string)) + ((eq 'acct-transaction context) + (end-of-line) + (ledger-remove-effective-date) + (insert " ; [=" date-string "]")))))))) + +(defun ledger-insert-effective-date-region (start end &optional date) + "Insert effective date `DATE' to all transactions starting within +the region. + +If `DATE' is nil, prompt the user for a date. + +Replace the current effective date if there is one. + +With a prefix argument, remove any effective dates." + (interactive "r") + (let* ((should-remove + (and (listp current-prefix-arg) + (= 4 (prefix-numeric-value current-prefix-arg)))) + (date-string + (unless should-remove + (or date (ledger-read-date "Effective date: "))))) + (save-excursion + (setq end (copy-marker end)) + (goto-char start) + (while (< (point) end) + (let ((context (car (ledger-context-at-point)))) + (save-restriction + (narrow-to-region (line-beginning-position) (line-end-position)) + (when (eq 'xact context) + (if should-remove + (ledger-remove-effective-date) + (beginning-of-line) + (re-search-forward ledger-iso-date-regexp) + (when (= (char-after) ?=) + (ledger-remove-effective-date)) + (insert "=" date-string))))) + (forward-line 1))))) (defun ledger-mode-remove-extra-lines () "Get rid of multiple empty lines." diff --git a/ledger-reconcile.el b/ledger-reconcile.el index db5cf126..5a4c75fd 100644 --- a/ledger-reconcile.el +++ b/ledger-reconcile.el @@ -36,7 +36,8 @@ (require 'ledger-exec) (require 'ledger-navigate) (require 'ledger-state) -(declare-function ledger-insert-effective-date "ledger-mode" (&optional date)) +(declare-function ledger-insert-effective-date "ledger-mode" + (&optional start end date)) (declare-function ledger-read-account-with-prompt "ledger-mode" (prompt)) (declare-function ledger-read-date "ledger-mode" (prompt)) From 1a3289066a8cb92296633cee9bdc3019705b4895 Mon Sep 17 00:00:00 2001 From: Phil Hazelden Date: Tue, 16 Jul 2024 22:40:47 +0100 Subject: [PATCH 2/4] Fix insert-effective-date argument order. Need to keep `date` first for backwards compatibility. Fixes tests. --- ledger-mode.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ledger-mode.el b/ledger-mode.el index 5b6edbb0..bd852428 100644 --- a/ledger-mode.el +++ b/ledger-mode.el @@ -187,7 +187,7 @@ the balance into that." nil 'noerr) (replace-match "")))))))) -(defun ledger-insert-effective-date (&optional start end date) +(defun ledger-insert-effective-date (&optional date start end) "Insert effective date `DATE' to the transaction or posting. If `DATE' is nil, prompt the user for a date. @@ -201,8 +201,8 @@ With an active region (`START' and `END' non-nil), insert or remove for all transactions starting within the region." (interactive (if (use-region-p) - (list (region-beginning) (region-end)) - (list nil nil))) + (list nil (region-beginning) (region-end)) + (list nil nil nil))) (if (and start end) (ledger-insert-effective-date-region start end date) (if (and (listp current-prefix-arg) From 40c484de07a1b9e145451608548f4d333d492a1b Mon Sep 17 00:00:00 2001 From: Phil Hazelden Date: Sat, 20 Jul 2024 23:13:55 +0100 Subject: [PATCH 3/4] Add tests for ledger-insert-effective-date-region. --- test/mode-test.el | 73 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/test/mode-test.el b/test/mode-test.el index abfa14a7..77fbb2e7 100644 --- a/test/mode-test.el +++ b/test/mode-test.el @@ -284,6 +284,79 @@ http://bugs.ledger-cli.org/show_bug.cgi?id=256" (call-interactively 'ledger-insert-effective-date)) (should (equal (buffer-string) orig-file-contents))))) +(ert-deftest ledger-mode/test-009 () + "Baseline test for `ledger-insert-effective-date-region'." + :tags '(mode baseline) + + (cl-flet ((file-with-dates (date-1 date-2) + ;; A file with two optional effective dates. + (format + "\ +2024-01-01%s Grocery Store + Expenses:Groceries $30 + Liabilities:Credit Card + +2024-01-02%s Grocery Store + Expenses:Groceries $10 + Expenses:Tax $1.50 + Liabilities:Credit Card -$11.50 +" + (if date-1 (concat "=" date-1) "") + (if date-2 (concat "=" date-2) "")))) + (ledger-tests-with-temp-file + (file-with-dates nil nil) + + ;; With no prefix arg, insert or replace effective date for xacts that + ;; start on a line that overlaps the range. With one, remove the + ;; effective date. + + ;; Range fully contained in start line. + (save-excursion + (let ((min (progn (forward-char 1) (point))) + (max (progn (forward-char 1) (point)))) + (ledger-insert-effective-date-region min max "2024-03-01") + (should (equal (buffer-string) (file-with-dates "2024-03-01" nil))))) + + ;; Range doesn't overlap the start of an xact: don't do anything. + (save-excursion + (let ((min (progn (forward-line 1) (point))) + (max (progn (forward-line 3) (point)))) + (ledger-insert-effective-date-region min max "2024-03-02") + (should (equal (buffer-string) (file-with-dates "2024-03-01" nil))))) + + ;; Range overlaps multiple xacts: update them all. + (save-excursion + (let ((min (point)) + (max (progn (forward-line 4) (forward-char 1) (point)))) + (ledger-insert-effective-date-region min max "2024-03-03") + (should (equal (buffer-string) + (file-with-dates "2024-03-03" "2024-03-03"))))) + + ;; Remove effective date from multiple. + (save-excursion + (let ((min (point)) + (max (progn (forward-line 4) (forward-char 1) (point))) + (current-prefix-arg '(4))) + (ledger-insert-effective-date-region min max nil) + (should (equal (buffer-string) (file-with-dates nil nil))))) + + ;; Add it back to both. + (save-excursion + (let ((min (point)) + (max (progn (forward-line 4) (forward-char 1) (point)))) + (ledger-insert-effective-date-region min max "2024-03-04") + (should (equal (buffer-string) + (file-with-dates "2024-03-04" "2024-03-04"))))) + + ;; Remove from just the first. + (save-excursion + (let ((min (progn (forward-char 1) (point))) + (max (progn (forward-char 1) (point))) + (current-prefix-arg '(4))) + (ledger-insert-effective-date-region min max nil) + (should (equal (buffer-string) + (file-with-dates nil "2024-03-04")))))))) + (provide 'mode-test) ;;; mode-test.el ends here From 1afb7722ebfc33a26665078746145c50d34b1efd Mon Sep 17 00:00:00 2001 From: Phil Hazelden Date: Tue, 23 Jul 2024 22:36:53 +0100 Subject: [PATCH 4/4] Remove some duplicate code. --- ledger-mode.el | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ledger-mode.el b/ledger-mode.el index bd852428..51d56958 100644 --- a/ledger-mode.el +++ b/ledger-mode.el @@ -245,16 +245,8 @@ With a prefix argument, remove any effective dates." (goto-char start) (while (< (point) end) (let ((context (car (ledger-context-at-point)))) - (save-restriction - (narrow-to-region (line-beginning-position) (line-end-position)) - (when (eq 'xact context) - (if should-remove - (ledger-remove-effective-date) - (beginning-of-line) - (re-search-forward ledger-iso-date-regexp) - (when (= (char-after) ?=) - (ledger-remove-effective-date)) - (insert "=" date-string))))) + (when (eq 'xact context) + (ledger-insert-effective-date date-string))) (forward-line 1))))) (defun ledger-mode-remove-extra-lines ()