ledger-commodities.el 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. ;;; ledger-commodities.el --- Helper code for use with the "ledger" command-line tool
  2. ;; Copyright (C) 2003-2016 John Wiegley (johnw AT gnu DOT org)
  3. ;; This file is not part of GNU Emacs.
  4. ;; This is free software; you can redistribute it and/or modify it under
  5. ;; the terms of the GNU General Public License as published by the Free
  6. ;; Software Foundation; either version 2, or (at your option) any later
  7. ;; version.
  8. ;;
  9. ;; This is distributed in the hope that it will be useful, but WITHOUT
  10. ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11. ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  12. ;; for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs; see the file COPYING. If not, write to the
  16. ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  17. ;; MA 02110-1301 USA.
  18. ;;; Commentary:
  19. ;; Helper functions to deal with commoditized numbers. A commoditized
  20. ;; number will be a list of value and string where the string contains
  21. ;; the commodity
  22. ;;; Code:
  23. (require 'ledger-regex)
  24. (defcustom ledger-reconcile-default-commodity "$"
  25. "The default commodity for use in target calculations in ledger reconcile."
  26. :type 'string
  27. :group 'ledger-reconcile)
  28. (defun ledger-read-commodity-with-prompt (prompt)
  29. "Read commodity name after PROMPT.
  30. Default value is `ledger-reconcile-default-commodity'."
  31. (let* ((buffer (current-buffer))
  32. (commodities (with-temp-buffer
  33. (ledger-exec-ledger buffer (current-buffer) "commodities")
  34. (split-string (buffer-string) "\n" t))))
  35. (completing-read prompt commodities nil t nil nil ledger-reconcile-default-commodity)))
  36. (defun ledger-split-commodity-string (str)
  37. "Split a commoditized string, STR, into two parts.
  38. Returns a list with (value commodity)."
  39. (let ((number-regex (if (assoc "decimal-comma" ledger-environment-alist)
  40. ledger-amount-decimal-comma-regex
  41. ledger-amount-decimal-period-regex)))
  42. (if (> (length str) 0)
  43. (with-temp-buffer
  44. (insert str)
  45. (goto-char (point-min))
  46. (cond
  47. ((re-search-forward "\"\\(.*\\)\"" nil t) ; look for quoted commodities
  48. (let ((com (delete-and-extract-region
  49. (match-beginning 1)
  50. (match-end 1))))
  51. (if (re-search-forward
  52. number-regex nil t)
  53. (list
  54. (ledger-string-to-number
  55. (delete-and-extract-region (match-beginning 0) (match-end 0)))
  56. com))))
  57. ((re-search-forward number-regex nil t)
  58. ;; found a number in the current locale, return it in the
  59. ;; car. Anything left over is annotation, the first
  60. ;; thing should be the commodity, separated by
  61. ;; whitespace, return it in the cdr. I can't think of
  62. ;; any counterexamples
  63. (list
  64. (ledger-string-to-number
  65. (delete-and-extract-region (match-beginning 0) (match-end 0)))
  66. (nth 0 (split-string (buffer-substring-no-properties (point-min) (point-max))))))
  67. ((re-search-forward "0" nil t)
  68. ;; couldn't find a decimal number, look for a single 0,
  69. ;; indicating account with zero balance
  70. (list 0 ledger-reconcile-default-commodity))))
  71. ;; nothing found, return 0
  72. (list 0 ledger-reconcile-default-commodity))))
  73. (defun ledger-string-balance-to-commoditized-amount (str)
  74. "Return a commoditized amount (val, 'comm') from STR."
  75. ; break any balances with multi commodities into a list
  76. (mapcar #'(lambda (st)
  77. (ledger-split-commodity-string st))
  78. (split-string str "[\n\r]")))
  79. (defun -commodity (c1 c2)
  80. "Subtract C2 from C1, ensuring their commodities match."
  81. (if (string= (cadr c1) (cadr c2))
  82. (list (-(car c1) (car c2)) (cadr c1))
  83. (error "Can't subtract different commodities %S from %S" c2 c1)))
  84. (defun +commodity (c1 c2)
  85. "Add C1 and C2, ensuring their commodities match."
  86. (if (string= (cadr c1) (cadr c2))
  87. (list (+ (car c1) (car c2)) (cadr c1))
  88. (error "Can't add different commodities, %S to %S" c1 c2)))
  89. (defun ledger-strip (str char)
  90. "Return STR with CHAR removed."
  91. (replace-regexp-in-string char "" str))
  92. (defun ledger-string-to-number (str &optional decimal-comma)
  93. "improve builtin string-to-number by handling internationalization, and return nil if number can't be parsed"
  94. (let ((nstr (if (or decimal-comma
  95. (assoc "decimal-comma" ledger-environment-alist))
  96. (ledger-strip str ".")
  97. (ledger-strip str ","))))
  98. (while (string-match "," nstr) ;if there is a comma now, it is a thousands separator
  99. (setq nstr (replace-match "." nil nil nstr)))
  100. (string-to-number nstr)))
  101. (defun ledger-number-to-string (n &optional decimal-comma)
  102. "number-to-string that handles comma as decimal."
  103. (let ((str (number-to-string n)))
  104. (when (or decimal-comma
  105. (assoc "decimal-comma" ledger-environment-alist))
  106. (while (string-match "\\." str)
  107. (setq str (replace-match "," nil nil str))))
  108. str))
  109. (defun ledger-commodity-to-string (c1)
  110. "Return string representing C1.
  111. Single character commodities are placed ahead of the value,
  112. longer ones are after the value."
  113. (let ((str (ledger-number-to-string (car c1)))
  114. (commodity (cadr c1)))
  115. (if (> (length commodity) 1)
  116. (concat str " " commodity)
  117. (concat commodity " " str))))
  118. (defun ledger-read-commodity-string (prompt)
  119. "Read an amount from mini-buffer using PROMPT."
  120. (let ((str (read-from-minibuffer
  121. (concat prompt " (" ledger-reconcile-default-commodity "): ")))
  122. comm)
  123. (if (and (> (length str) 0)
  124. (ledger-split-commodity-string str))
  125. (progn
  126. (setq comm (ledger-split-commodity-string str))
  127. (if (cadr comm)
  128. comm
  129. (list (car comm) ledger-reconcile-default-commodity))))))
  130. (provide 'ledger-commodities)
  131. ;;; ledger-commodities.el ends here