Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 64 additions & 49 deletions ledger-check.el
Original file line number Diff line number Diff line change
Expand Up @@ -28,88 +28,104 @@

(require 'easymenu)
(require 'ledger-navigate)
(require 'ledger-report) ; for ledger-master-file
(require 'ledger-regex)
(require 'ledger-report)


(defvar ledger-check-buffer-name "*Ledger Check*")
(defvar-local ledger-check--original-window-configuration nil)
(defvar-local ledger-check--source-buffer nil)




(defvar ledger-check-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "RET") #'ledger-report-visit-source)
(define-key map (kbd "r") #'ledger-check-redo)
(define-key map (kbd "q") #'ledger-check-quit)
map)
"Keymap for `ledger-check-mode'.")

(easy-menu-define ledger-check-mode-menu ledger-check-mode-map
"Ledger check menu."
'("Check"
;; ["Re-run Check" ledger-check-redo]
["Re-run Check" ledger-check-redo]
"---"
["Visit Source" ledger-report-visit-source]
"---"
["Quit" ledger-check-quit]
))

(define-derived-mode ledger-check-mode text-mode "Ledger-Check"
(define-derived-mode ledger-check-mode special-mode "Ledger-Check"
"A mode for viewing ledger errors and warnings.")


(defun ledger-do-check ()
"Run a check command ."
(goto-char (point-min))
(let ((data-pos (point))
(have-warnings nil))
(shell-command
;; ledger balance command will just return empty if you give it
;; an account name that doesn't exist. I will assume that no
;; one will ever have an account named "e342asd2131". If
;; someones does, this will probably still work for them.
;; I should only highlight error and warning lines.
(format "%s bal e342asd2131 --strict --explicit "
(shell-quote-argument ledger-binary-path))
t nil)
(goto-char data-pos)
"Run a check command and put the output in the current buffer."
(with-silent-modifications
(erase-buffer)

(let ((cbuf (current-buffer)))
(with-current-buffer ledger-check--source-buffer
;; ledger balance command will just return empty if you give it
;; an account name that doesn't exist. I will assume that no
;; one will ever have an account named "e342asd2131". If
;; someones does, this will probably still work for them.
;; I should only highlight error and warning lines.
(call-process-region (point-min) (point-max)
ledger-binary-path
nil cbuf t
"bal" "e342asd2131" "--strict" "--explicit" "--file=-")))

;; format check report to make it navigate the file

(while (re-search-forward "^.*: \"\\(.*\\)\", line \\([0-9]+\\)" nil t)
(let ((file (match-string 1))
(line (string-to-number (match-string 2))))
(when file
(set-text-properties (line-beginning-position) (line-end-position)
(list 'ledger-source (cons file (save-window-excursion
(save-excursion
(find-file file)
(widen)
(ledger-navigate-to-line line)
(point-marker))))))
(add-text-properties (line-beginning-position) (line-end-position)
(list 'font-lock-face 'ledger-font-report-clickable-face))
(setq have-warnings t)
(end-of-line))))
(if (not have-warnings)
(insert "No errors or warnings reported."))))
(goto-char (point-min))
(while (re-search-forward ledger-error-or-warning-regex nil t)
(let* ((beg (match-beginning 0))
(end (match-end 0))
(line (string-to-number (match-string 2)))
(source-marker
(with-current-buffer ledger-check--source-buffer
(save-excursion
(save-restriction
(widen)
(ledger-navigate-to-line line)
(point-marker))))))
(set-text-properties beg end
(list 'ledger-source source-marker
'face 'ledger-font-report-clickable-face))
(end-of-line)))
(when (= (buffer-size) 0)
(insert "No errors or warnings reported.\n"))))

(defun ledger-check-goto ()
"Goto the ledger check buffer."
(interactive)
(let ((rbuf (get-buffer ledger-check-buffer-name)))
(if (not rbuf)
(error "There is no ledger check buffer"))
(pop-to-buffer rbuf)
(shrink-window-if-larger-than-buffer)))
(unless rbuf
(user-error "There is no ledger check buffer"))
(pop-to-buffer rbuf
'(display-buffer-below-selected
(window-height . shrink-window-if-larger-than-buffer)))))

(defun ledger-check-redo ()
"Re-run the check command for the current output buffer."
(interactive)
(ledger-do-check)
(goto-char (point-min))
(message (substitute-command-keys "\\[ledger-check-quit] to quit; \\[ledger-check-redo] to redo")))

