html-template.rkt
#lang racket/base
;; For legal info, see file "info.rkt".

(require (for-syntax racket/base
                     syntax/parse
                     "html-template-generate.rkt"
                     "html-template-parse.rkt"
                     "planet-neil-html-writing.rkt")
         (for-template "html-template-generate.rkt"
                       "html-template-parse.rkt"
                       "planet-neil-html-writing.rkt")
         syntax/parse
         (planet neil/mcfly)
         "html-template-generate.rkt"
         "html-template-parse.rkt"
         "planet-neil-html-writing.rkt")

(doc (section "Introduction"))

(doc (para "The "
           (bold "html-template")
           " package implements an HTML-writing template language based on "
           (link "http://www.neilvandyke.org/racket-xexp/"
                 "SXML/xexp")
           ".  Compared to doing comparable work using the "
           (link "http://www.neilvandyke.org/racket-html-writing/"
                 "html-writing")
           " package, "
           (bold "html-template")
           " offers improved compile-time error-checking, arguably better
readability of code, and possibly improved run-time efficiency.  Uses of "
           (bold "html-template")
           " are often mixed with uses of the "
           (bold "html-writing")
           " package.")

     "For example:"

     (racketblock
      (define (write-essay my-title)
        (html-template
          (html (head (title (% my-title)))
                (body (h1 (% my-title))
                      (p "Kittens claw."
                         (br)
                         "Puppies pee.")))))

      (write-essay "All About Kittens & Puppies"))

     "produces the output:"

     (nested #:style 'inset
             (racketoutput
              "<html><head><title>All About Kittens &amp; "
              "Puppies</title></head><body><h1>All About "
              "Kittens &amp; Puppies</h1><p>Kittens "
              "claw.<br>Puppies pee.</p></body></html>"))

     "Expanding that use of the "
     (racket html-template)
     " macro in this case results in something like:"

     (racketblock
      (let-values (((out) (current-output-port)))
        (parameterize ((current-output-port
                        html-template-error-catching-output-port))
          (write-bytes #"<html><head><title>" out)
          (%html-template:format/content/write my-title out)
          (write-bytes #"</title></head><body><h1>" out)
          (%html-template:format/content/write my-title out)
          (write-bytes #"</h1><p>Kittens claw.<br>Puppies pee.</p></body></html>" out)
          (void))))

     "Observe that much of the computation for HTML formatting is done at
compile time rather than at run time."

     (para "By design, the "
           (racket html-template)
           " has no special syntax for behavior such as iterating over the rows
of an SQL query result.  Instead, it provides "
           (italic "template escape")
           " syntax that supports pieces of arbitrary Racket code to be
inserted into the template to perform behavior such as this.  The different
template escapes provide different ways for the Racket code to produce HTML.
For example, the "
           (racket %write)
           " escape can be used to have a ``nested'' use of "
           (racket html-template)
           ", such as for formatting each row of a table for each row of a database
query result:")

     (racketblock
      (html-template
        (html (h1 "People")
              (p "The people are:")
              (table (@ (border "1"))
                     (tr (th "Name") (th "Color"))
                     (%write
                      (for-each (lambda (person)
                                  (html-template
                                    (tr (td (% (person-name  person)))
                                        (td (% (person-color person))))))
                                people)))
              (p "Fin."))))

     (para "Given some particular value for "
           (racket people)
           ", this produces the output:")

     (nested #:style 'inset
             (racketoutput
              "<html><h1>People</h1><p>The people are:</p><table border=\"1\"><tr><th>Name</th><th>Color</th></tr><tr><td>Juliette</td><td>Blue</td></tr><tr><td>Zbigniew</td><td>White</td></tr><tr><td>Irene</td><td>Red</td></tr></table><p>Fin.</p></html>"))

     (para "The different template escapes are discussed in more detail in the
documentation for the "
           (racket html-template)
           " form.")
     
     (para "Note that application programmers don't necessarily call "
           (racket html-template)
           " directly very often.  Programs for Web sites with particular
conventions for page layout might define their own macros for their own
conventions, which then expand to uses of "
           (racket html-template)
           "."))

;; (doc (section "Low-Level Interface"))

(provide (all-from-out               "html-template-parse.rkt"))
(provide (for-syntax   (all-from-out "html-template-parse.rkt")))
(provide (for-template (all-from-out "html-template-parse.rkt")))

(provide (all-from-out               "html-template-generate.rkt"))
(provide (for-syntax   (all-from-out "html-template-generate.rkt")))
(provide (for-template (all-from-out "html-template-generate.rkt")))

(doc (section "Interface")

     (para "The interface in this version of the package is the "
           (racket html-template)
           " form."))

(begin-for-syntax
 (define-syntax-class ordering-sc
   #:description "ordering (identifier `any', `evaluation', or `writes-and-evaluation')"
   (pattern (~or (~datum any)
                 (~datum evaluation)
                 (~datum writes-and-evaluation))))
 (define-syntax-class xexp-sc
   #:description "html-template SXML/xexp part"
   (pattern (X ...))
   (pattern S:str)))

(doc (defform/subs #:literals (@ % %format %verbatim %void %write %write/port)
       (html-template maybe-port content ...+)
       (

        (maybe-port code:blank
                    (code:line #:port output-port-or-false))

        ;; TODO: Document #:ordering once we finalize it.
        ;;
        ;; (maybe-ordering code:blank
        ;;                 (code:line #:ordering xxx))

        (content-context element
                         string?
                         bytes?
                         escape)

        (element-context element
                         escape)

        (element (symbol? maybe-attributes content-context ...))

        (maybe-attributes code:blank
                          (@ attribute-context ...+))

        (attribute-context attribute
                           escape-except-format)

        (attribute (symbol? attribute-value-context ...+))

        (attribute-value-context string?
                                 bytes?
                                 escape)

        (escape escape-except-format
                (%format expr ...+)
                (%       expr ...+))

        (escape-except-format (%verbatim       expr ...+)
                              (%write          expr ...+)
                              (%write/port var expr ...+)
                              (%void           expr ...+))))

     (para "Write the SXML/xexp template as HTML bytes to the output port specified by "
           (racket #:port)
           ", or, if that is "
           (racket #f)
           ", to "
           (racket current-output-port)
           " (the default).  The UTF-8 encoding is used.  The template consists of literal SXML/xexp, and of "
           (italic "template escapes")
           " with names beginning with "
           (racketidfont "%")
           ".")

     (para "The template escapes each permit Racket expressions, "
           (racket expr)
           "s, to be evaluated during the generation of HTML at that point in
the template, sequenced like "
           (racket begin)
           ".  The differences between the template escapes concern what is done with
the value of the sequence of expressions, such as whether the value is to be
output literally as part of the HTML or to be further processed as SXML/xexp,
and with what happens to writing to "
           (racket current-output-port)
           ".  Specifically, the escapes and their meaning are:")

     (itemlist

      (item (racket (%xexp expr ...))
            " and "
            (racket (%sxml expr ...))
            " --- "
            (racket expr)
            " evaluates to an SXML/xexp value, which is output as HTML in the
appropriate context (e.g., content context vs. attribute value context).")

      (item (racket (%format expr ...))
            " and "
            (racket (% expr ...))
            " --- "
            (racket expr)
            " evaluates to some value,
and this value is formatted for appropriate literal display in HTML.  For
example, a string value is displayed as text, an integer value is displayed as
a number, a date object is displayed as a date, etc.  The formatting handler is
customizable by the application programmer.  (Note that the meaning of "
            (racket %)
            " changed purposes in PLaneT version 2:0 of this package: in
version 1:1, it was similar to the current "
            (racket %xexp)
            ", rather than being shorthand for "
            (racket %format)
            ".")

      (item (racket (%verbatim expr ...))
            " --- "
            (racket expr)
            " evaluates to bytes, string, or a list of byteses and/or
strings, which are output verbatim as bytes.")

      (item (racket (%write expr ...))
            " --- "
            (racket expr)
            " is evaluated, and any writes to "
            (racket current-output-port)
            " are added verbatim to the output.  Note that "
            (racket %write)
            " and "
            (racket %write/port)
            " are the only template escapes that permit writing directly to a
port that goes to HTML output.")

      (item (racket (%write/port var expr ...))
            " --- Like "
            (racket %write)
            ", except that writing must be to the output port "
            (racket var)
            ".  Writing to "
            (racket current-output-port)
            " within "
            (racket %write/var)
            " will raise an error, on the assumption that it's most likely a
bug (like a missing port parameter in a "
            (racket display)
            ", "
            (racket printf)
            ", or nested "
            (racket html-template)
            ").")

      (item (racket (%void expr ...))
            " --- "
            (racket expr)
            " is evaluated, and any value is ignored.  This is used for side-effects"
            ;; TODO: Don't talk about "#:ordering" until it is finalized.
            ;;
            ;; ", when "
            ;; (racket #:ordering)
            ;; " is "
            ;; (racket 'evaluation)
            ;; " or "
            ;; (racket 'writes-and-evaluation)
            ;; " (not "
            ;; (racket 'any)
            ;; ")"
            "."))

     (para "Note that "
           (racket %write)
           " is the only escape that permits an "
           (racket expr)
           " to write to "
           (racket current-output-port)
           " --- an error will be raised in all other escapes."))
(provide html-template)
(define-syntax (html-template stx)
  (syntax-parse stx
    ((_ (~or (~optional (~seq #:port     PORT))                #:name "#:port option"
             (~optional (~seq #:ordering ORDERING:ordering-sc) #:name "#:ordering option"))
        ...
        BODY:xexp-sc ...)
     (with-syntax ((PORT     (or (attribute PORT)     (syntax/loc stx (current-output-port))))
                   (ORDERING (or (attribute ORDERING) #'guess)))
       (expand-html-template
        #:error-name  'html-template
        #:stx         stx
        #:ordering    (syntax ORDERING.value)
        #:reverse-lvs '()
        #:irep        (compress-html-template-irep
                       (parse-html-template 'html-template
                                            (syntax (BODY ...))))
        #:port-stx    (syntax PORT))))))

(doc history

     (#:planet 2:1 #:date "2012-09-12"
               (itemlist
                (item "Element and attribute names can now contain minus and
underscore characters.")
                (item "Commented-out some "
                      (racket log-debug)
                      " uses.")))
               
     (#:planet 2:0 #:date "2012-06-12"
               (itemlist
                (item "Heavy API changes, including changing all the template escapes.")
                (item "Heavy internal changes, to enable optimizations in the forthcoming "
                      (code "web-server-xexp")
                      " package.")
                (item "Much more testing.")
                (item "Converted to use McFly and Overeasy.")))

     (#:version "0.2" #:planet 1:1 #:date "2011-08-22"
                (itemlist
                 (item "Added "
                       (code "%")
                       " as alias for "
                       (code "%eval")
                       ".")))

     (#:version "0.1" #:planet 1:0 #:date "2011-08-21"
                (itemlist
                 (item "Initial release."))))