Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

244 lines
7.2 KiB

;;; number.el --- Working with numbers at point.
;; Package-Version: 20170901.1312
;; Package-Commit: bbc278d34dbcca83e70e3be855ec98b23debfb99
;; Copyright (c) 2014 Chris Done. All rights reserved.
;; This file 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, or (at your option)
;; any later version.
;; This file 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 <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Do trivial arithmetic on the numbers at point. Attempts to preserve
;; padding when it can. Examples:
;; M-x number/add 1 RET
;; 1 -> 2
;; 05 -> 06
;; 6.30 -> 7.30
;; 07.30 -> 08.30
;; -08.30 -> -07.30
;; M-x number/pad 2 RET
;; 5 -> 05
;; M-x number/pad 2 RET 6 RET
;; 3.141 -> 03.141000
;; The "guessing" where the number is isn't yet quite awesome, e.g. it
;; doesn't know that the 05 in "2014-05-01" is a month and not,
;; e.g. the number -05. But you can use the region to explicitly
;; denote the start and end of the number.
;; The following keybindings might be nice to use:
;;
;; (global-set-key (kbd "C-c C-+") 'number/add)
;; (global-set-key (kbd "C-c C--") 'number/sub)
;; (global-set-key (kbd "C-c C-*") 'number/multiply)
;; (global-set-key (kbd "C-c C-/") 'number/divide)
;; (global-set-key (kbd "C-c C-0") 'number/pad)
;; (global-set-key (kbd "C-c C-=") 'number/eval)
;;; Code:
(defun number/add (n)
"Add to the number at point."
(interactive (list (number-read-from-minibuffer)))
(number-arith-op n '+))
(defun number/sub (n)
"Subtract to the number at point."
(interactive (list (number-read-from-minibuffer)))
(number-arith-op n '-))
(defun number/multiply (n)
"Multiply the number at point."
(interactive (list (number-read-from-minibuffer)))
(number-arith-op n '*))
(defun number/divide (n)
"Divide the number at point."
(interactive (list (number-read-from-minibuffer)))
(number-arith-op n '/))
(defun number/eval ()
(interactive)
(number-transform
(lambda (number)
(number-modify
:number
(lambda (x)
(funcall (eval
`(lambda (n)
,(read-from-minibuffer "Eval (e.g. (* n 2)): " "" nil t)))
x))
number))))
(defun number/pad ()
"Pad the number at point."
(interactive)
(number-transform
(lambda (number)
(ecase (number-get number :type)
(integral
(number-modify
:padding
(lambda (p)
(number-read-padding
number
:padding
"Pad (default: %d): "))
number))
(decimal
(number-modify
:decimal-padding
(lambda (p)
(number-read-padding
number
:decimal-padding
"Decimal precision (default: %d): "))
(number-modify
:padding
(lambda (p)
(number-read-padding
number
:padding
"Pad (default: %d): "))
number)))))))
(defun number-read-padding (number key caption)
"Read a padding value for a number."
(or (let ((str (read-from-minibuffer
(format caption
(number-get number key)))))
(unless (string= "" str)
(string-to-number str)))
(number-get number key)))
(defun number/mark ()
"Mark the number at point."
(interactive)
(skip-chars-backward "0-9.")
(when (looking-back "[+\\-]")
(goto-char (1- (point))))
(let ((point (point)))
(skip-chars-forward "+-")
(skip-chars-forward "0-9")
(when (looking-at "\\.[0-9]")
(skip-chars-forward ".")
(skip-chars-forward "0-9"))
(set-mark (point))
(goto-char point)))
(defun number-arith-op (n op)
"Apply the arithmetic operation to the current point."
(number-transform
(lambda (number)
(number-modify
:number
(lambda (x) (funcall op x (number-get n :number)))
number))))
(defun number-transform (f)
"Transform the number at point in some way."
(let ((point (point)))
(let* ((beg-end (prog2 (unless (region-active-p)
(number/mark))
(list (region-beginning)
(region-end))
(deactivate-mark)))
(string (apply 'buffer-substring-no-properties beg-end)))
(let ((new (number-format (funcall f (number-read string)))))
(apply 'delete-region beg-end)
(insert new)))
(goto-char point)))
(defun number-modify (key f number)
"Modify the number contained in a number specifier."
(mapcar (lambda (entry)
(if (eq (car entry) key)
(cons key (funcall f (cdr entry)))
entry))
number))
(defun number-format (number)
"Format the given number specifier to a string."
(ecase (number-get number :type)
(decimal
(if (= 0 (number-get number :padding))
(format (format "%%.%df" (number-get number :decimal-padding))
(number-get number :number))
(number-pad-decimal (number-get number :padding)
(number-get number :decimal-padding)
(number-get number :number))))
(integral
(if (= 0 (number-get number :number))
"0"
(format (format "%%0.%dd" (number-get number :padding))
(number-get number :number))))))
(defun number-pad-decimal (left-pad right-pad n)
"Pad a decimal on the left- and right-hand side of the decimal
place."
(let ((precision right-pad)
(total (+ left-pad 1 right-pad (if (< n 0) 1 0))))
(format (format "%%0%d.%df"
total
precision)
n)))
(defun number-get (number key)
"Get the KEY value from NUMBER."
(cdr (assoc key number)))
(defun number-read-from-minibuffer ()
"Read a number from the minibuffer."
(number-read (read-from-minibuffer "Number: ")))
(defun number-read (string)
"Read a number from a string."
(cond
((string-match "\\." string)
`((:string . ,string)
(:number . ,(string-to-number string))
(:type . decimal)
(:padding . ,(number-padding string))
(:decimal-padding . ,(number-decimal-padding string))))
((string-match "[-+]?[0-9]+" string)
`((:string . ,string)
(:number . ,(string-to-number string))
(:type . integral)
(:padding . ,(number-padding string))))
(t (error "Unable to parse a number."))))
(defun number-padding (string)
"Calculate the padding a number has."
(if (string-match "[-+]?\\(\\(0+\\)[^\\.]*\\)" string)
(length (match-string 1 string))
0))
(defun number-decimal-padding (string)
"Calculate the padding a number has."
(if (string-match "\\.\\([0-9]+\\)$" string)
(length (match-string 1 string))
0))
(defun number-guess-padding (string)
"Guess the padding a number has."
(if (string-match "[-+]?\\(\\([0-9]+\\)[^\\.]*\\)" string)
(length (match-string 1 string))
0))
(provide 'number)
;;; number.el ends here