diff options
Diffstat (limited to 'emacs.d')
-rw-r--r-- | emacs.d/lisp/template-simple.el | 418 | ||||
-rw-r--r-- | emacs.d/templates/TEMPLATE.py.tpl | 4 |
2 files changed, 422 insertions, 0 deletions
diff --git a/emacs.d/lisp/template-simple.el b/emacs.d/lisp/template-simple.el new file mode 100644 index 0000000..60e268b --- /dev/null +++ b/emacs.d/lisp/template-simple.el @@ -0,0 +1,418 @@ +;;; template-simple.el --- Simple template functions and utils + +;; Copyright (C) 2007 Ye Wenbin + +;; Author: Ye Wenbin <wenbinye@gmail.com> +;; Maintainer: Ye Wenbin <wenbinye@gmail.com> +;; Created: 21 Dec 2007 +;; Version: 0.01 +;; Keywords: tools, convenience +;; +;; This file is part of PDE (Perl Development Environment). +;; But it is useful for generic programming. + +;; 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 2, 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 GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; * Why not template? +;; A template.el is already exists, and it does everything well. +;; But I hate to read the code to use it in my extension. I need +;; simple thing to get work done. template-simple is designed +;; to compatible with template. The two useful features are +;; implemented, expand template in file and update file header. +;; And with addtional, you can use this to write simple skeleton +;; and tempo template. Or you can implement other expand function +;; to expand the parsed templates. +;; +;; * Where to use it? +;; You can use it with autoinsert, tempo, skeleton or other related +;; extensions. I hope this help you to write template for tempo or +;; skeleton without any knowledge with emacs lisp. +;; +;; * Tips +;; If you don't like the (>>> and <<<) for open and close paren, +;; you can overwrite it like file variable in template, for example: +;; +;; (template-simple-expand +;; ";; -*- template-parens: (\"{\" . \"}\"); template-expand-function: template-tempo-expand -*- +;; (defun {p} ({p}) +;; \"{p}\" +;; {p} +;; )") +;; +;; The template is expand by template-tempo-expand and use {} as paren inside +;; template string. + +;;; Dependencies: +;; no extra libraries is required + +;;; Installation: +;; Put this file into your load-path and the following into your ~/.emacs: +;; (require 'template-simple) +;; +;;; Code: +(eval-when-compile + (require 'cl)) + + +;;; Customizable variables +(defgroup template-simple nil + "Simple template functions and utils" + :group 'abbrev + :group 'convenience + :group 'pde) + +(defcustom template-directory-list + (append '("~/.templates/") + (if (boundp 'auto-insert-directory) + (list auto-insert-directory))) + "*Directory for lookup template files." + :type '(repeat directory) + :group 'template-simple) + +(defcustom template-default-alist + '(("dir" (file-name-directory template-file-name)) + ("file" (file-name-nondirectory template-file-name)) + ("file-sans" (file-name-sans-extension + (file-name-nondirectory template-file-name))) + ("file-ext" (file-name-extension + (file-name-nondirectory template-file-name))) + ("file-upcase" (upcase (file-name-sans-extension + (file-name-nondirectory template-file-name)))) + ("date" (format-time-string template-date-format)) + ("cdate" (let ((system-time-locale "C")) + (format-time-string template-cdate-format))) + ("iso-date" (format-time-string "%Y-%m-%d")) + ("vc-date" (prog2 + (set-time-zone-rule "UTC") + (format-time-string "%Y/%m/%d %T") + (set-time-zone-rule nil))) + ("year" (format-time-string "%Y")) + ("time" (format-time-string template-time-format)) + ("author" (or user-mail-address + (concat (user-login-name) "@" (system-name)))) + ("user-name" user-full-name) + ("login-name" user-login-name) + ("host-addr" (or mail-host-address (system-name)))) + "*Default expansion list" + :type '(alist :key-type string :value-type sexp) + :group 'template-simple) + +(defcustom template-date-format "%d %b %Y" + "*Date format for date in `template-default-alist'." + :type 'string + :group 'template-simple) + +(defcustom template-cdate-format "%d %b %Y" + "*Date format for date with `system-time-locale' has value \"C\"" + :type 'string + :group 'template-simple) + +(defcustom template-time-format "%T" + "*Time format for time in `template-time-format'." + :type 'string + :group 'template-simple) + +(defcustom template-header-regexp + '(("@(#)\\([^ \t\n]+\\)" . 1) + ("^\\([^ \t]\\{,3\\}[ \t]+\\)\\([^ \t\n][^ \t\n]*\\)[ \t]+--" . 2)) + "Alist of regexps matching the file name in the header. +`car' is a regexp to match file header, `cdr' indicate which part +to replace with the file name." + :type '(alist :key-type regexp :value-type integer) + :group 'template-simple) + +(defcustom template-query t + "*Non-nil means ask user before expand template or update header." + :type 'boolean + :group 'template-simple) + +(defvar template-skeleton-alist + '(("point" _)) + "*Translation between parsed template to skeleton element.") + +(defvar template-tempo-alist + '(("point" p) + ("p" p)) + "*Translation between parsed template to tempo element.") + + +;;; Internal variables +(defvar template-expand-function 'template-tempo-expand + "Functions to expand parsed template.") +(put 'template-expand-function 'safe-local-variable 'functionp) + +(defvar template-parens (cons "(>>>" "<<<)") + "Open and close parenthesis.") +(put 'template-parens 'safe-local-variable 'consp) + +(defvar template-file-name nil + "Internal variable: full name of the file when template expanded.") + + +;;; Core functions +(defun template-compile () + "Parse current buffer to parsed template. +The template can have a file variable line, which can override default +global variable `template-parens' and `template-expand-function'. +The program fragment is surrounded by `template-parens', the escape +char `\\' is used for escape the open parenthesis. +The text in the parentheseses are `read' into a list. For example: + (template-compile-string + \";; -*- template-parens: (\\\"{\\\" . \\\"}\\\") -*- + (defun {p} ({p}) + \\\"{(read-from-minibuffer \\\"Document: \\\")}\\\" + ) + \") + + is compile to a list like this: + (\" (defun \" (p) \" (\" (p) \") + \\\"\" ((read-from-minibuffer \"Document: \")) \"\\\" + ) + \") +" + (save-excursion + (let ((vars (hack-local-variables-prop-line)) + (beg (point-min)) + (template-parens template-parens) + open close templates escape) + (goto-char (point-min)) + (when vars + (mapc (lambda (var) (set (car var) (cdr var))) vars) + ;; delete the file variable line for template-simple only + (forward-line 1) + (delete-region (point-min) (point))) + (setq open (regexp-quote (car template-parens)) + close (regexp-quote (cdr template-parens))) + (while (re-search-forward open nil t) + (setq escape nil) + (when (looking-back (concat "\\([^\\]\\|\\`\\)\\([\\]+\\)" open)) + (setq escape (match-string 2)) + (replace-match (substring escape 0 (/ (length escape) 2)) + nil t nil 2) + (goto-char (match-end 0)) + ;; if length of escape is odd, just a normal string, continue + (setq escape (= (% (length escape) 2) 1))) + (unless escape + ;; parse template expansion + (let ((expansion-start (point)) + state done forms) + (push (buffer-substring-no-properties beg (- (point) (length (car template-parens)))) + templates) + (with-syntax-table emacs-lisp-mode-syntax-table + (while (not done) + (if (re-search-forward close nil t) + (progn + (setq state (parse-partial-sexp expansion-start (point))) + (if (nth 3 state) ; if inside a string, continue + () + (setq done t))) + (error "Unmatch parentheses for line %d" + (line-number-at-pos expansion-start))))) + (setq beg (point)) + (save-excursion + (save-restriction + (narrow-to-region expansion-start + (- beg (length (cdr template-parens)))) + (goto-char (point-min)) + (while (not (eobp)) + (push (read (current-buffer)) forms)))) + (push (nreverse forms) templates)))) + (push (buffer-substring-no-properties (point) (point-max)) templates) + (nreverse templates)))) + +(defun template-compile-string (str) + (with-temp-buffer + (insert str) + (template-compile))) + + +;;; Expand functions +(defun template-normal-name (name) + "Convert all kinds of symbol name to standard name." + (replace-regexp-in-string "_" "-" (downcase (symbol-name name)))) + +(defun template-expansion (elem) + "Lookup name in `template-default-alist'. +If the elem is a list with length more" + (if (stringp elem) + (list elem) + (if (= (length elem) 1) + (progn + (setq elem (car elem)) + (list + (cond ((symbolp elem) + (or (cadr (assoc (template-normal-name elem) + template-default-alist)) + (and (boundp elem) (symbol-value elem)) + `(or (cadr (assoc (template-normal-name ',elem) + template-default-alist)) + (let ((str (read-from-minibuffer (format "Replace '%S' with: " ',elem)))) + (add-to-list 'template-default-alist + (list (template-normal-name ',elem) str)) + str)))) + ;; ignore integer + ((integerp elem) "") + (t elem)))) + elem))) + +(defmacro define-template-expander (name alist &rest body) + "Define a new type of `template-expand-function'. +NAME is used to create a function template-<NAME>-expand. +ALIST can be a symbol or a form to return a list of symbol table add +to template-default-alist. +BODY is the code to expand and insert the template. the value of +variable TEMPLATE is the translated template. The element of parsed +template is translated by `template-expansion'" + (declare (debug t) (indent 2)) + `(defun ,(intern (format "template-%s-expand" name)) (template) + ,(format "Expand template by %s" name) + (let ((template-default-alist + (append ,alist template-default-alist)) + ;; save global variable + (template-expand-function + ',(intern (format "template-%s-expand" name)))) + (if (stringp template) + (setq template (template-compile-string template))) + (setq template (apply 'append (mapcar 'template-expansion template))) + ,@body))) + +(define-template-expander skeleton template-skeleton-alist + (skeleton-insert (cons nil template))) + +(autoload 'tempo-insert-template "tempo") +(define-template-expander tempo template-tempo-alist + (let ((tempo-template template)) + (tempo-insert-template 'tempo-template nil))) + +;;; Exported commands +(defun template-derive-template () + "Derive which template file should use for current buffer." + (when buffer-file-name + (let ((ext (or (file-name-extension buffer-file-name) + (file-name-nondirectory buffer-file-name)))) + (locate-file "TEMPLATE." template-directory-list + (list ext (concat ext ".tpl")))))) + +;; (defun template-include (name) +;; (let ((file (locate-file name template-directory-list))) +;; (when file (template-simple-expand-template file)))) + +;;;###autoload +(defun template-simple-expand-template (file) + "Expand template in file. +Parse the template to parsed templates with `template-compile'. +Use `template-expand-function' to expand the parsed template." + (interactive + (list + (let ((def (template-derive-template)) + file) + (and def (setq def (file-name-nondirectory def))) + (setq file + (completing-read + (if def + (format "Insert template(default %s): " def) + "Insert template: ") + (apply 'append (mapcar 'directory-files template-directory-list)) + nil t nil nil def)) + (locate-file file template-directory-list)))) + (let ((template-expand-function template-expand-function)) + (template-simple-expand + (with-temp-buffer + (insert-file-contents file) + (template-compile))))) + +;;;###autoload +(defun template-simple-expand (template) + "Expand string TEMPLATE. +Parse the template to parsed templates with `template-compile'. +Use `template-expand-function' to expand the parsed template." + ;; in case the template-expand-function is overide in template + (let ((template-file-name (or buffer-file-name + (concat (file-name-as-directory default-directory) + (buffer-name)))) + (template-expand-function template-expand-function) + err) + (condition-case err + (progn + (if (stringp template) + (setq template (template-compile-string template))) + (funcall template-expand-function template)) + (error (message "%s: %s" (car err) (cdr err)))))) + +;;; Commands for write template to string +(defun template-kill-ring-save (beg end) + "Stringfy text in region, `yank' to see it." + (interactive "r") + (kill-new (format "%S" (buffer-substring-no-properties beg end)) nil)) + +;;; Provide addtional command in template.el +(defun template-simple-update-header () + (interactive) + (when buffer-file-name + (save-excursion + (goto-char (point-min)) + (let ((end (progn (forward-line 3) (point))) + ; check only first 3 lines + (alist template-header-regexp) + (fn (file-name-sans-versions + (file-name-nondirectory buffer-file-name))) + case-fold-search) + (while alist + (goto-char (point-min)) + (if (re-search-forward (caar alist) end t) + (progn + (when (not (string= (match-string (cdar alist)) fn)) + (if (or (null template-query) + (y-or-n-p (format "Update file header %s to %s? " + (match-string (cdar alist)) + fn))) + (replace-match fn nil t nil (cdar alist)))) + (setq alist nil)) + (setq alist (cdr alist))))))) + ;; return nil for calling other functions + nil) +;; Hope auto-insert can add a test for template-derive-template +(defun template-auto-insert () + (and (not buffer-read-only) + (or (eq this-command 'template-auto-insert) + (and (bobp) (eobp))) + (let ((file (template-derive-template))) + (when file + (switch-to-buffer (current-buffer)) + (if (or (null template-query) + (y-or-n-p (format "Use template %s? " file))) + (template-simple-expand-template file))))) + nil) + +(if (boundp 'write-file-functions) + (add-hook 'write-file-functions 'template-simple-update-header) + (add-hook 'write-file-hooks 'template-simple-update-header)) + +(let ((hook (if (boundp 'find-file-hook) + 'find-file-hook + 'find-file-hooks))) + ;; make template-auto-insert the last, so session history + ;; will not affect point set by template + (add-hook hook 'template-auto-insert t) + ;; make auto-insert lower priority + (when (memq 'auto-insert (symbol-value hook)) + (remove-hook hook 'auto-insert) + (add-hook hook 'auto-insert t))) + +(provide 'template-simple) +;;; template-simple.el ends here diff --git a/emacs.d/templates/TEMPLATE.py.tpl b/emacs.d/templates/TEMPLATE.py.tpl new file mode 100644 index 0000000..27253c1 --- /dev/null +++ b/emacs.d/templates/TEMPLATE.py.tpl @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# date: (>>>DATE<<<) + +(>>>POINT<<<) |