From: Oleh Krehel Date: Sun, 22 Mar 2015 17:37:26 +0000 (+0100) Subject: Add 'packages/tiny/' from commit '159c3f74e75970808b83fe4b732f180cb76872a3' X-Git-Url: https://code.delx.au/gnu-emacs-elpa/commitdiff_plain/3f035ad4f8ef0bf6b8e60a819ec97d6f7f6ea5b5?hp=302a16a15bbaf3842246293a27c77ba2fd9a56e1 Add 'packages/tiny/' from commit '159c3f74e75970808b83fe4b732f180cb76872a3' git-subtree-dir: packages/tiny git-subtree-mainline: 302a16a15bbaf3842246293a27c77ba2fd9a56e1 git-subtree-split: 159c3f74e75970808b83fe4b732f180cb76872a3 --- diff --git a/packages/tiny/.travis.yml b/packages/tiny/.travis.yml new file mode 100644 index 000000000..fe3ab71e8 --- /dev/null +++ b/packages/tiny/.travis.yml @@ -0,0 +1,20 @@ +language: emacs-lisp +before_install: + # PPA for stable Emacs packages + - sudo add-apt-repository -y ppa:cassou/emacs + # PPA for Emacs nightlies + - sudo add-apt-repository -y ppa:ubuntu-elisp/ppa + # Update and install the Emacs for our environment + - sudo apt-get update -qq + - sudo apt-get install -qq -yy ${EMACS}-nox ${EMACS}-el + # Install cask dependencies + - curl -fsSLo /tmp/cask-master.zip https://github.com/cask/cask/archive/master.zip + - sudo unzip -qq -d /opt /tmp/cask-master.zip + - sudo ln -sf /opt/cask-master/bin/cask /usr/local/bin/cask + - cask +env: + - EMACS=emacs24 + - EMACS=emacs-snapshot +script: + - emacs --version + - make test diff --git a/packages/tiny/Cask b/packages/tiny/Cask new file mode 100644 index 000000000..5336cec6a --- /dev/null +++ b/packages/tiny/Cask @@ -0,0 +1,5 @@ +(source gnu) +(source melpa) + +(development + (depends-on "undercover")) diff --git a/packages/tiny/Makefile b/packages/tiny/Makefile new file mode 100644 index 000000000..633223880 --- /dev/null +++ b/packages/tiny/Makefile @@ -0,0 +1,18 @@ +EMACS ?= emacs +CASK_EXEC ?= cask exec + +all: test + +test: clean-elc + ${MAKE} unit + +unit: + ${CASK_EXEC} ${EMACS} -Q -batch -l tiny-test.el -l tiny.el --eval "(ert t)" + +compile: + ${CASK_EXEC} ${EMACS} -Q -batch -f batch-byte-compile tiny.el + +clean-elc: + rm -f f.elc + +.PHONY: all test diff --git a/packages/tiny/README.md b/packages/tiny/README.md new file mode 100644 index 000000000..ac9e69049 --- /dev/null +++ b/packages/tiny/README.md @@ -0,0 +1,98 @@ +[![Build Status](https://travis-ci.org/abo-abo/tiny.svg?branch=master)](https://travis-ci.org/abo-abo/tiny) +[![Coverage Status](https://coveralls.io/repos/abo-abo/tiny/badge.svg?branch=master)](https://coveralls.io/r/abo-abo/tiny?branch=master) + +### Main idea: + +This is an alternative to inserting numeric ranges with macros (i.e. `F3 F3`). +The advantages are: + +1. Brevity: consider `F3 F3 SPC M-1 M-0 F4` vs. `m10 C-;`. +2. Much better undo context: a single `C-_` will undo the whole thing + and allow you to edit the code. With macros you'd have to undo multiple + times and restart from scratch, instead of tweaking what you just invoked. +3. The ability to insert the same number several times in a single iteration, + and transform it with `format`-style expressions + e.g. `m6\n15%s=0%o=0x%x` will expand to + + 6=06=0x6 + 7=07=0x7 + 8=010=0x8 + 9=011=0x9 + 10=012=0xa + 11=013=0xb + 12=014=0xc + 13=015=0xd + 14=016=0xe + 15=017=0xf +4. Last but not least, the ability to transform the number with lisp expressions. + For instance: + 1. `m5 10*xx` -> `25 36 49 64 81 100` + 2. `m5 10*xx|0x%x` -> `0x19 0x24 0x31 0x40 0x51 0x64` + 3. `m10+x?a%c` -> `a b c d e f g h i j k` + 4. `m10+x?A%c` -> `A B C D E F G H I J K` + 5. `m97,105stringx` -> `a,b,c,d,e,f,g,h,i` + 6. `m97,102stringxupcasex` -> `aA,bB,cC,dD,eE,fF` + 7. `m,3|%(+ x x) and %(* x x) and %s` -> `0 and 0 and 0,2 and 1 and 1,4 and 4 and 2,6 and 9 and 3,8 and 16 and 4,10 and 25 and 5` + +### Use in conjunction with `org-mode`: + + m1\n14|*** TODO http://emacsrocks.com/e%02d.html + + *** TODO http://emacsrocks.com/e01.html + *** TODO http://emacsrocks.com/e02.html + *** TODO http://emacsrocks.com/e03.html + *** TODO http://emacsrocks.com/e04.html + *** TODO http://emacsrocks.com/e05.html + *** TODO http://emacsrocks.com/e06.html + *** TODO http://emacsrocks.com/e07.html + *** TODO http://emacsrocks.com/e08.html + *** TODO http://emacsrocks.com/e09.html + *** TODO http://emacsrocks.com/e10.html + *** TODO http://emacsrocks.com/e11.html + *** TODO http://emacsrocks.com/e12.html + *** TODO http://emacsrocks.com/e13.html + *** TODO http://emacsrocks.com/e14.html + +You can even schedule and deadline: + + m\n8|**** TODO Learning from Data Week %(+ x 2) \nSCHEDULED: <%(date "Oct 7" (* x 7))> DEADLINE: <%(date "Oct 14" (* x 7))> + + **** TODO Learning from Data Week 2 + SCHEDULED: <2013-10-07 Mon> DEADLINE: <2013-10-14 Mon> + **** TODO Learning from Data Week 3 + SCHEDULED: <2013-10-14 Mon> DEADLINE: <2013-10-21 Mon> + **** TODO Learning from Data Week 4 + SCHEDULED: <2013-10-21 Mon> DEADLINE: <2013-10-28 Mon> + **** TODO Learning from Data Week 5 + SCHEDULED: <2013-10-28 Mon> DEADLINE: <2013-11-04 Mon> + **** TODO Learning from Data Week 6 + SCHEDULED: <2013-11-04 Mon> DEADLINE: <2013-11-11 Mon> + **** TODO Learning from Data Week 7 + SCHEDULED: <2013-11-11 Mon> DEADLINE: <2013-11-18 Mon> + **** TODO Learning from Data Week 8 + SCHEDULED: <2013-11-18 Mon> DEADLINE: <2013-11-25 Mon> + **** TODO Learning from Data Week 9 + SCHEDULED: <2013-11-25 Mon> DEADLINE: <2013-12-02 Mon> + **** TODO Learning from Data Week 10 + SCHEDULED: <2013-12-02 Mon> DEADLINE: <2013-12-09 Mon> + +Here's how to schedule a task that repeats Monday through Friday at 10:00, every week: + + m0\n4|** TODO Something work-related\nSCHEDULED: <%(date "mon" x) 10:00 +1w> + + ** TODO Something work-related + SCHEDULED: <2013-11-04 Mon 10:00 +1w> + ** TODO Something work-related + SCHEDULED: <2013-11-05 Tue 10:00 +1w> + ** TODO Something work-related + SCHEDULED: <2013-11-06 Wed 10:00 +1w> + ** TODO Something work-related + SCHEDULED: <2013-11-07 Thu 10:00 +1w> + ** TODO Something work-related + SCHEDULED: <2013-11-08 Fri 10:00 +1w> + +### Setup +In `~/.emacs`: + + (require 'tiny) + (tiny-setup-default) diff --git a/packages/tiny/tiny-test.el b/packages/tiny/tiny-test.el new file mode 100644 index 000000000..dc9c3b958 --- /dev/null +++ b/packages/tiny/tiny-test.el @@ -0,0 +1,214 @@ +;;; tiny-test.el --- Tests for Tiny + +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; Author: Oleh Krehel + +;; 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 of the License, or +;; (at your option) any later version. + +;; 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. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +(when (require 'undercover nil t) + (undercover "tiny.el")) + +(require 'tiny nil t) + +(defun with-text-value (txt fn &rest args) + "Return the result of (apply 'FN ARGS), in a temp buffer with TXT, +with point at the end of TXT." + (with-temp-buffer + (insert txt) + (apply fn args))) + +(ert-deftest tiny-mapconcat-parse () + (should (equal (with-text-value "m10" #'tiny-mapconcat-parse) + '(nil nil "10" nil nil))) + (should (equal (with-text-value "m5%x" #'tiny-mapconcat-parse) + '(nil nil "5" nil "%x"))) + (should (equal (with-text-value "m5 10" #'tiny-mapconcat-parse) + '("5" " " "10" nil nil))) + (should (equal (with-text-value "m5,10" #'tiny-mapconcat-parse) + '("5" "," "10" nil nil))) + (should (equal (with-text-value "m5 10*xx" #'tiny-mapconcat-parse) + '("5" " " "10" "(* x x)" nil))) + (should (equal (with-text-value "m5 10*xx%x" #'tiny-mapconcat-parse) + '("5" " " "10" "(* x x)" "%x"))) + (should (equal (with-text-value "m5 10*xx|0x%x" #'tiny-mapconcat-parse) + '("5" " " "10" "(* x x)" "0x%x"))) + (should (equal (with-text-value "m25+x?a%c" #'tiny-mapconcat-parse) + '(nil nil "25" "(+ x 97)" "%c"))) + (should (equal (with-text-value "m25+x?A%c" #'tiny-mapconcat-parse) + '(nil nil "25" "(+ x 65)" "%c"))) + (should (equal (with-text-value "m97,122stringx" #'tiny-mapconcat-parse) + '("97" "," "122" "(string x)" nil))) + (should (equal (with-text-value "m97,122stringxx" #'tiny-mapconcat-parse) + '("97" "," "122" "(string x x)" nil))) + (should (equal (with-text-value "m97,120stringxupcasex" #'tiny-mapconcat-parse) + '("97" "," "120" "(string x (upcase x))" nil))) + (should (equal (with-text-value "m97,120stringxupcasex)x" #'tiny-mapconcat-parse) + '("97" "," "120" "(string x (upcase x) x)" nil))) + (should (equal (with-text-value "m\\n;; 10|%(+ x x) and %(* x x) and %s" #'tiny-mapconcat-parse) + '(nil "\\n;; " "10" nil "%(+ x x) and %(* x x) and %s"))) + (should (equal (with-text-value "m10|%0.2f" #'tiny-mapconcat-parse) + '(nil nil "10" nil "%0.2f")))) + +(ert-deftest tiny-extract-sexps () + (should (equal (tiny-extract-sexps "expr1 %(+ x x), nothing %% char %c, hex %x, and expr2 %(* x x), float %0.2f and sym %s") + '("expr1 %s, nothing %% char %c, hex %x, and expr2 %s, float %0.2f and sym %s" + "(+ x x)" nil nil "(* x x)" nil nil))) + (should (equal (tiny-extract-sexps "m1\n5| (%c(+ x ?a -1)) %0.1f(* x 0.2)") + '("m1 +5| (%c) %0.1f" "(+ x ?a -1)" "(* x 0.2)")))) + +(ert-deftest tiny-mapconcat () + (should (equal (with-text-value "m10" (lambda()(eval (read (tiny-mapconcat))))) + "0 1 2 3 4 5 6 7 8 9 10")) + (should (equal (with-text-value "m5 10" (lambda()(eval (read (tiny-mapconcat))))) + "5 6 7 8 9 10")) + (should (equal (with-text-value "m5 10*xx" (lambda()(eval (read (tiny-mapconcat))))) + "25 36 49 64 81 100")) + (should (equal (with-text-value "m5 10*xx%x" (lambda()(eval (read (tiny-mapconcat))))) + "19 24 31 40 51 64")) + (should (equal (with-text-value "m5 10*xx|0x%x" (lambda()(eval (read (tiny-mapconcat))))) + "0x19 0x24 0x31 0x40 0x51 0x64")) + (should (equal (with-text-value "m25+x?a%c" (lambda()(eval (read (tiny-mapconcat))))) + "a b c d e f g h i j k l m n o p q r s t u v w x y z")) + (should (equal (with-text-value "m25+x?A%c" (lambda()(eval (read (tiny-mapconcat))))) + "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z")) + (should (equal (with-text-value "m97,122(string x)" (lambda()(eval (read (tiny-mapconcat))))) + "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z")) + (should (equal (with-text-value "m97,122stringxx" (lambda()(eval (read (tiny-mapconcat))))) + "aa,bb,cc,dd,ee,ff,gg,hh,ii,jj,kk,ll,mm,nn,oo,pp,qq,rr,ss,tt,uu,vv,ww,xx,yy,zz")) + (should (equal (with-text-value "m97,120stringxupcasex" (lambda()(eval (read (tiny-mapconcat))))) + "aA,bB,cC,dD,eE,fF,gG,hH,iI,jJ,kK,lL,mM,nN,oO,pP,qQ,rR,sS,tT,uU,vV,wW,xX")) + (should (equal (with-text-value "m97,120stringxupcasex)x" (lambda()(eval (read (tiny-mapconcat))))) + "aAa,bBb,cCc,dDd,eEe,fFf,gGg,hHh,iIi,jJj,kKk,lLl,mMm,nNn,oOo,pPp,qQq,rRr,sSs,tTt,uUu,vVv,wWw,xXx")) + (should (equal (with-text-value "m10|%(+ x x) and %(* x x) and %s" (lambda()(eval (read (tiny-mapconcat))))) + "0 and 0 and 0 2 and 1 and 1 4 and 4 and 2 6 and 9 and 3 8 and 16 and 4 10 and 25 and 5 12 and 36 and 6 14 and 49 and 7 16 and 64 and 8 18 and 81 and 9 20 and 100 and 10")) + (should (equal (with-text-value "m10*2+3x" (lambda()(eval (read (tiny-mapconcat))))) + "6 8 10 12 14 16 18 20 22 24 26")) + (should (equal (with-text-value "m10expx" (lambda()(eval (read (tiny-mapconcat))))) + "1.0 2.718281828459045 7.38905609893065 20.085536923187668 54.598150033144236 148.4131591025766 403.4287934927351 1096.6331584284585 2980.9579870417283 8103.083927575384 22026.465794806718")) + (should (equal (with-text-value "m5 20expx%014.2f" (lambda()(eval (read (tiny-mapconcat))))) + "00000000148.41 00000000403.43 00000001096.63 00000002980.96 00000008103.08 00000022026.47 00000059874.14 00000162754.79 00000442413.39 00001202604.28 00003269017.37 00008886110.52 00024154952.75 00065659969.14 00178482300.96 00485165195.41")) + (should (equal (with-text-value "m, 7|0x%02x" (lambda()(eval (read (tiny-mapconcat))))) + "0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07")) + (should (equal (with-text-value "m1\\n14|*** TODO http://emacsrocks.com/e%02d.html" (lambda()(eval (read (tiny-mapconcat))))) + "*** TODO http://emacsrocks.com/e01.html +*** TODO http://emacsrocks.com/e02.html +*** TODO http://emacsrocks.com/e03.html +*** TODO http://emacsrocks.com/e04.html +*** TODO http://emacsrocks.com/e05.html +*** TODO http://emacsrocks.com/e06.html +*** TODO http://emacsrocks.com/e07.html +*** TODO http://emacsrocks.com/e08.html +*** TODO http://emacsrocks.com/e09.html +*** TODO http://emacsrocks.com/e10.html +*** TODO http://emacsrocks.com/e11.html +*** TODO http://emacsrocks.com/e12.html +*** TODO http://emacsrocks.com/e13.html +*** TODO http://emacsrocks.com/e14.html")) + (should (equal (with-text-value "m1\\n10|convert img%s.jpg -monochrome -resize 50%% -rotate 180 img%s_mono.pdf" (lambda()(eval (read (tiny-mapconcat))))) + "convert img1.jpg -monochrome -resize 50% -rotate 180 img1_mono.pdf +convert img2.jpg -monochrome -resize 50% -rotate 180 img2_mono.pdf +convert img3.jpg -monochrome -resize 50% -rotate 180 img3_mono.pdf +convert img4.jpg -monochrome -resize 50% -rotate 180 img4_mono.pdf +convert img5.jpg -monochrome -resize 50% -rotate 180 img5_mono.pdf +convert img6.jpg -monochrome -resize 50% -rotate 180 img6_mono.pdf +convert img7.jpg -monochrome -resize 50% -rotate 180 img7_mono.pdf +convert img8.jpg -monochrome -resize 50% -rotate 180 img8_mono.pdf +convert img9.jpg -monochrome -resize 50% -rotate 180 img9_mono.pdf +convert img10.jpg -monochrome -resize 50% -rotate 180 img10_mono.pdf")) + (should (equal (with-text-value "m\\n;; 16list*xxx)*xx%s:%s:%s" (lambda()(eval (read (tiny-mapconcat))))) + "0:0:0 +;; 1:1:1 +;; 8:4:2 +;; 27:9:3 +;; 64:16:4 +;; 125:25:5 +;; 216:36:6 +;; 343:49:7 +;; 512:64:8 +;; 729:81:9 +;; 1000:100:10 +;; 1331:121:11 +;; 1728:144:12 +;; 2197:169:13 +;; 2744:196:14 +;; 3375:225:15 +;; 4096:256:16")) + (should (equal (with-text-value "m\\n8|**** TODO Learning from Data Week %(+ x 2)\\nSCHEDULED: <%(date \"Oct 7\" (* x 7))> DEADLINE: <%(date \"Oct 14\" (* x 7))>" + (lambda()(eval (read (tiny-mapconcat))))) + "**** TODO Learning from Data Week 2 +SCHEDULED: <2015-10-07 Wed> DEADLINE: <2015-10-14 Wed> +**** TODO Learning from Data Week 3 +SCHEDULED: <2015-10-14 Wed> DEADLINE: <2015-10-21 Wed> +**** TODO Learning from Data Week 4 +SCHEDULED: <2015-10-21 Wed> DEADLINE: <2015-10-28 Wed> +**** TODO Learning from Data Week 5 +SCHEDULED: <2015-10-28 Wed> DEADLINE: <2015-11-04 Wed> +**** TODO Learning from Data Week 6 +SCHEDULED: <2015-11-04 Wed> DEADLINE: <2015-11-11 Wed> +**** TODO Learning from Data Week 7 +SCHEDULED: <2015-11-11 Wed> DEADLINE: <2015-11-18 Wed> +**** TODO Learning from Data Week 8 +SCHEDULED: <2015-11-18 Wed> DEADLINE: <2015-11-25 Wed> +**** TODO Learning from Data Week 9 +SCHEDULED: <2015-11-25 Wed> DEADLINE: <2015-12-02 Wed> +**** TODO Learning from Data Week 10 +SCHEDULED: <2015-12-02 Wed> DEADLINE: <2015-12-09 Wed>")) + (should (string= (with-text-value "m\\n4|**** TODO Classical Mechanics Week %(+ x 5)\\nSCHEDULED: <%(date \"Oct 15\" (* x 7))> DEADLINE: <%(date \"Oct 23\" (* x 7))>" + (lambda()(eval (read (tiny-mapconcat))))) + "**** TODO Classical Mechanics Week 5 +SCHEDULED: <2015-10-15 Thu> DEADLINE: <2015-10-23 Fri> +**** TODO Classical Mechanics Week 6 +SCHEDULED: <2015-10-22 Thu> DEADLINE: <2015-10-30 Fri> +**** TODO Classical Mechanics Week 7 +SCHEDULED: <2015-10-29 Thu> DEADLINE: <2015-11-06 Fri> +**** TODO Classical Mechanics Week 8 +SCHEDULED: <2015-11-05 Thu> DEADLINE: <2015-11-13 Fri> +**** TODO Classical Mechanics Week 9 +SCHEDULED: <2015-11-12 Thu> DEADLINE: <2015-11-20 Fri>")) + (should (string= (with-text-value "m7|%(expt 2 x)" + (lambda()(eval (read (tiny-mapconcat))))) + "1 2 4 8 16 32 64 128")) + (should (string= (with-text-value "m\\n25+?ax|(\"%c\")" + (lambda()(eval (read (tiny-mapconcat))))) + "(\"a\")\n(\"b\")\n(\"c\")\n(\"d\")\n(\"e\")\n(\"f\")\n(\"g\")\n(\"h\")\n(\"i\")\n(\"j\")\n(\"k\")\n(\"l\")\n(\"m\")\n(\"n\")\n(\"o\")\n(\"p\")\n(\"q\")\n(\"r\")\n(\"s\")\n(\"t\")\n(\"u\")\n(\"v\")\n(\"w\")\n(\"x\")\n(\"y\")\n(\"z\")"))) + +(ert-deftest tiny-replace-this-sexp () + (should (equal (with-text-value "(mapcar (lambda (x) (* x x)) '(1 2 3))" + (lambda()(goto-char 16)(tiny-replace-this-sexp)(buffer-string))) + "(1 4 9)")) + (should (equal (with-text-value "(mapcar (lambda (x) (* x x)) '(1 2 3))" + (lambda()(goto-char 2)(tiny-replace-this-sexp)(buffer-string))) + "(1 4 9)"))) + +(ert-deftest tiny-tokenize () + (should (equal (tiny-tokenize "stringxx") "(string x x)")) + (should (equal (tiny-tokenize "*2+xxx") "(* 2 (+ x x x))")) + (should (equal (tiny-tokenize "*2+xxx") "(* 2 (+ x x x))")) + (should (equal (tiny-tokenize "*2+xx)x") "(* 2 (+ x x) x)")) + (should (equal (tiny-tokenize "string x") "(string x)")) + (should (equal (tiny-tokenize "(string x)") "(string x)")) + (should (equal (tiny-tokenize "string x)") "(string x)")) + (should (equal (tiny-tokenize "stringxupcasex)x") "(string x (upcase x) x)")) + (should (equal (tiny-tokenize "(stringxupcasex)x") "(string x (upcase x) x)")) + (should (equal (tiny-tokenize "(string xupcasex)x") "(string x (upcase x) x)")) + (should (equal (tiny-tokenize "(string x upcasex)x") "(string x (upcase x) x)")) + (should (equal (tiny-tokenize "(string x upcase x) x") "(string x (upcase x) x)")) + (should (equal (tiny-tokenize "(string x (upcase x) x") "(string x (upcase x) x)")) + (should (equal (tiny-tokenize "(string x (upcase x) x)") "(string x (upcase x) x)"))) + +(provide 'tiny-test) diff --git a/packages/tiny/tiny.el b/packages/tiny/tiny.el new file mode 100644 index 000000000..78d44bbbb --- /dev/null +++ b/packages/tiny/tiny.el @@ -0,0 +1,412 @@ +;;; tiny.el --- Quickly generate linear ranges in Emacs + +;; Copyright (C) 2013-2015 Free Software Foundation, Inc. + +;; Author: Oleh Krehel +;; URL: https://github.com/abo-abo/tiny +;; Version: 0.1 +;; Keywords: convenience + +;; 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 of the License, or +;; (at your option) any later version. + +;; 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. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see + +;;; Commentary: +;; +;; 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 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. +;; +;; There's also some emphasis on the brevity of the expression to be +;; expanded: e.g. instead of typing (+ x 2), you can do +x2. +;; You can still do the full thing, but +x2 would save you some +;; key strokes. +;; +;; You can test out the following snippets +;; by positioning the point at the end of the expression +;; and calling `tiny-expand' (default shortcut is C-;): +;; +;; m10 +;; 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,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[][][Lisp expr]|[format expr] +;; +;; 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 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 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 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." + (interactive) + (let ((str (tiny-mapconcat))) + (when str + (delete-region tiny-beg tiny-end) + (insert str) + (tiny-replace-this-sexp)))) + +(defun tiny-setup-default () + "Setup shortcuts." + (global-set-key (kbd "C-;") 'tiny-expand)) + +;;;###autoload +(defun tiny-replace-this-sexp () + "Eval and replace the current sexp. +On error go up list and try again." + (interactive) + (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 + (unless (looking-back ")") + (error "Bad location")) + (let ((sexp (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-up-list () + "An `up-list' that can exit from string. +Must throw an error when can't go up further." + (interactive) + ;; 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 () + "Format output of `tiny-mapconcat-parse'. +Defaults are used in place of null values." + (let ((parsed (tiny-mapconcat-parse))) + (when parsed + (let* ((n1 (or (nth 0 parsed) "0")) + (s1 (or (nth 1 parsed) " ")) + (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 n1) (read n2)) + (format + format-expression + expr + (replace-regexp-in-string "\\\\n" "\n" fmt) + n1 + 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] + +* 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 (+ 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]*?\\)") + (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]*\\)") + (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][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][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) + "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) + (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