(defun ledger-check-quit ()
"Quit the ledger check buffer."
(interactive)
(ledger-check-goto)
(set-window-configuration ledger-check--original-window-configuration)
(kill-buffer (get-buffer ledger-check-buffer-name)))
(kill-buffer ledger-check-buffer-name))

;; FIXME: This is an awful lot of faff, couldn't we just use the report logic
;; and maybe define a custom "check" report? It would work better in most ways,
;; just need to add any missing features like window config restore (and maybe
;; it should use markers).
(defun ledger-check-buffer (&optional interactive)
"Check the current buffer for errors.

Expand All @@ -126,20 +142,19 @@ prompt to save if the current buffer is modified."
(buffer-modified-p)
(y-or-n-p "Buffer modified, save it? "))
(save-buffer))
(let ((_buf (find-file-noselect (ledger-master-file)))
(let ((source-buffer (current-buffer))
(cbuf (get-buffer ledger-check-buffer-name))
(wcfg (current-window-configuration)))
(if cbuf
(kill-buffer cbuf))
(with-current-buffer
(pop-to-buffer (get-buffer-create ledger-check-buffer-name))
(with-current-buffer (get-buffer-create ledger-check-buffer-name)
(ledger-check-mode)
(setq ledger-check--original-window-configuration wcfg)
(ledger-do-check)
(shrink-window-if-larger-than-buffer)
(set-buffer-modified-p nil)
(setq buffer-read-only t)
(message "q to quit; r to redo; k to kill"))))
(setq ledger-check--source-buffer source-buffer
ledger-check--original-window-configuration wcfg)
(pop-to-buffer (current-buffer)
'(display-buffer-below-selected
(window-height . shrink-window-if-larger-than-buffer)))
(ledger-check-redo))))


