;;;; normalize-page.el -- rebuild a page usinga given template ;;; Time-stamp: <2005-01-18 12:05:56 john> ;;; Code to put a page together using a given template and the data ;;; extracted mostly using functions and variables in ;;; page-attributes.el (which understands a lot more than just the name ;;; and URL of the page file). The template is a list of things to ;;; evaluate (in the context of the buffer containing the original ;;; page and processed by webmaster:find-file-hook from ;;; page-attributes.el -- each thing should return a string (or list / ;;; tree of strings) to be inserted into the new buffer in order. (provide 'normalize-page) (require 'webmaster-macros) (require 'page-attributes) (defun insert-string-or-tree (thing) "Insert THING which may be a string, list of strings, list of (string or list...)..." (when thing (if (or (stringp thing) (integerp thing)) (insert thing) (mapcar 'insert-string-or-tree thing)))) (defun webmaster:format-apache-directives (directives &optional just-these) "Format DIRECTIVES for output by insert-string-or-tree. With optional JUST-THESE, format only the ones matching elements of JUST-THESE." (mapcar (function (lambda (directive) (if (or (null just-these) (string-match-any just-these directive)) (list directive "\n") nil))) directives)) (defun metadata-match (metadata-item match-these) "Return whether METADATA-ITEM matches any of MATCH-THESE." (let ((metadata-tail (cdr metadata-item))) (catch 'matched (dolist (match-this match-these) (when (and (or (null match-this) (string-match (car match-this) (car metadata-item))) (not (catch 'failed (dolist (option-pair (cdr match-this)) (let ((data-pair (assoc (car option-pair) metadata-tail))) (if (and data-pair (not (string-match (cdr option-pair) (cdr data-pair)))) (throw 'failed t)))) nil))) (throw 'matched t))) nil))) (defun webmaster:normalize-metadata (head-tags &optional just-these) "Format HEAD-TAGS for output by insert-string-or-tree. With optional JUST-THESE, format only the ones matching elements of JUST-THESE, which are in the form of (headmatch (fieldname . fieldmatch)+ )." (mapcar (function (lambda (head-tag) (if (or (null just-these) (metadata-match head-tag just-these)) (append (list "<" (car head-tag)) (mapcar (function (lambda (option-pair) (list " " (car option-pair) "=\"" (cdr option-pair) "\""))) (cdr head-tag)) '(">\n")) nil))) head-tags)) ;;;###autoload (defun get-page-attribute (attribute) "Get ATTRIBUTE of this page. The attribute may be found from various places, including meta-data and apache directives. These places are in general those found by webmaster:set-page-variables in page-attributes.el." (interactive "sShow attribute: ") (let ((result (let* ((var-name-string (if (stringp attribute) attribute (symbol-name attribute))) (var-name-symbol (if (symbolp attribute) attribute (intern attribute))) (apache-var (get-page-head-apache-variable var-name-string))) ;; (message "apache-var=%S" apache-var) (if apache-var apache-var (let ((meta-var (get-page-head-tag "meta" "name" var-name-string))) ;; (message "meta-var=%S" meta-var) (if meta-var (cdr (assoc "value" (cdr meta-var))) (let ((symbol (intern (concat "webmaster:page-" var-name-string)))) ;; (message "get-page-attribute made symbol %S bound=%S" symbol (boundp symbol)) (if (boundp symbol) (symbol-value symbol) nil)))))))) (when (interactive-p) (message "Attribute %s has value %s" attribute result)) result)) ;;;###autoload (defun set-page-attribute (attribute value) "Set ATTRIBUTE of this page to VALUE. ATTRIBUTE may be a symbol or a string. The new value will be written into the file when it is rebuilt by normalize-page-buffer. The attribute may be stored in various places, including meta-data and apache directives." (interactive "sSet attribute: sSet attribute %s to: ") (if (not (null value)) (let* ((var-name-string (if (stringp attribute) attribute (symbol-name attribute))) (var-name-symbol (intern (substitute ?_ ?- var-name-string)))) (set-page-head-tag "meta" "name" var-name-string (list (cons "value" value))) (apache-set var-name-symbol value) (set (intern (concat "webmaster:page-" var-name-string)) value) ))) (defun spread-page-attribute (attribute) "Write ATTRIBUTE into all the places in which it can be stored, having found it from one of the such places." (set-page-attribute attribute (get-page-attribute attribute))) (defun meld-page-info () "Meld the various pieces of information held about the page." (interactive) ;; (webmaster:show-webmaster-page-variables (format "*%s before meld*" (buffer-name))) (mapcar 'spread-page-attribute '(author keywords stylesheet description title logo site-homepage-url directory-name )) ;; (webmaster:show-webmaster-page-variables (format "*%s after meld*" (buffer-name))) ) (defvar current-html-page-template-name "" "The name of the template being used. This variable may be used within templates.") (defun normalize-page-buffer (template old-buffer new-buffer) "Using TEMPLATE and data from OLD-BUFFER construct a normalized equivalent page in NEW-BUFFER." (set-buffer old-buffer) (unless webmaster:page-url ; non-nil when webmaster:set-page-variables has been run in the buffer (webmaster:set-page-variables)) (meld-page-info) (let ((items (mapcar 'eval template))) (set-buffer new-buffer) (let ((html-helper-build-new-buffer nil) (html-helper-mode-hook nil)) (html-helper-mode)) (erase-buffer) (mapcar 'insert-string-or-tree items))) ;;;###autoload (defun normalize-page-file (file template) "Normalize FILE using TEMPLATE. The old file is saved, to be on the safe side." (interactive (list (read-file-name "Normalize page file: ") (let ((completion-ignore-case t)) (completing-read "Template: " page-templates)))) (let* ((full-file (expand-file-name file)) (shortfile (file-name-nondirectory full-file)) (old-name (expand-file-name (concat "old---" shortfile) (file-name-directory full-file)))) (find-file file) (basic-save-buffer) (when (file-exists-p old-name) (delete-file old-name)) (copy-file file old-name) (let* ((src (current-buffer)) (dest (get-buffer-create (format "*new-%s*" shortfile)))) (set-buffer dest) (erase-buffer) (set-buffer src) (let ((current-html-page-template-name template)) (normalize-page-buffer (getpagetemplate template) src dest)) (kill-buffer src) (delete-file full-file) (set-buffer dest) (write-file full-file) (normal-mode) ))) ;;;###autoload (defun normalize-this-page-file (template) "Do a normalize-page-file on the file in this buffer, using TEMPLATE." (interactive (list (let ((completion-ignore-case t)) (completing-read "Template: " page-templates)))) (let ((filename (buffer-file-name))) (normalize-page-file filename template) (when (interactive-p) (find-file filename)))) ;;;###autoload (defun normalize-web-tree (tree template) "Normalize TREE using TEMPLATE. Each old file is saved, to be on the safe side." (interactive (list (read-file-name "Normalize web tree: ") (let ((completion-ignore-case t)) (completing-read "Template: " page-templates)))) (webmaster:apply-throughout-tree tree 'normalize-this-page-file (list template))) (defun compare-buffers (a b) "Compare buffers A and B." ;; written for test-normalize-page (let ((case-fold-search nil)) (set-buffer a) (let ((a-size (buffer-size))) (set-buffer b) (zerop (compare-buffer-substrings a 1 (1- a-size) b 1 (1- (buffer-size))))))) (defun test-normalize-page (template) "Test a page, using TEMPLATE." (interactive (list (let ((completion-ignore-case t)) (completing-read "Template: " page-templates)))) (let* ((stack-trace-on-error t) (inbuffer (current-buffer)) (base-file-name (file-name-nondirectory (buffer-file-name))) (outbuffnameformat (format "test-%s-%%d-%%d-%s" (substring template 0 (string-match "[^a-z]" template)) base-file-name))) (dotimes (i 3) (let ((outbuffname (format outbuffnameformat 0 i))) (when (get-buffer outbuffname) (kill-buffer outbuffname)) (let ((outbuff (get-buffer-create outbuffname))) (normalize-page-buffer (getpagetemplate template) inbuffer outbuff) outbuff))) (let* ((same-0-1 (compare-buffers (format outbuffnameformat 0 0) (format outbuffnameformat 0 1))) (same-1-2 (compare-buffers (format outbuffnameformat 0 1) (format outbuffnameformat 0 2)))) (message "same-0-1=%S same-1-2=%S" same-0-1 same-1-2) (if (and same-0-1 same-1-2) (progn ;; now test for idempotence -- run one of the copies for a few generations (message "finding F1 buffer") (set-buffer (format outbuffnameformat 0 1)) (message "Got F1 buffer") (dotimes (gen 3) (message "Writing latest so far as gen-%d" gen) (let ((gen-file-name (expand-file-name (format outbuffnameformat gen 1)))) (write-file gen-file-name) (kill-buffer nil) ;; make it set the buffer variables (message "Finding %s" gen-file-name) (find-file gen-file-name) (let ((inbuffname (buffer-name)) (outbuffname (format outbuffnameformat (1+ gen) 0))) (when (get-buffer outbuffname) (message "Killing pre-existing output buffer of same name (%s)" outbuffname) (kill-buffer outbuffname)) (let ((outbuff (get-buffer-create outbuffname))) (normalize-page-buffer (getpagetemplate template) inbuffname outbuff) (let ((same (compare-buffers inbuffname outbuffname))) (message "%s at generation %d" (if same "same" "different") gen) ) (set-buffer outbuff)))) )) (error "Variation in the first generation") ) ))) (defvar page-templates nil "The page templates declared so far, as an alist of title to varname.") ;;;###autoload (defmacro defpagetemplate (varname title elements docstring) "Define a page template in VARNAME with TITLE and ELEMENTS and DOCSTRING." `(progn (defvar ,varname ,elements ,docstring) (setq ,varname ,elements) (let ((pair (assoc ,title page-templates))) (if pair (rplacd pair ',varname) (push (cons ,title ',varname) page-templates))))) (defun getpagetemplate (title) "Return the page template registered with TITLE." (let ((name (cdr (assoc title page-templates)))) (if name (symbol-value name) nil))) (defpagetemplate page-template-plain-complete "Plain but complete" '( "\n" "\n" "\n" (webmaster:format-apache-directives webmaster:page-head-apache-directives) (webmaster:normalize-metadata webmaster:page-head-tags) "" webmaster:page-title "\n" "\n" "\n" "\n" webmaster:page-top-banner "\n" "

" webmaster:page-first-heading "

\n" (webmaster:page-body-content) webmaster:page-footer "\n" "\n" ) "A page template that includes everything significant from the page, but no boilerplate.") (defpagetemplate page-template-with-thick-boilerplate "Ornate and complete" '( "\n" "\n" "\n" "\n" (webmaster:format-apache-directives webmaster:page-head-apache-directives) "\n" "\n" (webmaster:normalize-metadata webmaster:page-head-tags) "\n" "" webmaster:page-title "\n" "\n" "\n" "\n" webmaster:page-top-banner "\n" "\n" "

" webmaster:page-first-heading "

\n" "\n" "\n" (webmaster:page-body-content) "\n" "\n" webmaster:page-footer "\n" "\n" "\n" ) "A page template that includes everything significant from the page, plus lots of structural information.") (defpagetemplate page-template-all-inline "Inline, with comments separating mechanically produced material" '( "\n" "\n" "\n" "\n" (webmaster:normalize-metadata webmaster:page-head-tags '(("meta") ("link"))) "\n" "\n" "\n" "" webmaster:page-title "\n" "\n" "\n" "\n" webmaster:page-top-banner "\n" "\n" "

" webmaster:page-first-heading "

\n" "\n" "\n" (webmaster:page-body-content) "\n" "\n" webmaster:page-footer "\n" "\n" "\n" ) "A page template that includes everything significant from the page, plus lots of structural information. Apache variables and SSIs are not used; everything is done inline.") (defpagetemplate page-template-all-ssi "Using SSI a lot, with inline comments delimiting things." '( "\n" (webmaster:format-apache-directives webmaster:page-head-apache-directives) "\n" "\n" "\n" "

" webmaster:page-first-heading "

\n" "\n" "\n" (webmaster:page-body-content) "\n" "\n" ) "A page template that includes everything significant from the page, plus lots of structural information. Just about everything is done through the apache SSI mechanism.") (defpagetemplate page-template-all-ssi-even-first-heading "Using SSI a lot (even for the first heading), with inline comments delimiting things." '( "\n" (webmaster:format-apache-directives webmaster:page-head-apache-directives) "\n" "\n" "\n" (webmaster:page-body-content) "\n" "\n" ) "A page template that includes everything significant from the page, plus lots of structural information. Just about everything is done through the apache SSI mechanism.") ;;; end of normalize-page.el