]> code.delx.au - gnu-emacs-elpa/blobdiff - tiny.el
Fix byte-compiler warnings
[gnu-emacs-elpa] / tiny.el
diff --git a/tiny.el b/tiny.el
index 7c7c3f20b0241c20afe0c65c868e38e1b08fa382..90c89d5aba5481df79fee9b51543c001838d6de9 100644 (file)
--- a/tiny.el
+++ b/tiny.el
@@ -1,34 +1,36 @@
 ;;; tiny.el --- Quickly generate linear ranges in Emacs
 
-;; Copyright (C) 2013  Oleh Krehel
+;; Copyright (C) 2013-2015  Free Software Foundation, Inc.
 
 ;; Author: Oleh Krehel <ohwoeowho@gmail.com>
 ;; URL: https://github.com/abo-abo/tiny
-;; Version: 0.1
+;; Version: 0.1.1
+;; Keywords: convenience
 
-;; This file is free software; you can redistribute it and/or modify
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+;; 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,
+;; GNU Emacs 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.
 
-;; For a full copy of the GNU General Public License
-;; see <http://www.gnu.org/licenses/>.
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>
 
 ;;; Commentary:
 ;;
-;; Setup:
-;; In ~/.emacs:
-;;     (require 'tiny)
-;;     (tiny-setup-default)
+;; To set it up, just bind e.g.:
+;;
+;;     (global-set-key (kbd "C-;") 'tiny-expand)
 ;;
 ;; Usage:
 ;; This extension's main command is `tiny-expand'.
-;; It's meant to generate quickly linear ranges, e.g. 5, 6, 7, 8.
+;; It's meant to quickly generate linear ranges, e.g. 5, 6, 7, 8.
 ;; Some elisp proficiency is an advantage, since you can transform
 ;; your numeric range with an elisp expression.
 ;;
 ;; m5 10
 ;; m5,10
 ;; m5 10*xx
-;; m5 10*xx&x
-;; m5 10*xx&0x&x
-;; m25+x?a&c
-;; m25+x?A&c
-;; m97,122stringx
+;; m5 10*xx%x
+;; m5 10*xx|0x%x
+;; m25+x?a%c
+;; m25+x?A%c
+;; m97,122(string x)
 ;; m97,122stringxx
+;; m97,120stringxupcasex
+;; m97,120stringxupcasex)x
+;; m\n;; 10|%(+ x x) and %(* x x) and %s
 ;; m10*2+3x
 ;; m\n;; 10expx
+;; m5\n;; 20expx%014.2f
+;; m7|%(expt 2 x)
+;; m, 7|0x%02x
+;; m10|%0.2f
+;; m1\n14|*** TODO http://emacsrocks.com/e%02d.html
+;; m1\n10|convert img%s.jpg -monochrome -resize 50%% -rotate 180 img%s_mono.pdf
+;; (setq foo-list '(m1 11+x96|?%c))
+;; m1\n10listx+x96|convert img%s.jpg -monochrome -resize 50%% -rotate 180 img%c_mono.pdf
+;; m1\n10listxnthxfoo-list|convert img%s.jpg -monochrome -resize 50%% -rotate 180 img%c_mono.pdf
+;; m\n;; 16list*xxx)*xx%s:%s:%s
+;; m\n8|**** TODO Learning from Data Week %(+ x 2) \nSCHEDULED: <%(date "Oct 7" (* x 7))> DEADLINE: <%(date "Oct 14" (* x 7))>
 ;;
 ;; As you might have guessed, the syntax is as follows:
-;; m[<range start:=0>][<separator:= >]<range end>[lisp expr][&][format expr]
+;; m[<range start:=0>][<separator:= >]<range end>[Lisp expr]|[format expr]
 ;;
-;; x is the default var in the elisp expression. It will take one by one
+;; x is the default var in the elisp expression.  It will take one by one
 ;; the value of all numbers in the range.
 ;;
-;; & means that elisp expr has ended and format expr has begun.
-;; It can be used as part of the format expr if there's only one.
-;; The keys are the same as for format: I just translate & to %.
-\f
-(require 'cl)
+;; | means that elisp expr has ended and format expr has begun.
+;; It can be omitted if the format expr starts with %.
+;; The keys are the same as for format.
+;; In addition %(sexp) forms are allowed.  The sexp can depend on x.
+;;
+;; Note that multiple % can be used in the format expression.
+;; In that case:
+;; * if the Lisp expression returns a list, the members of this list
+;;   are used in the appropriate place.
+;; * otherwise, it's just the result of the expression repeated as
+;;   many times as necessary.
+
+;;; Code:
+
+(eval-when-compile
+  (require 'cl))
 (require 'help-fns)
+(require 'org)
+
+(defvar tiny-beg nil
+  "Last matched snippet start position.")
+
+(defvar tiny-end nil
+  "Last matched snippet end position.")
 
+;;;###autoload
 (defun tiny-expand ()
   "Expand current snippet.
-It's intended to poll all registered expander functions
+It polls the expander functions one by one
 if they can expand the thing at point.
 First one to return a string succeeds.
-These functions are expcted to set `tiny-beg' and `tiny-end'
+These functions are expected to set `tiny-beg' and `tiny-end'
 to the bounds of the snippet that they matched.
 At the moment, only `tiny-mapconcat' is supported.
 `tiny-mapconcat2' should be added to expand rectangles."
@@ -83,213 +118,308 @@ At the moment, only `tiny-mapconcat' is supported.
       (insert str)
       (tiny-replace-this-sexp))))
 
-(defvar tiny-beg nil
-  "Last matched snipped start position.")
-
-(defvar tiny-end nil
-  "Last matched snipped end position.")
-
 (defun tiny-setup-default ()
+  "Setup shortcuts."
   (global-set-key (kbd "C-;") 'tiny-expand))
 
+(defalias 'tiny--preceding-sexp
+    (if (fboundp 'elisp--preceding-sexp)
+        'elisp--preceding-sexp
+      'preceding-sexp))
+
+;;;###autoload
 (defun tiny-replace-this-sexp ()
-  "Intellegintly replace current sexp."
+  "Eval and replace the current sexp.
+On error go up list and try again."
   (interactive)
-  (or
-   (and (looking-back ")")
+  (if (region-active-p)
+      (let ((s (buffer-substring-no-properties
+                (region-beginning)
+                (region-end))))
+        (delete-region (region-beginning)
+                       (region-end))
+        (insert (format "%s" (eval (read s)))))
+    (catch 'success
+      (while t
         (ignore-errors
-          (tiny-replace-last-sexp)))
-   (save-excursion (tiny-replace-sexp-desperately))))
-
-(defun tiny-replace-last-sexp ()
-  (interactive)
-  (let ((sexp (preceding-sexp)))
-    (unless (eq (car sexp) 'lambda)
-      (let ((value (eval sexp)))
-        (kill-sexp -1)
-        (insert (format "%s" value))
-        t))))
+          (unless (looking-back ")" (line-beginning-position))
+            (error "Bad location"))
+          (let ((sexp (tiny--preceding-sexp)))
+            (if (eq (car sexp) 'lambda)
+                (error "Lambda evaluates to itself")
+              (let ((value (eval sexp)))
+                (kill-sexp -1)
+                (insert (format "%s" value))
+                (throw 'success t)))))
+        ;; if can't replace, go up list
+        (condition-case nil
+            (tiny-up-list)
+          (error
+           (message "reached the highest point, couldn't eval.")
+           (throw 'success nil)))))))
 
-(defun tiny-replace-sexp-desperately ()
-  "Try to eval the current sexp.
-Replace it if there's no error.
-Go upwards until it's posible to eval.
-Skip lambdas."
+(defun tiny-up-list ()
+  "An `up-list' that can exit from string.
+Must throw an error when can't go up further."
   (interactive)
-  (when (yas/snippets-at-point)
-    (yas/exit-all-snippets))
-  (condition-case nil
-      (up-list*)
-    (error "can't go up this list"))
-  (let ((sexp (preceding-sexp)))
-    (cond
-      ((eq (car sexp) 'lambda)
-       (tiny-replace-sexp-desperately))
-      (t
-       (condition-case nil
-           (let ((value (eval sexp)))
-             (kill-sexp -1)
-             (insert (format "%s" value)))
-         (error (tiny-replace-sexp-desperately)))))))
+  ;; check if inside string
+  (let ((p (nth 8 (syntax-ppss))))
+    (when (eq (char-after p) ?\")
+      ;; go to beginning for string
+      (goto-char p)))
+  (up-list))
 
 (defun tiny-mapconcat ()
-  "Take the output of `tiny-mapconcat-parse' and replace
-the null values with defaults and return the formatted
-expression."
-  (destructuring-bind (n1 s1 n2 expr fmt) (tiny-mapconcat-parse)
-    (when (zerop (length n1))
-      (setq n1 "0"))
-    (when (zerop (length s1))
-      (setq s1 " "))
-    (when (zerop (length expr))
-      (setq expr "x"))
-    (when (zerop (length fmt))
-      (setq fmt "%s"))
-    (unless (>= (read n1) (read n2))
-      (format
-       "(mapconcat (lambda(x) (format \"%s\" %s)) (number-sequence %s %s) \"%s\")"
-       fmt
-       expr
-       n1
-       n2
-       s1))))
-\f
+  "Format output of `tiny-mapconcat-parse'.
+Defaults are used in place of null values."
+  (let ((parsed (tiny-mapconcat-parse)))
+    (when parsed
+      (let* ((n0 (or (nth 0 parsed) "0"))
+             (n1 (nth 1 parsed))
+             (s1 (cond ((null n1)
+                        " ")
+                       ((equal n1 "m")
+                        "")
+                       (t
+                        n1)))
+             (n2 (nth 2 parsed))
+             (expr (or (nth 3 parsed) "x"))
+             (lexpr (read expr))
+             (n-have (if (and (listp lexpr) (eq (car lexpr) 'list))
+                         (1- (length lexpr))
+                       0))
+             (expr (if (zerop n-have) `(list ,lexpr) lexpr))
+             (n-have (if (zerop n-have) 1 n-have))
+             (tes (tiny-extract-sexps (or (nth 4 parsed) "%s")))
+             (fmt (car tes))
+             (n-need (cl-count nil (cdr tes)))
+             (idx -1)
+             (format-expression
+              (concat "(mapconcat (lambda(x) (let ((lst %s)) (format %S "
+                      (mapconcat (lambda (x)
+                                   (or x
+                                       (if (>= (1+ idx) n-have)
+                                           "x"
+                                         (format "(nth %d lst)" (incf idx)))))
+                                 (cdr tes)
+                                 " ")
+                      ")))(number-sequence %s %s) \"%s\")")))
+        (unless (>= (read n0) (read n2))
+          (format
+           format-expression
+           expr
+           (replace-regexp-in-string "\\\\n" "\n" fmt)
+           n0
+           n2
+           s1))))))
+
+(defconst tiny-format-str
+  (let ((flags "[+ #-0]\\{0,1\\}")
+        (width "[0-9]*")
+        (precision "\\(?:\\.[0-9]+\\)?")
+        (character "[sdoxXefgcS]?"))
+    (format "\\(%s%s%s%s\\)("
+            flags width precision character)))
+
+(defun tiny-extract-sexps (str)
+  "Return (STR & FORMS).
+Each element of FORMS corresponds to a `format'-style % form in STR.
+
+  * %% forms are skipped
+  * %(sexp) is replaced with %s in STR, and put in FORMS
+  * the rest of forms are untouched in STR, and put as nil in FORMS"
+  (let ((start 0)
+        forms beg fexp)
+    (condition-case nil
+        (while (setq beg (string-match "%" str start))
+          (setq start (1+ beg))
+
+          (cond ((= ?% (aref str (1+ beg)))
+                 (incf start))
+
+                ((and (eq beg (string-match tiny-format-str str beg))
+                      (setq fexp (match-string-no-properties 1 str)))
+                 (incf beg (length fexp))
+                 (destructuring-bind (sexp . end)
+                     (read-from-string str beg)
+                   (push
+                    (replace-regexp-in-string "(date" "(tiny-date"
+                                              (substring str beg end))
+                    forms)
+                   (setq str (concat (substring str 0 beg)
+                                     (if (string= fexp "%") "s" "")
+                                     (substring str end)))))
+                (t (push nil forms))))
+      (error (message "Malformed sexp: %s" (substring str beg))))
+    (cons str (nreverse forms))))
+
 (defun tiny-mapconcat-parse ()
   "Try to match a snippet of this form:
-m[START][SEPARATOR]END[EXPR][FORMAT]
+m[START][SEPARATOR]END[EXPR]|[FORMAT]
 
-* START - integer, default is 0
-* SEPARATOR - string, default is " "
-* END - integer, required
-* EXPR - lisp expression.
-  Parens are optional if it's unabiguous, e.g.
-  `(* 2 (+ x 3))' can be shortened to *2+x3,
-  and `(exp x)' can be shortened to expx.
+* START     - integer (defaults to 0)
+* SEPARATOR - string  (defaults to \" \")
+* END       - integer (required)
+* EXPR      - Lisp expression: function body with argument x (defaults to x)
+  Parens are optional if it's unambiguous:
+  - `(* 2 (+ x 3))'  <-> *2+x3
+  - `(exp x)'        <-> expx
   A closing paren may be added to resolve ambiguity:
-  *2+x3"
-  (let (n1 s1 n2 expr fmt str)
-    (and (catch 'done
-           (cond
-             ;; either start with a number
-             ((looking-back "\\m\\(-?[0-9]+\\)\\([^\n]*?\\)")
-              (setq n1 (match-string-no-properties 1)
-                    str (match-string-no-properties 2)
-                    tiny-beg (match-beginning 0)
-                    tiny-end (match-end 0))
-              (when (zerop (length str))
-                (setq n2 n1
-                      n1 nil)
-                (throw 'done t)))
-             ;; else capture the whole thing
-             ((looking-back "\\m\\([^\n]*\\)")
-              (setq str (match-string-no-properties 1)
-                    tiny-beg (match-beginning 0)
-                    tiny-end (match-end 0))
-              (when (zerop (length str))
-                (throw 'done nil))))
-           ;; at this point, `str' should be either [sep]<num>[expr][fmt]
-           ;; or [expr][fmt]
-           ;;
-           ;; First, try to match [expr][fmt]
-           (string-match "^\\(.*?\\)\\(&.*\\)?$" str)
-           (setq expr (match-string-no-properties 1 str))
-           (setq fmt  (match-string-no-properties 2 str))
-           ;; If it's a valid expression, we're done
-           (when (setq expr (tiny-tokenize expr))
-             (when fmt
-               (setq fmt (replace-regexp-in-string
-                          "&" "%" fmt)))
-             (setq n2 n1
-                   n1 nil)
-             (throw 'done t))
-           ;; at this point, `str' is [sep]<num>[expr][fmt]
-           (if (string-match "^\\([^\n0-9]*?\\)\\(-?[0-9]+\\)\\(.*\\)?$" str)
-               (setq s1 (match-string-no-properties 1 str)
-                     n2 (match-string-no-properties 2 str)
-                     str (match-string-no-properties 3 str))
-             ;; here there's only n2 that was matched as n1
-             (setq n2 n1
-                   n1 nil))
-           ;; match expr_fmt
-           (when str
-             (if (string-match "^:?\\([^\n&]*?\\)\\(&[^\n]*\\)?$" str)
-                (progn
-                   (setq expr (tiny-tokenize (match-string-no-properties 1 str)))
-                   (setq fmt (match-string-no-properties 2 str)))
-               (error "couldn't match %s" str)))
-           (when (> (length fmt) 0)
-             (if (string-match "^&.*&.*$" fmt)
-                 (setq fmt (replace-regexp-in-string "&" "%" (substring fmt 1)))
-               (aset fmt 0 ?%)))
-           t)
-         (list n1 s1 n2 expr fmt))))
+  - `(* 2 (+ x 3) x) <-> *2+x3)
+* FORMAT    - string, `format'-style (defaults to \"%s\")
+  | separator can be omitted if FORMAT starts with %.
+
+Return nil if nothing was matched, otherwise
+ (START SEPARATOR END EXPR FORMAT)"
+  (let ((case-fold-search nil)
+        n1 s1 n2 expr fmt str
+        n-uses)
+    (when (catch 'done
+            (cond
+              ;; either start with a number
+              ((looking-back "\\bm\\(-?[0-9]+\\)\\([^\n]*?\\)"
+                             (line-beginning-position))
+               (setq n1 (match-string-no-properties 1)
+                     str (match-string-no-properties 2)
+                     tiny-beg (match-beginning 0)
+                     tiny-end (match-end 0))
+               (when (zerop (length str))
+                 (setq n2 n1
+                       n1 nil)
+                 (throw 'done t)))
+              ;; else capture the whole thing
+              ((looking-back "\\bm\\([^%|\n]*[0-9][^\n]*\\)"
+                             (line-beginning-position))
+               (setq str (match-string-no-properties 1)
+                     tiny-beg (match-beginning 0)
+                     tiny-end (match-end 0))
+               (when (zerop (length str))
+                 (throw 'done nil)))
+              (t (throw 'done nil)))
+            ;; at this point, `str' should be either [sep]<num>[expr][fmt]
+            ;; or [expr][fmt]
+            ;;
+            ;; First, try to match [expr][fmt]
+            (string-match "^\\(.*?\\)|?\\(%.*\\)?$" str)
+            (setq expr (match-string-no-properties 1 str))
+            (setq fmt (match-string-no-properties 2 str))
+            ;; If it's a valid expression, we're done
+            (when (setq expr (tiny-tokenize expr))
+              (setq n2 n1
+                    n1 nil)
+              (throw 'done t))
+            ;; at this point, `str' is [sep]<num>[expr][fmt]
+            (if (string-match "^\\([^\n0-9]*?\\)\\(-?[0-9]+\\)\\(.*\\)?$" str)
+                (setq s1 (match-string-no-properties 1 str)
+                      n2 (match-string-no-properties 2 str)
+                      str (match-string-no-properties 3 str))
+              ;; here there's only n2 that was matched as n1
+              (setq n2 n1
+                    n1 nil))
+            ;; match expr_fmt
+            (unless (zerop (length str))
+              (if (or (string-match "^\\([^\n%|]*?\\)|\\([^\n]*\\)?$" str)
+                      (string-match "^\\([^\n%|]*?\\)\\(%[^\n]*\\)?$" str))
+                  (progn
+                    (setq expr (tiny-tokenize (match-string-no-properties 1 str)))
+                    (setq fmt (match-string-no-properties 2 str)))
+                (error "Couldn't match %s" str)))
+            t)
+      (when (equal expr "")
+        (setq expr nil))
+      (list n1 s1 n2 expr fmt))))
 
+;; TODO: check for arity: this doesn't work: exptxy
 (defun tiny-tokenize (str)
-  (ignore-errors
-    (let ((i 0)
-          (j 1)
-          (len (length str))
-          sym
-          s
-          out
-          (n-paren 0)
-          (expect-fun t))
-      (while (< i len)
-        (setq s (substring str i j))
-        (when (cond
-                ((string= s "x")
-                 (push s out)
-                 (push " " out))
-                ((string= s "y")
-                 (push s out)
-                 (push " " out))
-                ((string= s " ")
-                 (error "unexpected \" \""))
-                ((string= s "?")
-                 (setq s (format "%s" (read (substring str i (incf j)))))
-                 (push s out)
-                 (push " " out))
-                ((string= s ")")
-                 ;; expect a close paren only if it's necessary
-                 (if (>= n-paren 2)
-                     (decf n-paren)
-                   (error "unexpected \")\""))
-                 (pop out)
-                 (push ") " out))
-                ((string= s "(")
-                 ;; open paren is used sometimes
-                 ;; when there are numbers in the expression
-                 (incf n-paren)
-                 (push "(" out))
-                ((progn (setq sym (intern-soft s))
-                        (cond
-                          ;; general functionp
-                          ((not (eq t (help-function-arglist sym)))
-                           (setq expect-fun)
-                           ;; (when (zerop n-paren)
-                           ;;   (push "(" out))
-                           (push "(" out)
-                           (incf n-paren))
-                          ((and sym (boundp sym) (not expect-fun))
-                           t)))
-                 (push s out)
-                 (push " " out))
-                ((numberp (read s))
-                 (let* ((num (string-to-number (substring str i)))
-                        (num-s (format "%s" num)))
-                   (push num-s out)
-                   (push " " out)
-                   (setq j (+ i (length num-s)))))
-                (t
-                 (incf j)
-                 nil))
-          (setq i j)
-          (setq j (1+ i))))
-      ;; last space
-      (pop out)
-      (concat
-       (apply #'concat (nreverse out))
-       (make-string n-paren ?\))))))
+  "Transform shorthand Lisp expression STR to proper Lisp."
+  (if (equal str "")
+      ""
+    (ignore-errors
+      (let ((i 0) (j 1)
+            (len (length str))
+            sym s out allow-spc
+            (n-paren 0)
+            (expect-fun t))
+        (while (< i len)
+          (setq s (substring str i j))
+          (when (cond
+                  ((string= s "x")
+                   (push s out)
+                   (push " " out))
+                  ((string= s " ")
+                   (if allow-spc
+                       t
+                     (error "Unexpected \" \"")))
+                  ;; special syntax to read chars
+                  ((string= s "?")
+                   (setq s (format "%s" (read (substring str i (incf j)))))
+                   (push s out)
+                   (push " " out))
+                  ((string= s ")")
+                   ;; expect a close paren only if it's necessary
+                   (if (>= n-paren 0)
+                       (decf n-paren)
+                     (error "Unexpected \")\""))
+                   (when (string= (car out) " ")
+                     (pop out))
+                   (push ")" out)
+                   (push " " out))
+                  ((string= s "(")
+                   ;; open paren is used sometimes
+                   ;; when there are numbers in the expression
+                   (setq expect-fun t)
+                   (incf n-paren)
+                   (push "(" out))
+                  ((progn (setq sym (intern-soft s))
+                          (cond
+                            ;; general functionp
+                            ((not (eq t (help-function-arglist sym)))
+                             (setq expect-fun nil)
+                             (setq allow-spc t)
+                             ;; (when (zerop n-paren) (push "(" out))
+                             (unless (equal (car out) "(")
+                               (push "(" out)
+                               (incf n-paren))
+                             t)
+                            ((and sym (boundp sym) (not expect-fun))
+                             t)))
+                   (push s out)
+                   (push " " out))
+                  ((numberp (read s))
+                   (let* ((num (string-to-number (substring str i)))
+                          (num-s (format "%s" num)))
+                     (push num-s out)
+                     (push " " out)
+                     (setq j (+ i (length num-s)))))
+                  (t
+                   (incf j)
+                   nil))
+            (setq i j)
+            (setq j (1+ i))))
+        ;; last space
+        (when (string= (car out) " ")
+          (pop out))
+        (concat
+         (apply #'concat (nreverse out))
+         (make-string n-paren ?\)))))))
+
+(defun tiny-date (s &optional shift)
+  "Return date representation of S.
+`org-mode' format is used.
+Optional SHIFT argument is the integer amount of days to shift."
+  (let* ((ct (decode-time (current-time)))
+         (time (apply 'encode-time
+                      (org-read-date-analyze
+                       s nil
+                       ct)))
+         (formatter
+          (if (equal (cl-subseq ct 1 3)
+                     (cl-subseq (decode-time time) 1 3))
+              "%Y-%m-%d %a"
+            "%Y-%m-%d %a %H:%M")))
+    (when shift
+      (setq time (time-add time (days-to-time shift))))
+    (format-time-string formatter time)))
 
 (provide 'tiny)
 ;;; tiny.el ends here