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.
|
|
;;; 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
|