diff options
Diffstat (limited to 'lisp/cedet/srecode/insert.el')
-rw-r--r-- | lisp/cedet/srecode/insert.el | 983 |
1 files changed, 983 insertions, 0 deletions
diff --git a/lisp/cedet/srecode/insert.el b/lisp/cedet/srecode/insert.el new file mode 100644 index 00000000000..743c8e8e652 --- /dev/null +++ b/lisp/cedet/srecode/insert.el @@ -0,0 +1,983 @@ +;;; srecode/insert --- Insert srecode templates to an output stream. + +;;; Copyright (C) 2005, 2007, 2008, 2009 Free Software Foundation, Inc. + +;; Author: Eric M. Ludlam <zappo@gnu.org> + +;; 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 <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; Define and implements specific inserter objects. +;; +;; Manage the insertion process for a template. +;; + +(require 'srecode/compile) +(require 'srecode/find) +(require 'srecode/dictionary) + +(defvar srecode-template-inserter-point) +(declare-function srecode-overlaid-activate "srecode/fields") +(declare-function srecode-template-inserted-region "srecode/fields") + +;;; Code: + +(defcustom srecode-insert-ask-variable-method 'ask + "Determine how to ask for a dictionary value when inserting a template. +Only the ASK style inserter will query the user for a value. +Dictionary value references that ask begin with the ? character. +Possible values are: + 'ask - Prompt in the minibuffer as the value is inserted. + 'field - Use the dictionary macro name as the inserted value, + and place a field there. Matched fields change together. + +NOTE: The field feature does not yet work with XEmacs." + :group 'srecode + :type '(choice (const :tag "Ask" ask) + (cons :tag "Field" field))) + +(defvar srecode-insert-with-fields-in-progress nil + "Non-nil means that we are actively inserting a template with fields.") + +;;; INSERTION COMMANDS +;; +;; User level commands for inserting stuff. +(defvar srecode-insertion-start-context nil + "The context that was at point at the beginning of the template insertion.") + +(defun srecode-insert-again () + "Insert the previously inserted template (by name) again." + (interactive) + (let ((prev (car srecode-read-template-name-history))) + (if prev + (srecode-insert prev) + (call-interactively 'srecode-insert)))) + +;;;###autoload +(defun srecode-insert (template-name &rest dict-entries) + "Inesrt the template TEMPLATE-NAME into the current buffer at point. +DICT-ENTRIES are additional dictionary values to add." + (interactive (list (srecode-read-template-name "Template Name: "))) + (if (not (srecode-table)) + (error "No template table found for mode %s" major-mode)) + (let ((newdict (srecode-create-dictionary)) + (temp (srecode-template-get-table (srecode-table) template-name)) + (srecode-insertion-start-context (srecode-calculate-context)) + ) + (if (not temp) + (error "No Template named %s" template-name)) + (while dict-entries + (srecode-dictionary-set-value newdict + (car dict-entries) + (car (cdr dict-entries))) + (setq dict-entries (cdr (cdr dict-entries)))) + ;;(srecode-resolve-arguments temp newdict) + (srecode-insert-fcn temp newdict) + ;; Don't put code here. We need to return the end-mark + ;; for this insertion step. + )) + +(defun srecode-insert-fcn (template dictionary &optional stream skipresolver) + "Insert TEMPLATE using DICTIONARY into STREAM. +Optional SKIPRESOLVER means to avoid refreshing the tag list, +or resolving any template arguments. It is assumed the caller +has set everything up already." + ;; Perform the insertion. + (let ((standard-output (or stream (current-buffer))) + (end-mark nil)) + (unless skipresolver + ;; Make sure the semantic tags are up to date. + (semantic-fetch-tags) + ;; Resolve the arguments + (srecode-resolve-arguments template dictionary)) + ;; Insert + (if (bufferp standard-output) + ;; If there is a buffer, turn off various hooks. This will cause + ;; the mod hooks to be buffered up during the insert, but + ;; prevent tools like font-lock from fontifying mid-template. + ;; Especialy important during insertion of complex comments that + ;; cause the new font-lock to comment-color stuff after the inserted + ;; comment. + ;; + ;; I'm not sure about the motion hooks. It seems like a good + ;; idea though. + ;; + ;; Borrowed these concepts out of font-lock. + ;; + ;; I tried `combine-after-change-calls', but it did not have + ;; the effect I wanted. + (let ((start (point))) + (let ((inhibit-point-motion-hooks t) + (inhibit-modification-hooks t) + ) + (srecode--insert-into-buffer template dictionary) + ) + ;; Now call those after change functions. + (run-hook-with-args 'after-change-functions + start (point) 0) + ) + (srecode-insert-method template dictionary)) + ;; Handle specialization of the POINT inserter. + (when (and (bufferp standard-output) + (slot-boundp 'srecode-template-inserter-point 'point) + ) + (set-buffer standard-output) + (setq end-mark (point-marker)) + (goto-char (oref srecode-template-inserter-point point))) + (oset-default 'srecode-template-inserter-point point eieio-unbound) + + ;; Return the end-mark. + (or end-mark (point))) + ) + +(defun srecode--insert-into-buffer (template dictionary) + "Insert a TEMPLATE with DICTIONARY into a buffer. +Do not call this function yourself. Instead use: + `srecode-insert' - Inserts by name. + `srecode-insert-fcn' - Insert with objects. +This function handles the case from one of the above functions when +the template is inserted into a buffer. It looks +at `srecode-insert-ask-variable-method' to decide if unbound dictionary +entries ask questions or insert editable fields. + +Buffer based features related to change hooks is handled one level up." + ;; This line prevents the field archive from being let bound + ;; while the field insert tool is loaded via autoloads during + ;; the insert. + (when (eq srecode-insert-ask-variable-method 'field) + (require 'srecode-fields)) + + (let ((srecode-field-archive nil) ; Prevent field leaks during insert + (start (point)) ; Beginning of the region. + ) + ;; This sub-let scopes the 'in-progress' piece so we know + ;; when to setup the end-template. + (let ((srecode-insert-with-fields-in-progress + (if (eq srecode-insert-ask-variable-method 'field) t nil)) + ) + (srecode-insert-method template dictionary) + ) + ;; If we are not in-progress, and we insert fields, then + ;; create the end-template with fields editable area. + (when (and (not srecode-insert-with-fields-in-progress) + (eq srecode-insert-ask-variable-method 'field) ; Only if user asked + srecode-field-archive ; Only if there were fields created + ) + (let ((reg + ;; Create the field-driven editable area. + (srecode-template-inserted-region + "TEMPLATE" :start start :end (point)))) + (srecode-overlaid-activate reg)) + ) + ;; We return with 'point being the end of the template insertion + ;; area. Return value is not important. + )) + +;;; TEMPLATE ARGUMENTS +;; +;; Some templates have arguments. Each argument is assocaited with +;; a function that can resolve the inputs needed. +(defun srecode-resolve-arguments (temp dict) + "Resolve all the arguments needed by the template TEMP. +Apply anything learned to the dictionary DICT." + (srecode-resolve-argument-list (oref temp args) dict temp)) + +(defun srecode-resolve-argument-list (args dict &optional temp) + "Resolve arguments in the argument list ARGS. +ARGS is a list of symbols, such as :blank, or :file. +Apply values to DICT. +Optional argument TEMP is the template that is getting it's arguments resolved." + (let ((fcn nil)) + (while args + (setq fcn (intern-soft (concat "srecode-semantic-handle-" + (symbol-name (car args))))) + (if (not fcn) + (error "Error resolving template argument %S" (car args))) + (if temp + (condition-case nil + ;; Allow some to accept a 2nd argument optionally. + ;; They throw an error if not available, so try again. + (funcall fcn dict temp) + (wrong-number-of-arguments (funcall fcn dict))) + (funcall fcn dict)) + (setq args (cdr args))) + )) + +;;; INSERTION STACK & METHOD +;; +;; Code managing the top-level insert method and the current +;; insertion stack. +;; +(defmethod srecode-push ((st srecode-template)) + "Push the srecoder template ST onto the active stack." + (oset st active (cons st (oref st active)))) + +(defmethod srecode-pop :STATIC ((st srecode-template)) + "Pop the srecoder template ST onto the active stack. +ST can be a class, or an object." + (oset st active (cdr (oref st active)))) + +(defmethod srecode-peek :STATIC ((st srecode-template)) + "Fetch the topmost active template record. ST can be a class." + (car (oref st active))) + +(defmethod srecode-insert-method ((st srecode-template) dictionary) + "Insert the srecoder template ST." + ;; Merge any template entries into the input dictionary. + (when (slot-boundp st 'dictionary) + (srecode-dictionary-merge dictionary (oref st dictionary))) + ;; Do an insertion. + (unwind-protect + (let ((c (oref st code))) + (srecode-push st) + (srecode-insert-code-stream c dictionary)) + ;; Poping the stack is protected + (srecode-pop st))) + +(defun srecode-insert-code-stream (code dictionary) + "Insert the CODE from a template into `standard-output'. +Use DICTIONARY to resolve any macros." + (while code + (cond ((stringp (car code)) + (princ (car code))) + (t + (srecode-insert-method (car code) dictionary))) + (setq code (cdr code)))) + +;;; INSERTERS +;; +;; Specific srecode inserters. +;; The base class is from srecode-compile. +;; +;; Each inserter handles various macro codes from the temlate. +;; The `code' slot specifies a character used to identify which +;; inserter is to be created. +;; +(defclass srecode-template-inserter-newline (srecode-template-inserter) + ((key :initform "\n" + :allocation :class + :documentation + "The character code used to identify inserters of this style.") + (hard :initform nil + :initarg :hard + :documentation + "Is this a hard newline (always inserted) or optional? +Optional newlines don't insert themselves if they are on a blank line +by themselves.") + ) + "Insert a newline, and possibly do indenting. +Specify the :indent argument to enable automatic indentation when newlines +occur in your template.") + +(defmethod srecode-insert-method ((sti srecode-template-inserter-newline) + dictionary) + "Insert the STI inserter." + ;; To be safe, indent the previous line since the template will + ;; change what is there to indent + (let ((i (srecode-dictionary-lookup-name dictionary "INDENT")) + (inbuff (bufferp standard-output)) + (doit t) + (pm (point-marker))) + (when (and inbuff (not (oref sti hard))) + ;; If this is not a hard newline, we need do the calculation + ;; and set "doit" to nil. + (beginning-of-line) + (save-restriction + (narrow-to-region (point) pm) + (when (looking-at "\\s-*$") + (setq doit nil))) + (goto-char pm) + ) + ;; Do indentation reguardless of the newline. + (when (and (eq i t) inbuff) + (indent-according-to-mode) + (goto-char pm)) + + (when doit + (princ "\n") + ;; Indent after the newline, particularly for numeric indents. + (cond ((and (eq i t) (bufferp standard-output)) + ;; WARNING - indent according to mode requires that standard-output + ;; is a buffer! + ;; @todo - how to indent in a string??? + (setq pm (point-marker)) + (indent-according-to-mode) + (goto-char pm)) + ((numberp i) + (princ (make-string i " "))) + ((stringp i) + (princ i)))))) + +(defmethod srecode-dump ((ins srecode-template-inserter-newline) indent) + "Dump the state of the SRecode template inserter INS." + (call-next-method) + (when (oref ins hard) + (princ " : hard") + )) + +(defclass srecode-template-inserter-blank (srecode-template-inserter) + ((key :initform "\r" + :allocation :class + :documentation + "The character represeinting this inserter style. +Can't be blank, or it might be used by regular variable insertion.") + (where :initform 'begin + :initarg :where + :documentation + "This should be 'begin or 'end, indicating where to insrt a CR. +When set to 'begin, it will insert a CR if we are not at 'bol'. +When set to 'end it will insert a CR if we are not at 'eol'") + ;; @TODO - Add slot and control for the number of blank + ;; lines before and after point. + ) + "Insert a newline before and after a template, and possibly do indenting. +Specify the :blank argument to enable this inserter.") + +(defmethod srecode-insert-method ((sti srecode-template-inserter-blank) + dictionary) + "Make sure there is no text before or after point." + (let ((i (srecode-dictionary-lookup-name dictionary "INDENT")) + (inbuff (bufferp standard-output)) + (pm (point-marker))) + (when (and inbuff + ;; Don't do this if we are not the active template. + (= (length (oref srecode-template active)) 1)) + + (when (and (eq i t) inbuff (not (eq (oref sti where) 'begin))) + (indent-according-to-mode) + (goto-char pm)) + + (cond ((and (eq (oref sti where) 'begin) (not (bolp))) + (princ "\n")) + ((eq (oref sti where) 'end) + ;; If there is whitespace after pnt, then clear it out. + (when (looking-at "\\s-*$") + (delete-region (point) (point-at-eol))) + (when (not (eolp)) + (princ "\n"))) + ) + (setq pm (point-marker)) + (when (and (eq i t) inbuff (not (eq (oref sti where) 'end))) + (indent-according-to-mode) + (goto-char pm)) + ))) + +(defclass srecode-template-inserter-comment (srecode-template-inserter) + ((key :initform ?! + :allocation :class + :documentation + "The character code used to identify inserters of this style.") + ) + "Allow comments within template coding. This inserts nothing.") + +(defmethod srecode-inserter-prin-example :STATIC ((ins srecode-template-inserter-comment) + escape-start escape-end) + "Insert an example using inserter INS. +Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use." + (princ " ") + (princ escape-start) + (princ "! Miscellaneous text commenting in your template. ") + (princ escape-end) + (terpri) + ) + +(defmethod srecode-insert-method ((sti srecode-template-inserter-comment) + dictionary) + "Don't insert anything for comment macros in STI." + nil) + + +(defclass srecode-template-inserter-variable (srecode-template-inserter) + ((key :initform nil + :allocation :class + :documentation + "The character code used to identify inserters of this style.")) + "Insert the value of a dictionary entry +If there is no entry, insert nothing.") + +(defvar srecode-inserter-variable-current-dictionary nil + "The active dictionary when calling a variable filter.") + +(defmethod srecode-insert-variable-secondname-handler + ((sti srecode-template-inserter-variable) dictionary value secondname) + "For VALUE handle SECONDNAME behaviors for this variable inserter. +Return the result as a string. +By default, treat as a function name. +If SECONDNAME is nil, return VALUE." + (if secondname + (let ((fcnpart (read secondname))) + (if (fboundp fcnpart) + (let ((srecode-inserter-variable-current-dictionary dictionary)) + (funcall fcnpart value)) + ;; Else, warn. + (error "Variable insertion second arg %s is not a function." + secondname))) + value)) + +(defmethod srecode-insert-method ((sti srecode-template-inserter-variable) + dictionary) + "Insert the STI inserter." + ;; Convert the name into a name/fcn pair + (let* ((name (oref sti :object-name)) + (fcnpart (oref sti :secondname)) + (val (srecode-dictionary-lookup-name + dictionary name)) + (do-princ t) + ) + ;; Alert if a macro wasn't found. + (when (not val) + (message "Warning: macro %S was not found in the dictionary." name) + (setq val "")) + ;; If there was a functional part, call that function. + (cond ;; Strings + ((stringp val) + (setq val (srecode-insert-variable-secondname-handler + sti dictionary val fcnpart))) + ;; Compound data value + ((srecode-dictionary-compound-value-child-p val) + ;; Force FCN to be a symbol + (when fcnpart (setq fcnpart (read fcnpart))) + ;; Convert compound value to a string with the fcn. + (setq val (srecode-compound-toString val fcnpart dictionary)) + ;; If the value returned is nil, then it may be a special + ;; field inserter that requires us to set do-princ to nil. + (when (not val) + (setq do-princ nil) + ) + ) + ;; Dictionaries... not allowed in this style + ((srecode-dictionary-child-p val) + (error "Macro %s cannot insert a dictionary. Use section macros instead." + name)) + ;; Other stuff... convert + (t + (error "Macro %s cannot insert arbitrary data." name) + ;;(if (and val (not (stringp val))) + ;; (setq val (format "%S" val)))) + )) + ;; Output the dumb thing unless the type of thing specifically + ;; did the inserting forus. + (when do-princ + (princ val)))) + +(defclass srecode-template-inserter-ask (srecode-template-inserter-variable) + ((key :initform ?? + :allocation :class + :documentation + "The character code used to identify inserters of this style.") + (prompt :initarg :prompt + :initform nil + :documentation + "The prompt used to query for this dictionary value.") + (defaultfcn :initarg :defaultfcn + :initform nil + :documentation + "The function which can calculate a default value.") + (read-fcn :initarg :read-fcn + :initform 'read-string + :documentation + "The function used to read in the text for this prompt.") + ) + "Insert the value of a dictionary entry +If there is no entry, prompt the user for the value to use. +The prompt text used is derived from the previous PROMPT command in the +template file.") + +(defmethod srecode-inserter-apply-state ((ins srecode-template-inserter-ask) STATE) + "For the template inserter INS, apply information from STATE. +Loop over the prompts to see if we have a match." + (let ((prompts (oref STATE prompts)) + ) + (while prompts + (when (string= (semantic-tag-name (car prompts)) + (oref ins :object-name)) + (oset ins :prompt + (semantic-tag-get-attribute (car prompts) :text)) + (oset ins :defaultfcn + (semantic-tag-get-attribute (car prompts) :default)) + (oset ins :read-fcn + (or (semantic-tag-get-attribute (car prompts) :read) + 'read-string)) + ) + (setq prompts (cdr prompts))) + )) + +(defmethod srecode-insert-method ((sti srecode-template-inserter-ask) + dictionary) + "Insert the STI inserter." + (let ((val (srecode-dictionary-lookup-name + dictionary (oref sti :object-name)))) + (if val + ;; Does some extra work. Oh well. + (call-next-method) + + ;; How is our -ask value determined? + (if srecode-insert-with-fields-in-progress + ;; Setup editable fields. + (setq val (srecode-insert-method-field sti dictionary)) + ;; Ask the question... + (setq val (srecode-insert-method-ask sti dictionary))) + + ;; After asking, save in the dictionary so that + ;; the user can use the same name again later. + (srecode-dictionary-set-value + (srecode-root-dictionary dictionary) + (oref sti :object-name) val) + + ;; Now that this value is safely stowed in the dictionary, + ;; we can do what regular inserters do. + (call-next-method)))) + +(defmethod srecode-insert-ask-default ((sti srecode-template-inserter-ask) + dictionary) + "Derive the default value for an askable inserter STI. +DICTIONARY is used to derive some values." + (let ((defaultfcn (oref sti :defaultfcn))) + (cond ((stringp defaultfcn) + defaultfcn) + ((functionp defaultfcn) + (funcall defaultfcn)) + ((and (listp defaultfcn) + (eq (car defaultfcn) 'macro)) + (srecode-dictionary-lookup-name + dictionary (cdr defaultfcn))) + ((null defaultfcn) + "") + (t + (error "Unknown default for prompt: %S" + defaultfcn))))) + +(defmethod srecode-insert-method-ask ((sti srecode-template-inserter-ask) + dictionary) + "Do the \"asking\" for the template inserter STI. +Use DICTIONARY to resolve values." + (let* ((prompt (oref sti prompt)) + (default (srecode-insert-ask-default sti dictionary)) + (reader (oref sti :read-fcn)) + (val nil) + ) + (cond ((eq reader 'y-or-n-p) + (if (y-or-n-p (or prompt + (format "%s? " + (oref sti :object-name)))) + (setq val default) + (setq val ""))) + ((eq reader 'read-char) + (setq val (format + "%c" + (read-char (or prompt + (format "Char for %s: " + (oref sti :object-name)))))) + ) + (t + (save-excursion + (setq val (funcall reader + (or prompt + (format "Specify %s: " + (oref sti :object-name))) + default + ))))) + ;; Return our derived value. + val) + ) + +(defmethod srecode-insert-method-field ((sti srecode-template-inserter-ask) + dictionary) + "Create an editable field for the template inserter STI. +Use DICTIONARY to resolve values." + (let* ((default (srecode-insert-ask-default sti dictionary)) + (compound-value + (srecode-field-value (oref sti :object-name) + :firstinserter sti + :defaultvalue default)) + ) + ;; Return this special compound value as the thing to insert. + ;; This special compound value will repeat our asked question + ;; across multiple locations. + compound-value)) + +(defmethod srecode-dump ((ins srecode-template-inserter-ask) indent) + "Dump the state of the SRecode template inserter INS." + (call-next-method) + (princ " : \"") + (princ (oref ins prompt)) + (princ "\"") + ) + +(defclass srecode-template-inserter-width (srecode-template-inserter-variable) + ((key :initform ?| + :allocation :class + :documentation + "The character code used to identify inserters of this style.") + ) + "Inserts the value of a dictionary variable with a specific width. +The second argument specifies the width, and a pad, seperated by a colon. +thus a specification of `10:left' will insert the value of A +to 10 characters, with spaces added to the left. Use `right' for adding +spaces to the right.") + +(defmethod srecode-insert-variable-secondname-handler + ((sti srecode-template-inserter-width) dictionary value width) + "For VALUE handle WIDTH behaviors for this variable inserter. +Return the result as a string. +By default, treat as a function name." + (if width + ;; Trim or pad to new length + (let* ((split (split-string width ":")) + (width (string-to-number (nth 0 split))) + (second (nth 1 split)) + (pad (cond ((or (null second) (string= "right" second)) + 'right) + ((string= "left" second) + 'left) + (t + (error "Unknown pad type %s" second))))) + (if (>= (length value) width) + ;; Simple case - too long. + (substring value 0 width) + ;; We need to pad on one side or the other. + (let ((padchars (make-string (- width (length value)) ? ))) + (if (eq pad 'left) + (concat padchars value) + (concat value padchars))))) + (error "Width not specified for variable/width inserter."))) + +(defmethod srecode-inserter-prin-example :STATIC ((ins srecode-template-inserter-width) + escape-start escape-end) + "Insert an example using inserter INS. +Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use." + (princ " ") + (princ escape-start) + (princ "|A:10:right") + (princ escape-end) + (terpri) + ) + +(defvar srecode-template-inserter-point-override nil + "When non-nil, the point inserter will do this functin instead.") + +(defclass srecode-template-inserter-point (srecode-template-inserter) + ((key :initform ?^ + :allocation :class + :documentation + "The character code used to identify inserters of this style.") + (point :type (or null marker) + :allocation :class + :documentation + "Record the value of (point) in this class slot. +It is the responsibility of the inserter algorithm to clear this +after a successful insertion.")) + "Record the value of (point) when inserted. +The cursor is placed at the ^ macro after insertion. +Some inserter macros, such as `srecode-template-inserter-include-wrap' +will place text at the ^ macro from the included macro.") + +(defmethod srecode-inserter-prin-example :STATIC ((ins srecode-template-inserter-point) + escape-start escape-end) + "Insert an example using inserter INS. +Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use." + (princ " ") + (princ escape-start) + (princ "^") + (princ escape-end) + (terpri) + ) + +(defmethod srecode-insert-method ((sti srecode-template-inserter-point) + dictionary) + "Insert the STI inserter. +Save point in the class allocated 'point' slot. +If `srecode-template-inserter-point-override' then this generalized +marker will do something else. See `srecode-template-inserter-include-wrap' +as an example." + (if srecode-template-inserter-point-override + ;; Disable the old override while we do this. + (let ((over srecode-template-inserter-point-override) + (srecode-template-inserter-point-override nil)) + (funcall over dictionary) + ) + (oset sti point (point-marker)) + )) + +(defclass srecode-template-inserter-subtemplate (srecode-template-inserter) + () + "Wrap a section of a template under the control of a macro." + :abstract t) + +(defmethod srecode-inserter-prin-example :STATIC ((ins srecode-template-inserter-subtemplate) + escape-start escape-end) + "Insert an example using inserter INS. +Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use." + (call-next-method) + (princ " Template Text to control") + (terpri) + (princ " ") + (princ escape-start) + (princ "/VARNAME") + (princ escape-end) + (terpri) + ) + +(defmethod srecode-insert-subtemplate ((sti srecode-template-inserter-subtemplate) + dict slot) + "Insert a subtemplate for the inserter STI with dictionary DICT." + ;; make sure that only dictionaries are used. + (when (not (srecode-dictionary-child-p dict)) + (error "Only section dictionaries allowed for %s" + (object-name-string sti))) + ;; Output the code from the sub-template. + (srecode-insert-method (slot-value sti slot) dict) + ) + +(defmethod srecode-insert-method-helper ((sti srecode-template-inserter-subtemplate) + dictionary slot) + "Do the work for inserting the STI inserter. +Loops over the embedded CODE which was saved here during compilation. +The template to insert is stored in SLOT." + (let ((dicts (srecode-dictionary-lookup-name + dictionary (oref sti :object-name)))) + ;; If there is no section dictionary, then don't output anything + ;; from this section. + (while dicts + (srecode-insert-subtemplate sti (car dicts) slot) + (setq dicts (cdr dicts))))) + +(defmethod srecode-insert-method ((sti srecode-template-inserter-subtemplate) + dictionary) + "Insert the STI inserter. +Calls back to `srecode-insert-method-helper' for this class." + (srecode-insert-method-helper sti dictionary 'template)) + + +(defclass srecode-template-inserter-section-start (srecode-template-inserter-subtemplate) + ((key :initform ?# + :allocation :class + :documentation + "The character code used to identify inserters of this style.") + (template :initarg :template + :documentation + "A Template used to frame the codes from this inserter.") + ) + "Apply values from a sub-dictionary to a template section. +The dictionary saved at the named dictionary entry will be +applied to the text between the section start and the +`srecode-template-inserter-section-end' macro.") + +(defmethod srecode-parse-input ((ins srecode-template-inserter-section-start) + tag input STATE) + "For the section inserter INS, parse INPUT. +Shorten input until the END token is found. +Return the remains of INPUT." + (let* ((out (srecode-compile-split-code tag input STATE + (oref ins :object-name)))) + (oset ins template (srecode-template + (object-name-string ins) + :context nil + :args nil + :code (cdr out))) + (car out))) + +(defmethod srecode-dump ((ins srecode-template-inserter-section-start) indent) + "Dump the state of the SRecode template inserter INS." + (call-next-method) + (princ "\n") + (srecode-dump-code-list (oref (oref ins template) code) + (concat indent " ")) + ) + +(defclass srecode-template-inserter-section-end (srecode-template-inserter) + ((key :initform ?/ + :allocation :class + :documentation + "The character code used to identify inserters of this style.") + ) + "All template segments between the secion-start and section-end +are treated specially.") + +(defmethod srecode-insert-method ((sti srecode-template-inserter-section-end) + dictionary) + "Insert the STI inserter." + ) + +(defmethod srecode-match-end ((ins srecode-template-inserter-section-end) name) + + "For the template inserter INS, do I end a section called NAME?" + (string= name (oref ins :object-name))) + +(defclass srecode-template-inserter-include (srecode-template-inserter-subtemplate) + ((key :initform ?> + :allocation :class + :documentation + "The character code used to identify inserters of this style.") + (includedtemplate + :initarg :includedtemplate + :documentation + "The template included for this inserter.")) + "Include a different template into this one. +The included template will have additional dictionary entries from the subdictionary +stored specified by this macro.") + +(defmethod srecode-inserter-prin-example :STATIC ((ins srecode-template-inserter-include) + escape-start escape-end) + "Insert an example using inserter INS. +Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use." + (princ " ") + (princ escape-start) + (princ ">DICTNAME:contextname:templatename") + (princ escape-end) + (terpri) + ) + +(defmethod srecode-insert-include-lookup ((sti srecode-template-inserter-include) + dictionary) + "For the template inserter STI, lookup the template to include. +Finds the template with this macro function part and stores it in +this template instance." + (let* ((templatenamepart (oref sti :secondname)) + ) + ;; If there was no template name, throw an error + (if (not templatenamepart) + (error "Include macro %s needs a template name." (oref sti :object-name))) + ;; Find the template by name, and save it. + (if (or (not (slot-boundp sti 'includedtemplate)) + (not (oref sti includedtemplate))) + (let ((tmpl (srecode-template-get-table (srecode-table) + templatenamepart)) + (active (oref srecode-template active)) + ctxt) + (when (not tmpl) + ;; If it isn't just available, scan back through + ;; the active template stack, searching for a matching + ;; context. + (while (and (not tmpl) active) + (setq ctxt (oref (car active) context)) + (setq tmpl (srecode-template-get-table (srecode-table) + templatenamepart + ctxt)) + (when (not tmpl) + (when (slot-boundp (car active) 'table) + (let ((app (oref (oref (car active) table) application))) + (when app + (setq tmpl (srecode-template-get-table + (srecode-table) + templatenamepart + ctxt app))) + ))) + (setq active (cdr active))) + (when (not tmpl) + ;; If it wasn't in this context, look to see if it + ;; defines it's own context + (setq tmpl (srecode-template-get-table (srecode-table) + templatenamepart))) + ) + (oset sti :includedtemplate tmpl))) + + (if (not (oref sti includedtemplate)) + ;; @todo - Call into a debugger to help find the template in question. + (error "No template \"%s\" found for include macro `%s'" + templatenamepart (oref sti :object-name))) + )) + +(defmethod srecode-insert-method ((sti srecode-template-inserter-include) + dictionary) + "Insert the STI inserter. +Finds the template with this macro function part, and inserts it +with the dictionaries found in the dictinary." + (srecode-insert-include-lookup sti dictionary) + ;; Insert the template. + ;; Our baseclass has a simple way to do this. + (if (srecode-dictionary-lookup-name dictionary (oref sti :object-name)) + ;; If we have a value, then call the next method + (srecode-insert-method-helper sti dictionary 'includedtemplate) + ;; If we don't have a special dictitonary, then just insert with the + ;; current dictionary. + (srecode-insert-subtemplate sti dictionary 'includedtemplate)) + ) + +;; +;; This template combines the include template and the sectional template. +;; It will first insert the included template, then insert the embedded +;; template wherever the $^$ in the included template was. +;; +;; Since it uses dual inheretance, it will magically get the end-matching +;; behavior of #, with the including feature of >. +;; +(defclass srecode-template-inserter-include-wrap (srecode-template-inserter-include srecode-template-inserter-section-start) + ((key :initform ?< + :allocation :class + :documentation + "The character code used to identify inserters of this style.") + ) + "Include a different template into this one, and add text at the ^ macro. +The included template will have additional dictionary entries from the subdictionary +stored specified by this macro. If the included macro includes a ^ macro, +then the text between this macro and the end macro will be inserted at +the ^ macro.") + +(defmethod srecode-inserter-prin-example :STATIC ((ins srecode-template-inserter-include-wrap) + escape-start escape-end) + "Insert an example using inserter INS. +Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use." + (princ " ") + (princ escape-start) + (princ "<DICTNAME:contextname:templatename") + (princ escape-end) + (terpri) + (princ " Template Text to insert at ^ macro") + (terpri) + (princ " ") + (princ escape-start) + (princ "/DICTNAME") + (princ escape-end) + (terpri) + ) + +(defmethod srecode-insert-method ((sti srecode-template-inserter-include-wrap) + dictionary) + "Insert the template STI. +This will first insert the include part via inheritance, then +insert the section it wraps into the location in the included +template where a ^ inserter occurs." + ;; Step 1: Look up the included inserter + (srecode-insert-include-lookup sti dictionary) + ;; Step 2: Temporarilly override the point inserter. + (let* ((vaguely-unique-name sti) + (srecode-template-inserter-point-override + (lambda (dict2) + (if (srecode-dictionary-lookup-name + dict2 (oref vaguely-unique-name :object-name)) + ;; Insert our sectional part with looping. + (srecode-insert-method-helper + vaguely-unique-name dict2 'template) + ;; Insert our sectional part just once. + (srecode-insert-subtemplate vaguely-unique-name + dict2 'template)) + ))) + ;; Do a regular insertion for an include, but with our override in + ;; place. + (call-next-method) + )) + +(provide 'srecode/insert) + +;; Local variables: +;; generated-autoload-file: "loaddefs.el" +;; generated-autoload-feature: srecode/loaddefs +;; generated-autoload-load-name: "srecode/insert" +;; End: + +;;; srecode/insert.el ends here |