From: Andreas Gerler Date: Sat, 25 Nov 2023 10:42:31 +0000 (+0100) Subject: update mustache X-Git-Url: http://git.bundesbrandschatzamt.de/?a=commitdiff_plain;h=a8079c7c0b8ffef0b6f61a790600285ab19c0e95;p=emacsconfig.git update mustache Signed-off-by: Andreas Gerler --- diff --git a/elpa/mustache-20190905.2214/mustache-autoloads.el b/elpa/mustache-20190905.2214/mustache-autoloads.el deleted file mode 100644 index 1eb67edc..00000000 --- a/elpa/mustache-20190905.2214/mustache-autoloads.el +++ /dev/null @@ -1,17 +0,0 @@ -;;; mustache-autoloads.el --- automatically extracted autoloads -;; -;;; Code: -(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) - -;;;### (autoloads nil nil ("mustache-lex.el" "mustache-parse.el" -;;;;;; "mustache-pkg.el" "mustache-render.el" "mustache.el") (23994 -;;;;;; 46444 106595 624000)) - -;;;*** - -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; End: -;;; mustache-autoloads.el ends here diff --git a/elpa/mustache-20190905.2214/mustache-lex.el b/elpa/mustache-20190905.2214/mustache-lex.el deleted file mode 100644 index 2470876f..00000000 --- a/elpa/mustache-20190905.2214/mustache-lex.el +++ /dev/null @@ -1,166 +0,0 @@ -;; This file is part of mustache.el. -;; -;; mustache.el 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. -;; -;; mustache.el 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 mustache.el. If not, see . - -(eval-when-compile (require 'cl)) ;; first, second, destructuring-bind, loop -(require 's) - -(defun mst--lex (template) - "Iterate through TEMPLATE, splitting {{ tags }} and bare strings. -We return a list of lists: ((:text \"foo\") (:tag \"variable-name\"))" - ;; convert {{{foo}}} to {{& foo}} - (setq template (replace-regexp-in-string "{{{\\(.*?\\)}}}" "{{& \\1}}" template)) - - (let ((open-delimeter "{{") - (close-delimeter "}}") - (lexemes nil)) - (while (not (s-equals? template "")) - (let* ((open-index (s-index-of open-delimeter template)) - (close-index (s-index-of close-delimeter template))) - ;; todo: check open-index < close-index - ;; todo: error if we have an open and no close - (if (and open-index close-index) - ;; we have a tag - (let ((between-delimeters - (substring template (+ open-index (length open-delimeter)) close-index)) - (continue-from-index (+ close-index (length close-delimeter)))) - ;; save the string before the tag - (when (> open-index 0) - (push (list :text (substring template 0 open-index)) lexemes)) - - ;; if this is a tag that changes delimeters e.g. {{=<< >>=}} - ;; then set the new open/close delimeter string - (if (s-matches-p "^=.+ .+=$" between-delimeters) - (let* (;; strip leading/trailing = - (delimeter-spec (substring between-delimeters 1 -1)) - (spec-parts (s-split " " delimeter-spec))) - (setq open-delimeter (first spec-parts)) - (setq close-delimeter (second spec-parts))) - - ;; otherwise it's a normal tag, so save it - (push (list :tag (s-trim between-delimeters)) lexemes)) - - ;; iterate on the remaining template - (setq template - (substring template continue-from-index))) - ;; else only plain text left - (progn - (push (list :text template) lexemes) - (setq template ""))))) - (nreverse lexemes))) - -(defun mst--clean-whitespace (lexemes) - "Given a list of LEXEMES, remove whitespace around sections and -comments if they're on their own on a line. Modifies the original -list." - ;; iterate over all lexemes: - (loop for i from 0 to (- (length lexemes) 3) - collect - ;; find sections that have plain text before and after - (let ((first (elt lexemes i)) - (second (elt lexemes (+ i 1))) - (third (elt lexemes (+ i 2)))) - (when (and (mst--text-p first) - (or - (mst--section-tag-p second) - (mst--comment-tag-p second)) - (mst--text-p third) - ;; check the section is on its own line - (string-match-p "\n *$" (mst--tag-text first)) - (string-match-p "^\n" (mst--tag-text third))) - ;; then we cleanup whitespace - (setf (elt lexemes i) (mst--no-trailing-newline first))))) - lexemes) - -(defalias 'mst--tag-text 'second - "Returns the text context of a tag.") - -(defun mst--no-trailing-newline (lexeme) - "Replace \"\n\" or \"\n \" at the end of a plain text lexeme." - (list - :text - (replace-regexp-in-string "\n *$" "" (mst--tag-text lexeme)))) - -(defun mst--tag-p (lexeme) - "Is LEXEME a tag?" - (equal (car lexeme) :tag)) - -(defun mst--section-tag-p (lexeme) - "Is LEXEME a section tag?" - (or - (mst--open-section-tag-p lexeme) - (mst--inverted-section-tag-p lexeme) - (mst--close-section-tag-p lexeme))) - -(defun mst--open-section-tag-p (lexeme) - "Is LEXEME an open section tag? -See also `mst--inverted-section-tag-p'." - (and (mst--tag-p lexeme) - (s-starts-with-p "#" (second lexeme)))) - -(defun mst--inverted-section-tag-p (lexeme) - "Is LEXEME an inverted section tag?" - (and (mst--tag-p lexeme) - (s-starts-with-p "^" (second lexeme)))) - -(defun mst--close-section-tag-p (lexeme) - "Is LEXEME a close section tag?" - (and (mst--tag-p lexeme) - (s-starts-with-p "/" (second lexeme)))) - -(defun mst--comment-tag-p (lexeme) - "Is LEXEME a comment tag?" - (and (mst--tag-p lexeme) - (s-starts-with-p "!" (second lexeme)))) - -(defun mst--unescaped-tag-p (lexeme) - "Is LEXEME an unescaped variable tag? -Note that the lexer converts {{{foo}}} to {{& foo}}." - (and (mst--tag-p lexeme) - (s-starts-with-p "&" (second lexeme)))) - -(defun mst--partial-tag-p (lexeme) - "Is LEXEME a partial tag?" - (and (mst--tag-p lexeme) - (s-starts-with-p ">" (second lexeme)))) - -(defun mst--section-p (lexeme) - "Is LEXEME a nested section?" - (not (atom (car lexeme)))) - -(defun mst--text-p (lexeme) - "Is LEXEME plain text?" - (equal (car lexeme) :text)) - -;; fixme: assumes the delimeters haven't changed -;; fixme: mst--lex doens't preserve whitespace -(defun mst--unlex (lexemes) - "Given a lexed (and optionally parsed) list of lexemes, -return the original input string." - (if lexemes - (let ((lexeme (first lexemes)) - (rest (cdr lexemes))) - (cond - ;; recurse on this section, then the rest - ((mst--section-p lexeme) - (concat (mst--unlex lexeme) (mst--unlex rest))) - ((mst--tag-p lexeme) - ;; restore the delimeters, then unlex the rest - (let ((tag-name (second lexeme))) - (concat "{{" tag-name "}}" (mst--unlex rest)))) - ;; otherwise, it's just raw text - (t - (let ((text (second lexeme))) - (concat text (mst--unlex rest)))))) - "")) diff --git a/elpa/mustache-20190905.2214/mustache-parse.el b/elpa/mustache-20190905.2214/mustache-parse.el deleted file mode 100644 index c0c37b65..00000000 --- a/elpa/mustache-20190905.2214/mustache-parse.el +++ /dev/null @@ -1,70 +0,0 @@ -;; This file is part of mustache.el. -;; -;; mustache.el 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. -;; -;; mustache.el 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 mustache.el. If not, see . - -(require 's) -(eval-when-compile (require 'cl)) ;; loop, return - -(defvar mst--remaining-lexemes nil - "Since `mst--parse-inner' recursively calls itself, we need a shared value to mutate.") - -(defun mst--parse (lexemes) - "Given a list LEXEMES, return a list of lexemes nested according to #tags or ^tags." - (setq mst--remaining-lexemes lexemes) - (mst--parse-inner)) - -(defun mst--parse-inner (&optional section-name) - "Parse `mst--remaining-lexemes', and return a list of lexemes nested according to #tags or ^tags." - (let (parsed-lexemes - lexeme) - (loop while mst--remaining-lexemes do - (setq lexeme (pop mst--remaining-lexemes)) - (cond - ((mst--open-section-p lexeme) - ;; recurse on this nested section - (push (cons lexeme (mst--parse-inner (mst--section-name lexeme))) parsed-lexemes)) - ((mst--close-section-p lexeme) - ;; this is the last tag in this section - (unless (equal section-name (mst--section-name lexeme)) - (error "Mismatched brackets: You closed a section with %s, but it wasn't open" section-name)) - (push lexeme parsed-lexemes) - (return)) - (t - ;; this is just a tag in the current section - (push lexeme parsed-lexemes)))) - - ;; ensure we aren't inside an unclosed section - (when (and section-name (not (mst--close-section-p lexeme))) - (error "Unclosed section: You haven't closed %s" section-name)) - - (nreverse parsed-lexemes))) - -(defun mst--open-section-p (lexeme) - "Is LEXEME a #tag or ^tag ?" - (destructuring-bind (type value) lexeme - (and (equal type :tag) - (or - (s-starts-with-p "#" value) - (s-starts-with-p "^" value))))) - -(defun mst--close-section-p (lexeme) - "Is LEXEME a /tag ?" - (destructuring-bind (type value) lexeme - (and (equal type :tag) - (s-starts-with-p "/" value)))) - -(defun mst--section-name (lexeme) - "Get the name of the section from LEXEME, a two part list returned by `mst--lex'. -The leading character (the #, ^ or /) is stripped." - (s-chop-prefixes '("#" "^" "/") (cadr lexeme))) diff --git a/elpa/mustache-20190905.2214/mustache-pkg.el b/elpa/mustache-20190905.2214/mustache-pkg.el deleted file mode 100644 index 4f9f75b1..00000000 --- a/elpa/mustache-20190905.2214/mustache-pkg.el +++ /dev/null @@ -1,7 +0,0 @@ -(define-package "mustache" "20190905.2214" "a mustache templating library in emacs lisp" - '((ht "0.9") - (s "1.3.0") - (dash "1.2.0"))) -;; Local Variables: -;; no-byte-compile: t -;; End: diff --git a/elpa/mustache-20190905.2214/mustache-render.el b/elpa/mustache-20190905.2214/mustache-render.el deleted file mode 100644 index 087fd508..00000000 --- a/elpa/mustache-20190905.2214/mustache-render.el +++ /dev/null @@ -1,155 +0,0 @@ -;; This file is part of mustache.el. -;; -;; mustache.el 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. -;; -;; mustache.el 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 mustache.el. If not, see . - -(require 'ht) -(require 's) -(require 'dash) - -(eval-when-compile '(require 'cl)) ;; return, dolist - -(load "mustache-lex.el") -(load "mustache-parse.el") - -(defun mst--render (template context) - "Render a mustache TEMPLATE with hash table CONTEXT." - (-> template - mst--lex - mst--clean-whitespace - mst--parse - (mst--render-section-list context))) - -(defun mst--mapconcat (function sequence) - "Apply FUNCTION to every element in SEQUENCE, and concat the results as strings." - (mapconcat function sequence "")) - -(defmacro mst--amapconcat (form sequence) - "Anaphoric version of `mst--mapconcat'." - `(mst--mapconcat (lambda (it) ,form) ,sequence)) - -;; todo: set flag to set tolerance of missing templates -(defun mst--get-partial (name) - "Get the first partial whose file name is NAME.mustache, or nil otherwise. -Partials are searched for in `mustache-partial-paths'." - (unless (listp mustache-partial-paths) - (error "`mustache-partial-paths' must be a list of paths")) - (let ((partial-name (format "%s.mustache" name))) - (dolist (path mustache-partial-paths) - (-when-let* - ((partials (directory-files path nil "\\.mustache$")) - (matching-partial (--first - (string-match-p (regexp-quote partial-name) it) - partials))) - (return - (with-temp-buffer - (insert-file-contents-literally (expand-file-name matching-partial path)) - (buffer-substring-no-properties (point-min) (point-max)))))))) - -(defun mst--render-section-list (sections context) - "Render a parsed list SECTIONS in CONTEXT." - (mst--amapconcat (mst--render-section it context) sections)) - -(defun mst--context-get (context variable-name &optional default) - "Lookup VARIABLE-NAME in CONTEXT, returning DEFAULT if not present." - (when (eq mustache-key-type 'keyword) - (setq variable-name (intern (concat ":" variable-name)))) - (ht-get context variable-name default)) - -(defun mst--render-tag (parsed-tag context) - "Given PARSED-TAG, render it in hash table CONTEXT." - (let ((inner-text (mst--tag-text parsed-tag))) - (cond - ((mst--comment-tag-p parsed-tag) - "") - ((mst--unescaped-tag-p parsed-tag) - (let ((variable-value (mst--context-get context (s-trim (substring inner-text 1)) ""))) - (when (numberp variable-value) - (setq variable-value (number-to-string variable-value))) - variable-value)) - ((mst--partial-tag-p parsed-tag) - (let ((partial (mst--get-partial (s-trim (substring inner-text 1))))) - (if partial - (mst--render partial context) - ""))) - (t ;; normal variable - (let ((variable-value (mst--context-get context inner-text ""))) - (when (numberp variable-value) - (setq variable-value (number-to-string variable-value))) - (mst--escape-html variable-value)))))) - -(defun mst--context-add (table from-table) - "Return a copy of TABLE where all the key-value pairs in FROM-TABLE have been set." - (let ((new-table (ht-copy table))) - (ht-update new-table from-table) - new-table)) - -(defun mst--listp (object) - "Return t if OBJECT is a list. -Unlike `listp', does not return t if OBJECT is a function." - (and (not (functionp object)) (listp object))) - -(defun mst--section-name (section-tag) - "Get the name of this SECCTION-TAG. -E.g. from {{#foo}} to \"foo\"." - (-> section-tag ;; e.g (:tag "#foo") - mst--tag-text ;; to "#foo" - (substring 1) ;; to "foo" - s-trim)) - -(defun mst--render-section (parsed-lexeme context) - "Given PARSED-LEXEME -- a lexed tag, plain text, or a nested list, -render it in CONTEXT." - (cond ((mst--section-p parsed-lexeme) - ;; nested section - (let* ((section-tag (first parsed-lexeme)) - (section-name (mst--section-name section-tag)) - (context-value (mst--context-get context section-name)) - ;; strip section open and close - (section-contents (-slice parsed-lexeme 1 -1))) - (cond - ((mst--open-section-tag-p section-tag) - (cond - ;; if the context is a list of hash tables, render repeatedly - ((or (mst--listp context-value) (vectorp context-value)) - (mst--amapconcat (mst--render-section-list section-contents (mst--context-add context it)) context-value)) - ;; if the context is a hash table, render in that context - ((hash-table-p context-value) - (mst--render-section-list section-contents (mst--context-add context context-value))) - ;; if the context is a function, call it - ((functionp context-value) - (funcall context-value (mst--unlex section-contents) context)) - ;; if it's a truthy value, render in the current context - (context-value - (mst--render-section-list section-contents context)) - ;; otherwise, don't render anything - (t ""))) - ;; render ^tags - ((mst--inverted-section-tag-p section-tag) - (if context-value - "" - (mst--render-section-list section-contents context)))))) - ((mst--tag-p parsed-lexeme) - (mst--render-tag parsed-lexeme context)) - ;; plain text - (t - (second parsed-lexeme)))) - -(defun mst--escape-html (string) - "Escape HTML in STRING." - (->> string - (s-replace "&" "&") - (s-replace "<" "<") - (s-replace ">" ">") - (s-replace "'" "'") - (s-replace "\"" """))) diff --git a/elpa/mustache-20190905.2214/mustache.el b/elpa/mustache-20190905.2214/mustache.el deleted file mode 100644 index 4be68b01..00000000 --- a/elpa/mustache-20190905.2214/mustache.el +++ /dev/null @@ -1,65 +0,0 @@ -;;; mustache.el --- a mustache templating library in emacs lisp - -;; Copyright (C) 2013 Wilfred Hughes - -;; Author: Wilfred Hughes -;; Version: 0.24 -;; Keywords: mustache, template -;; Package-Requires: ((ht "0.9") (s "1.3.0") (dash "1.2.0")) - -;; This program 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. - -;; This program is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . - -;;; Commentary: - -;; See documentation at https://github.com/Wilfred/mustache.el - -;; Note on terminology: We treat mustache templates as a sequence of -;; strings (plain text), and tags (anything wrapped in delimeters: -;; {{foo}}). A section is a special tag that requires closing -;; (e.g. {{#foo}}{{/foo}}). - -;; We treat mustache templates as if they conform to a rough grammar: - -;; TEMPLATE = plaintext | TAG | SECTION | TEMPLATE TEMPLATE -;; SECTION = OPEN-TAG TEMPLATE CLOSE-TAG -;; TAG = "{{" text "}}" - -;; Public functions are of the form `mustache-FOO`, private -;; functions/variables are of the form `mst--FOO`. - -;;; Code: - -(load "mustache-render.el") - -;; todo: add flag to set tolerance of missing variables -(defun mustache-render (template context) - "Render a mustache TEMPLATE with hash table CONTEXT." - (mst--render template context)) - -(defvar mustache-partial-paths nil - "A list of paths to be searched for mustache partial templates (files ending .mustache).") - -(defvar mustache-key-type 'string - "What type of key we expect in contexts. -Can take the value 'string or 'keyword. - -For 'string we expect contexts of the form: -#s\(hash-table data \(\"name\" \"J. Random user\"\)\) - -for 'keyword we expect contexts of the form: -#s\(hash-table data \(:name \"J. Random user\"\)\) -") - -(provide 'mustache) -;;; mustache.el ends here diff --git a/elpa/mustache-20230713.514/mustache-autoloads.el b/elpa/mustache-20230713.514/mustache-autoloads.el new file mode 100644 index 00000000..7dd74c30 --- /dev/null +++ b/elpa/mustache-20230713.514/mustache-autoloads.el @@ -0,0 +1,26 @@ +;;; mustache-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- +;; +;;; Code: + +(add-to-list 'load-path (directory-file-name + (or (file-name-directory #$) (car load-path)))) + + +;;;### (autoloads nil "mustache" "mustache.el" (0 0 0 0)) +;;; Generated autoloads from mustache.el + +(register-definition-prefixes "mustache" '("mst--" "mustache-")) + +;;;*** + +;;;### (autoloads nil nil ("mustache-pkg.el") (0 0 0 0)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; coding: utf-8 +;; End: +;;; mustache-autoloads.el ends here diff --git a/elpa/mustache-20230713.514/mustache-pkg.el b/elpa/mustache-20230713.514/mustache-pkg.el new file mode 100644 index 00000000..ff1ce174 --- /dev/null +++ b/elpa/mustache-20230713.514/mustache-pkg.el @@ -0,0 +1,16 @@ +(define-package "mustache" "20230713.514" "Mustache templating library in emacs lisp" + '((emacs "26") + (s "1.3.0") + (dash "1.2.0")) + :commit "229e01f0f0a5684499bcc6a11a5bf8dbe14fd4e8" :authors + '(("Wilfred Hughes" . "me@wilfred.me.uk")) + :maintainers + '(("Wilfred Hughes" . "me@wilfred.me.uk")) + :maintainer + '("Wilfred Hughes" . "me@wilfred.me.uk") + :keywords + '("convenience" "mustache" "template") + :url "https://github.com/Wilfred/mustache.el") +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/mustache-20230713.514/mustache.el b/elpa/mustache-20230713.514/mustache.el new file mode 100644 index 00000000..925df156 --- /dev/null +++ b/elpa/mustache-20230713.514/mustache.el @@ -0,0 +1,465 @@ +;;; mustache.el --- Mustache templating library in emacs lisp + +;; Copyright (C) 2013 Wilfred Hughes + +;; Author: Wilfred Hughes +;; Version: 0.24 +;; Keywords: convenience mustache template +;; Package-Requires: ((emacs "26") (s "1.3.0") (dash "1.2.0")) +;; URL: https://github.com/Wilfred/mustache.el + +;; This program 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. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; See documentation at https://github.com/Wilfred/mustache.el + +;; Note on terminology: We treat mustache templates as a sequence of +;; strings (plain text), and tags (anything wrapped in delimeters: +;; {{foo}}). A section is a special tag that requires closing +;; (e.g. {{#foo}}{{/foo}}). + +;; We treat mustache templates as if they conform to a rough grammar: + +;; TEMPLATE = plaintext | TAG | SECTION | TEMPLATE TEMPLATE +;; SECTION = OPEN-TAG TEMPLATE CLOSE-TAG +;; TAG = "{{" text "}}" + +;; Public functions are of the form `mustache-FOO`, private +;; functions/variables are of the form `mst--FOO`. + +;;; Code: + +(require 's) +(require 'map) +(require 'dash) +(require 'cl-lib) + +;; todo: add flag to set tolerance of missing variables +(defun mustache-render (template context) + "Render a mustache TEMPLATE with hash table CONTEXT." + (-> template + mst--lex + mst--clean-whitespace + mst--parse + (mst--render-section-list context))) + +(defvar mustache-partial-paths nil + "A list of paths to be searched for mustache partial templates (files ending .mustache).") + +(defvar mustache-key-type 'string + "What type of key we expect in contexts. +Can take the value 'string or 'keyword. + +For 'string we expect contexts of the form: +#s(hash-table data (\"name\" \"J. Random user\")) + +for 'keyword we expect contexts of the form: +#s(hash-table data (:name \"J. Random user\"))") + +;;; Lexing + +(defun mst--lex (template) + "Iterate through TEMPLATE, splitting {{ tags }} and bare strings. +We return a list of lists: ((:text \"foo\") (:tag \"variable-name\"))" + (let ((open-delimeter "{{") + (close-delimeter "}}") + (lexemes nil)) + (while (not (s-equals? template "")) + (let* ((open-index (s-index-of open-delimeter template)) + (close-index (s-index-of close-delimeter template))) + ;; todo: error if we have an open and no close + (if (and open-index close-index + (< open-index close-index)) + ;; We have a well-formed tag. + (progn + ;; If we have a triple delimiter {{{foo}}}, we want to + ;; consider the inner content as "{foo}", so we need to + ;; increment the close index. + (when + (and (< close-index (- (length template) 2)) + (eq (elt template (+ close-index 2)) ?\})) + (setq close-index (1+ close-index))) + + (let ((between-delimeters + (substring template (+ open-index (length open-delimeter)) close-index)) + (continue-from-index (+ close-index (length close-delimeter)))) + ;; save the string before the tag + (when (> open-index 0) + (push (list :text (substring template 0 open-index)) lexemes)) + + ;; if this is a tag that changes delimeters e.g. {{=<< >>=}} + ;; then set the new open/close delimeter string + (if (s-matches-p "^=.+ .+=$" between-delimeters) + (let* (;; strip leading/trailing = + (delimeter-spec (substring between-delimeters 1 -1)) + (spec-parts (s-split " " delimeter-spec))) + (setq open-delimeter (-first-item spec-parts)) + (setq close-delimeter (-second-item spec-parts))) + + ;; otherwise it's a normal tag, so save it + (push (list :tag (s-trim between-delimeters)) lexemes)) + + ;; iterate on the remaining template + (setq template + (substring template continue-from-index)))) + ;; else only plain text left + (progn + (push (list :text template) lexemes) + (setq template ""))))) + (nreverse lexemes))) + +(defun mst--clean-whitespace (lexemes) + "Given a list of LEXEMES, remove whitespace around sections and +comments if they're on their own on a line. Modifies the original +list." + ;; iterate over all lexemes: + (dotimes (i (- (length lexemes) 2)) + ;; find sections that have plain text before and after + (let ((first (elt lexemes i)) + (second (elt lexemes (+ i 1))) + (third (elt lexemes (+ i 2)))) + (when (and (mst--text-p first) + (or + (mst--section-tag-p second) + (mst--comment-tag-p second)) + (mst--text-p third) + ;; check the section is on its own line + (string-match-p "\n *$" (mst--tag-text first)) + (string-match-p "^\n" (mst--tag-text third))) + ;; then we cleanup whitespace + (setf (elt lexemes i) (mst--no-trailing-newline first))))) + lexemes) + +(defalias 'mst--tag-text '-second-item + "Returns the text context of a tag.") + +(defun mst--no-trailing-newline (lexeme) + "Replace \"\n\" or \"\n \" at the end of a plain text LEXEME." + (list + :text + (replace-regexp-in-string "\n *$" "" (mst--tag-text lexeme)))) + +(defun mst--tag-p (lexeme) + "Is LEXEME a tag?" + (eq (car lexeme) :tag)) + +(defun mst--section-tag-p (lexeme) + "Is LEXEME a section tag?" + (or + (mst--open-section-tag-p lexeme) + (mst--inverted-section-tag-p lexeme) + (mst--close-section-tag-p lexeme))) + +(defun mst--open-section-tag-p (lexeme) + "Is LEXEME an open section tag? +See also `mst--inverted-section-tag-p'." + (and (mst--tag-p lexeme) + (s-starts-with-p "#" (-second-item lexeme)))) + +(defun mst--inverted-section-tag-p (lexeme) + "Is LEXEME an inverted section tag?" + (and (mst--tag-p lexeme) + (s-starts-with-p "^" (-second-item lexeme)))) + +(defun mst--close-section-tag-p (lexeme) + "Is LEXEME a close section tag?" + (and (mst--tag-p lexeme) + (s-starts-with-p "/" (-second-item lexeme)))) + +(defun mst--comment-tag-p (lexeme) + "Is LEXEME a comment tag?" + (and (mst--tag-p lexeme) + (s-starts-with-p "!" (-second-item lexeme)))) + +(defun mst--unescaped-tag-p (lexeme) + "Is LEXEME an unescaped variable tag?" + (and (mst--tag-p lexeme) + (let ((text (-second-item lexeme))) + (or + (s-starts-with-p "&" text) + (and + (s-starts-with-p "{" text) + (s-ends-with-p "}" text)))))) + +(defun mst--partial-tag-p (lexeme) + "Is LEXEME a partial tag?" + (and (mst--tag-p lexeme) + (s-starts-with-p ">" (-second-item lexeme)))) + +(defun mst--section-p (lexeme) + "Is LEXEME a nested section?" + (not (atom (car lexeme)))) + +(defun mst--text-p (lexeme) + "Is LEXEME plain text?" + (eq (car lexeme) :text)) + +;; fixme: assumes the delimeters haven't changed +;; fixme: mst--lex doesn't preserve whitespace +(defun mst--unlex (lexemes) + "Given a lexed (and optionally parsed) list of LEXEMES, +return the original input string." + (if lexemes + (let ((lexeme (car lexemes)) + (rest (cdr lexemes))) + (cond + ;; recurse on this section, then the rest + ((mst--section-p lexeme) + (concat (mst--unlex lexeme) (mst--unlex rest))) + ((mst--tag-p lexeme) + ;; restore the delimeters, then unlex the rest + (let ((tag-name (-second-item lexeme))) + (concat "{{" tag-name "}}" (mst--unlex rest)))) + ;; otherwise, it's just raw text + (t + (let ((text (-second-item lexeme))) + (concat text (mst--unlex rest)))))) + "")) + +;;; Parsing + +(defvar mst--remaining-lexemes nil + "Since `mst--parse-inner' recursively calls itself, we need a shared value to mutate.") + +(defun mst--parse (lexemes) + "Given a list LEXEMES, return a list of lexemes nested according to #tags or ^tags." + (setq mst--remaining-lexemes lexemes) + (mst--parse-inner)) + +(defun mst--parse-inner (&optional section-name) + "Parse `mst--remaining-lexemes', and return a list of lexemes nested according to #tags or ^tags." + (let (parsed-lexemes + lexeme) + (catch 'done + (while mst--remaining-lexemes + (setq lexeme (pop mst--remaining-lexemes)) + (cond + ((mst--open-section-p lexeme) + ;; recurse on this nested section + (push (cons lexeme (mst--parse-inner (mst--section-name lexeme))) + parsed-lexemes)) + ((mst--close-section-p lexeme) + ;; this is the last tag in this section + (unless (equal section-name (mst--section-name lexeme)) + (error "Mismatched brackets: You closed a section with %s, but it wasn't open" section-name)) + (push lexeme parsed-lexemes) + (throw 'done nil)) + (t + ;; this is just a tag in the current section + (push lexeme parsed-lexemes))))) + + ;; ensure we aren't inside an unclosed section + (when (and section-name (not (mst--close-section-p lexeme))) + (error "Unclosed section: You haven't closed %s" section-name)) + + (nreverse parsed-lexemes))) + +(defun mst--open-section-p (lexeme) + "Is LEXEME a #tag or ^tag ?" + (-let [(type value) lexeme] + (and (eq type :tag) + (or + (s-starts-with-p "#" value) + (s-starts-with-p "^" value))))) + +(defun mst--close-section-p (lexeme) + "Is LEXEME a /tag ?" + (-let [(type value) lexeme] + (and (eq type :tag) + (s-starts-with-p "/" value)))) + +;;; Rendering + +(defvar mustache-key-type) +(defvar mustache-partial-paths) + +;; todo: set flag to set tolerance of missing templates +(defun mst--get-partial (name) + "Get the first partial whose file name is NAME.mustache, or nil otherwise. +Partials are searched for in `mustache-partial-paths'." + (cl-block nil + (unless (listp mustache-partial-paths) + (error "`mustache-partial-paths' must be a list of paths")) + (let ((partial-name (format "%s.mustache" name))) + (dolist (path mustache-partial-paths) + (-when-let* + ((partials (directory-files path nil "\\.mustache$")) + (matching-partial (--first + (string-match-p (regexp-quote partial-name) it) + partials))) + (cl-return + (with-temp-buffer + (insert-file-contents-literally (expand-file-name matching-partial path)) + (buffer-substring-no-properties (point-min) (point-max))))))))) + +(defun mst--render-section-list (sections context) + "Render a parsed list SECTIONS in CONTEXT." + (s-join + "" + (--map (mst--render-section it context) sections))) + +(defun mst--context-get (context variable-name &optional default) + "Lookup VARIABLE-NAME in CONTEXT, returning DEFAULT if not present." + (when (eq mustache-key-type 'keyword) + (setq variable-name (intern (concat ":" variable-name)))) + ;; Work around incompatibility between Emacs versions: alist lookup + ;; are tested with `equal' starting from Emacs 28, `eq' before. + ;; Branch on version rather than forcing test function for all + ;; versions since the argument specifying the test function is + ;; deprecated and might disappear. + (if (and (version< emacs-version "28") + (listp context) + (not (mst--plist-p context))) + (map-elt context variable-name default 'equal) + (map-elt context variable-name default))) + +(defun mst--tag-name (tag-text) + "Given a tag {{foo}}, {{& foo}} or {{{foo}}}, return \"foo\"." + (s-trim + (cond + ((s-starts-with-p "&" tag-text) + (substring tag-text 1)) + ((and (s-starts-with-p "{" tag-text) + (s-ends-with-p "}" tag-text)) + (substring tag-text 1 -1))))) + +(defun mst--render-tag (parsed-tag context) + "Given PARSED-TAG, render it in hash table CONTEXT." + (let ((inner-text (mst--tag-text parsed-tag))) + (cond + ((mst--comment-tag-p parsed-tag) + "") + ((mst--unescaped-tag-p parsed-tag) + (let ((variable-value (mst--context-get context (mst--tag-name inner-text) ""))) + (when (numberp variable-value) + (setq variable-value (number-to-string variable-value))) + variable-value)) + ((mst--partial-tag-p parsed-tag) + (let ((partial (mst--get-partial (s-trim (substring inner-text 1))))) + (if partial + (mustache-render partial context) + ""))) + (t ;; normal variable + (let ((variable-value (mst--context-get context inner-text ""))) + (when (numberp variable-value) + (setq variable-value (number-to-string variable-value))) + (mst--escape-html variable-value)))))) + +(defun mst--context-add (table from-table) + "Return a copy of TABLE where all the key-value pairs in FROM-TABLE have been set." + (let ((new-table (map-copy table))) + (map-do (lambda (k v) + (if (and (version< emacs-version "28") + (not (listp new-table))) + ;; Work around incompatibility between Emacs versions: + ;; `map-insert' does not work with non-lists before + ;; Emacs 28. + (map-put! new-table k v) + ;; `setq' + `map-insert' is more expensive than `map-put!', but + ;; `map-put!' appears not to work with alists + (setq new-table (map-insert new-table k v)))) + from-table) + new-table)) + +(defun mst--listp (object) + "Return t if OBJECT is a list. +Unlike `listp', does not return t if OBJECT is a function." + (and (not (functionp object)) (listp object))) + +(defun mst--section-name (section-tag) + "Get the name of this SECTION-TAG. +E.g. from {{#foo}} to \"foo\"." + (-> section-tag ;; e.g (:tag "#foo") + mst--tag-text ;; to "#foo" + (substring 1) ;; to "foo" + s-trim)) + +(defun mst--render-section (parsed-lexeme context) + "Given PARSED-LEXEME -- a lexed tag, plain text, or a nested list, +render it in CONTEXT." + (cond ((mst--section-p parsed-lexeme) + ;; nested section + (let* ((section-tag (car parsed-lexeme)) + (section-name (mst--section-name section-tag)) + (context-value (mst--context-get context section-name)) + ;; strip section open and close + (section-contents (-slice parsed-lexeme 1 -1))) + (cond + ((mst--open-section-tag-p section-tag) + (cond + ;; if the context is a list of hash tables, render repeatedly + ((mst--sequence-of-maps-p context-value) + (s-join + "" + (--map + (mst--render-section-list section-contents (mst--context-add context it)) + context-value))) + ;; if the context is a hash table, render in that context + ((mst--map-p context-value) + (mst--render-section-list section-contents (mst--context-add context context-value))) + ;; if the context is a function, call it + ((functionp context-value) + (funcall context-value (mst--unlex section-contents) context)) + ;; if it's a truthy value, render in the current context + (context-value + (mst--render-section-list section-contents context)) + ;; otherwise, don't render anything + (t ""))) + ;; render ^tags + ((mst--inverted-section-tag-p section-tag) + (if context-value + "" + (mst--render-section-list section-contents context)))))) + ((mst--tag-p parsed-lexeme) + (mst--render-tag parsed-lexeme context)) + ;; plain text + (t + (-second-item parsed-lexeme)))) + +(defun mst--escape-html (string) + "Escape HTML in STRING." + (->> string + (s-replace "&" "&") + (s-replace "<" "<") + (s-replace ">" ">") + (s-replace "'" "'") + (s-replace "\"" """))) + +(defun mst--sequence-of-maps-p (object) + "Return t if OBJECT is a sequence of maps." + (and + (not (mst--strict-cons-p object)) + (not (functionp object)) + (or (and (listp object) + (mst--map-p (car object))) + (and (vectorp object) + (mst--map-p (aref object 0)))))) + +(defun mst--strict-cons-p (object) + "Return t if OBJECT is a strict cons cell (i.e. not a list)." + (and (consp object) + (not (listp (cdr object))))) + +(defsubst mst--plist-p (list) + (and (consp list) (atom (car list)))) + +(defun mst--map-p (object) + "Return t if OBJECT is a map." + (and (not (functionp object)) + (not (mst--strict-cons-p object)) + (mapp object))) + +(provide 'mustache) +;;; mustache.el ends here