;;; flycheck-ledger.el --- Flycheck integration for ledger files -*- lexical-binding: t -*- ;; Copyright (C) 2013-2014 Steve Purcell ;; Author: Steve Purcell ;; Homepage: https://github.com/purcell/flycheck-ledger ;; Version: DEV ;; Package-Version: 20200304.2204 ;; Package-Commit: 628e25ba66604946085571652a94a54f4d1ad96f ;; Keywords: convenience languages tools ;; Package-Requires: ((emacs "24.1") (flycheck "0.15")) ;; 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 3 of the License, 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 this program. If not, see . ;;; Commentary: ;; This flychecker uses the output of "ledger balance" on the current file to ;; find errors such as unbalanced transactions and syntax errors. ;;;; Setup ;; (eval-after-load 'flycheck '(require 'flycheck-ledger)) ;;; Code: (require 'flycheck) (flycheck-def-option-var flycheck-ledger-zero-accounts nil ledger-zero "Whether to check account names, tags, and payees from cleared transactions." :type '(repeat string) :safe #'flycheck-string-list-p) (flycheck-define-checker ledger "A checker for ledger files, showing unmatched balances and failed checks." :command ("ledger" (option-flag "--explicit" flycheck-ledger-explicit) (option-flag "--pedantic" flycheck-ledger-pedantic) (eval (when (eq flycheck-ledger-pedantic 'check-payees) "--check-payees")) "-f" source-inplace "balance" ;; to find non-zero zero accounts: "--flat" "--no-total" "--balance-format" "%(scrub(display_total))\t\t%(account())\n" (eval flycheck-ledger-zero-accounts)) :error-patterns ((error line-start "While parsing file \"" (file-name) "\", line " line ":" (zero-or-more whitespace) "\n" (zero-or-more line-start (or "While " "> ") (one-or-more not-newline) "\n" ) (message (minimal-match (zero-or-more line-start (zero-or-more not-newline) "\n")) "Error: " (one-or-more not-newline) "\n"))) :error-parser (lambda (output checker buffer) (let ((pattern-errors (flycheck-parse-with-patterns output checker buffer))) (or pattern-errors (when (> (length flycheck-ledger-zero-accounts) 0) (flycheck-ledger--zero-error-parser output checker buffer))))) :verify (lambda (checker) (let ((has-accounts (> (length flycheck-ledger-zero-accounts) 0))) (list (flycheck-verification-result-new :label "accounts" :message (if has-accounts (format "%s" flycheck-ledger-zero-accounts) "none") :face 'success)))) :modes ledger-mode) (flycheck-def-option-var flycheck-ledger-pedantic () ledger "Whether to be pedantic in ledger. When equal to `check-payees', be pedantic on account name and payees, When non-nil, be pedantic on account name, otherwise don't be pedantic." :type '(radio (const :tag "Run Ledger normally" nil) (const :tag "Check account names (--pedantic)" t) (const :tag "Also check payees (--check-payees)" check-payees))) (flycheck-def-option-var flycheck-ledger-explicit nil ledger "Whether to check account names, tags, and payees from cleared transactions." :type 'boolean) (defun flycheck-ledger--zero-last-position-of-account (account buffer) "Return (LINE . COL) of last occurrence of ACCOUNT in BUFFER. Return nil if ACCOUNT can't be found in BUFFER." (with-current-buffer buffer (save-restriction (save-excursion (goto-char (point-max)) (when (search-backward account nil t) (cons (line-number-at-pos (point)) (1+ (- (point) (line-beginning-position))))))))) (defun flycheck-ledger--zero-error-parser (output checker buffer) "Return errors found in OUTPUT. CHECKER is a `flycheck-ledger-zero' checker. BUFFER is the buffer being checked by flycheck. Return a list of parsed errors and warnings (as `flycheck-error' objects)." (let ((errors (list)) (buffer (current-buffer))) (save-match-data (with-temp-buffer (insert output) (goto-char (point-min)) (while (re-search-forward "^\\(.*\\)\\>\t\t\\<\\(.*\\)$" nil t) (let* ((amount (string-trim (match-string-no-properties 1))) (account (string-trim (match-string-no-properties 2))) (message (format "Account %s should have zero value but has %s" account amount)) (position (flycheck-ledger--zero-last-position-of-account account buffer)) (line (or (car position) 1)) (column (or (cdr position) 0))) (push (flycheck-error-new-at line column 'error message :checker checker :filename (buffer-file-name buffer) :buffer buffer) errors))))) errors)) (flycheck-def-option-var flycheck-ledger-zero-accounts nil ledger-zero "Whether to check account names, tags, and payees from cleared transactions." :type '(repeat string)) (add-to-list 'flycheck-checkers 'ledger) (provide 'flycheck-ledger) ;;; flycheck-ledger.el ends here