(provide 'ledger-check)
Expand Down
14 changes: 3 additions & 11 deletions ledger-flymake.el
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
(require 'cl-lib)
(require 'flymake)
(require 'ledger-exec) ; for `ledger-binary-path'
(require 'ledger-regex)
(require 'ledger-report) ; for `ledger-master-file'

(defvar-local ledger--flymake-proc nil)
Expand Down Expand Up @@ -99,17 +100,8 @@ Flymake calls this with REPORT-FN as needed."
;; messages and locations, collect them in a list
;; of objects, and call `report-fn'.
(cl-loop
while (search-forward-regexp
;; This regex needs to match the whole error. We
;; also need a capture group for the error message
;; (that's group 1 here) and the line number
;; (group 2).
(rx line-start "While parsing file \"" (one-or-more (not whitespace)) " line " (group-n 2 (one-or-more num)) ":\n"
(zero-or-more line-start "While " (one-or-more not-newline) "\n" )
(minimal-match (zero-or-more line-start (zero-or-more not-newline) "\n"))
(group-n 1 "Error: " (one-or-more not-newline) "\n"))
nil t)
for msg = (match-string 1)
while (search-forward-regexp ledger-error-regex nil t)
for msg = (match-string 3)
for region = (flymake-diag-region
source
(string-to-number (match-string 2)))
Expand Down
17 changes: 17 additions & 0 deletions ledger-regex.el
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,23 @@
(defconst ledger-directive-start-regex
"[=~;#%|\\*[A-Za-z]")

(defconst ledger-error-regex
(rx line-start "While parsing file \"" (group-n 1 (zero-or-more (not ?\"))) ; file, subexp 1
"\", line " (group-n 2 (one-or-more num)) ":\n" ; line number, subexp 2
(zero-or-more line-start "While " (one-or-more not-newline) "\n" )
(minimal-match (zero-or-more line-start (zero-or-more not-newline) "\n"))
(group-n 3 "Error: " (one-or-more not-newline) "\n"))) ; message, subexp 3

(defconst ledger-warning-regex
(rx line-start "Warning: \""
(group-n 1 (zero-or-more (not ?\"))) ;file, subexp 1
"\", line "
(group-n 2 (one-or-more num)) ": " ;line number, subexp 2
(group-n 3 (one-or-more not-newline)))) ;message, subexp 3

(defconst ledger-error-or-warning-regex
(rx (or (regexp ledger-error-regex)
(regexp ledger-warning-regex))))

(provide 'ledger-regex)

Expand Down
23 changes: 11 additions & 12 deletions ledger-report.el
Original file line number Diff line number Diff line change
Expand Up @@ -579,18 +579,17 @@ replaced by arguments returned by `ledger-report--compute-extra-args'."
If `ledger-report-links-beginning-of-xact' is nil, visit the
specific posting at point instead."
(interactive)
(let* ((prop (get-text-property (point) 'ledger-source))
(file (car prop))
(xact-position (cdr prop)))
(when (and file xact-position)
(find-file-other-window file)
(widen)
(if (markerp xact-position)
(goto-char xact-position)
(progn (goto-char (point-min))
(forward-line (1- xact-position))))
(when ledger-report-links-beginning-of-xact
(ledger-navigate-beginning-of-xact)))))
;; `ledger-source' may be (FILE . LINE) or MARKER.
(when-let* ((prop (get-text-property (point) 'ledger-source)))
(if (consp prop)
(let ((file (car prop)) (line (cdr prop)))
(find-file-other-window file)
(widen)
(ledger-navigate-to-line line))
(pop-to-buffer (marker-buffer prop))
(goto-char prop))
(when ledger-report-links-beginning-of-xact
(ledger-navigate-beginning-of-xact))))

(defun ledger-report-goto ()
"Goto the ledger report buffer."
Expand Down
61 changes: 26 additions & 35 deletions test/check-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,6 @@
(require 'test-helper)
(require 'ledger-check)


(ert-deftest ledger-check/mode-derived-from-text-mode ()
"`ledger-check-mode' is a major mode derived from `text-mode'."
(with-temp-buffer
(ledger-check-mode)
(should (eq major-mode 'ledger-check-mode))
(should (derived-mode-p 'text-mode))))

(ert-deftest ledger-check/mode-keymap-bindings ()
"RET visits source, q quits."
(should (eq (lookup-key ledger-check-mode-map (kbd "RET"))
Expand All @@ -48,36 +40,35 @@

(ert-deftest ledger-check/do-check-no-errors ()
"`ledger-do-check' inserts a 'no warnings' note when ledger reports nothing."
(cl-letf (((symbol-function 'shell-command)
;; Simulate ledger producing no output (empty input → empty out).
(lambda (_cmd &rest _) nil)))
(with-temp-buffer
(ledger-do-check)
(should (string-match-p "No errors or warnings reported."
(buffer-string))))))
;; TODO: This test should succeed once ledger-do-check starts actually reading
;; from the current buffer. For now, it does not pass any -f argument to the
;; ledger binary so falls back to whatever is specified in ~/.ledgerrc.
:expected-result :failed
(ledger-tests-with-temp-file ""
(ledger-do-check)
(should (equal "No errors or warnings reported.\n"
(buffer-string)))))

(ert-deftest ledger-check/do-check-parses-error-line ()
"An error line is decorated with `ledger-source' text properties."
(let* ((tmp (make-temp-file "ledger-check-")))
(unwind-protect
(progn
(with-temp-file tmp
(insert "2024/01/01 Acme\n"
" Expenses:Food $10\n"
" Assets:Cash\n"))
(cl-letf (((symbol-function 'shell-command)
(lambda (_cmd &rest _)
(insert (format "Error: \"%s\", line 2: bad amount\n" tmp)))))
(with-temp-buffer
(ledger-do-check)
;; The marked line should carry a 'ledger-source text property
;; whose CDR is a marker into the file.
(goto-char (point-min))
(let ((src (get-text-property (point) 'ledger-source)))
(should (consp src))
(should (string= (car src) tmp))
(should (markerp (cdr src)))))))
(when (file-exists-p tmp) (delete-file tmp)))))
(ledger-tests-with-temp-file
"\
2024/01/01 Acme
Expenses:Food $10
Assets:Cash -$5
"
(let ((src-buffer (current-buffer)))
(ledger-check-buffer)
(with-current-buffer ledger-check-buffer-name
;; The marked line should carry a 'ledger-source text property
;; whose value is a marker in the source buffer.
(goto-char (point-min))
(let ((src (get-text-property (point) 'ledger-source)))
(should (markerp src))
(should (eq (marker-buffer src) src-buffer))
(should (equal (with-current-buffer src-buffer
(line-number-at-pos src))
2)))))))

(ert-deftest ledger-check/check-buffer-creates-output-buffer ()
"`ledger-check-buffer' switches to the check buffer in `ledger-check-mode'."
Expand Down
Loading