got regenerate working

pull/9/head
Matthew Butterick 11 years ago
parent 1405113ed1
commit dbc1c28d95

@ -1,5 +1,7 @@
#lang racket #lang racket
;; todo: add command to check validity of installation
(require (for-syntax "readability.rkt")) (require (for-syntax "readability.rkt"))
(define-syntax (handle-pollen-command stx) (define-syntax (handle-pollen-command stx)

@ -140,10 +140,29 @@
(path? . -> . path?) (path? . -> . path?)
(remove-ext path)) (remove-ext path))
(define/contract (make-pollen-output-path thing ext)
(pathish? (or/c string? symbol?) . -> . path?)
(add-ext (remove-ext (->path thing)) ext))
(module+ test
(check-equal? (make-pollen-output-path (->path "foo.p") 'html) (->path "foo.html"))
(check-equal? (make-pollen-output-path (->path "/Users/mb/git/foo.p") 'html) (->path "/Users/mb/git/foo.html"))
(check-equal? (make-pollen-output-path "foo" 'xml) (->path "foo.xml"))
(check-equal? (make-pollen-output-path 'foo 'barml) (->path "foo.barml")))
;; turns input into corresponding pollen source path
;; does not, however, validate that new path exists
;; todo: should it? I don't think so, sometimes handy to make the name for later use
;; OK to use pollen source as input (comes out the same way)
(define/contract (make-pollen-source-path thing) (define/contract (make-pollen-source-path thing)
(path? . -> . path?) (pathish? . -> . path?)
(add-ext (remove-ext (->path thing)) POLLEN_SOURCE_EXT)) (add-ext (remove-ext (->path thing)) POLLEN_SOURCE_EXT))
(module+ test
(check-equal? (make-pollen-source-path (->path "foo.p")) (->path "foo.p"))
(check-equal? (make-pollen-source-path "foo") (->path "foo.p"))
(check-equal? (make-pollen-source-path 'foo) (->path "foo.p")))
(define/contract (project-files-with-ext ext) (define/contract (project-files-with-ext ext)
(symbol? . -> . (listof complete-path?)) (symbol? . -> . (listof complete-path?))
(filter (λ(i) (has-ext? i ext)) (directory-list pollen-file-root))) (filter (λ(i) (has-ext? i ext)) (directory-list pollen-file-root)))

@ -30,16 +30,17 @@
(check-true (exact-integer? (path->mod-date-value (build-path (current-directory) (->path "regenerate.rkt")))))) (check-true (exact-integer? (path->mod-date-value (build-path (current-directory) (->path "regenerate.rkt"))))))
;; put list of paths into mod-dates ;; put list of paths into mod-dates
;; want to take list as input (rather than individual path) ;; need list as input (rather than individual path)
;; because hash key needs to be a list ;; because hash key needs to be a list
(define/contract (store-refresh-in-mod-dates path-or-paths) ;; so it's convenient to use a rest argument
((or/c path? (listof path?)) . -> . void?) ;; therefore, use function by just listing out the paths
(define paths (->list path-or-paths)) (define/contract (store-refresh-in-mod-dates . rest-paths)
(hash-set! mod-dates paths (map path->mod-date-value paths))) (() #:rest (listof path?) . ->* . void?)
(hash-set! mod-dates rest-paths (map path->mod-date-value rest-paths)))
(module+ test (module+ test
(reset-mod-dates) (reset-mod-dates)
(store-refresh-in-mod-dates (list (build-path (current-directory) (->path "regenerate.rkt")))) (store-refresh-in-mod-dates (build-path (current-directory) (->path "regenerate.rkt")))
(check-true (= (len mod-dates) 1)) (check-true (= (len mod-dates) 1))
(reset-mod-dates)) (reset-mod-dates))
@ -52,24 +53,24 @@
(module+ test (module+ test
(reset-mod-dates) (reset-mod-dates)
(store-refresh-in-mod-dates (list (build-path (current-directory) (->path "regenerate.rkt")))) (store-refresh-in-mod-dates (build-path (current-directory) (->path "regenerate.rkt")))
(reset-mod-dates) (reset-mod-dates)
(check-true (= (len mod-dates) 0))) (check-true (= (len mod-dates) 0)))
;; how to know whether a certain combination of paths needs a refresh ;; how to know whether a certain combination of paths needs a refresh
(define/contract (source-needs-refresh? path-or-paths) ;; use rest argument here so calling pattern matches store-refresh
((or/c path? (listof path?)) . -> . boolean?) (define/contract (mod-date-expired? . rest-paths)
(let ([paths (->list path-or-paths)]) (() #:rest (listof path?) . ->* . boolean?)
(or (not (paths . in? . mod-dates)) ; no stored mod date (or (not (rest-paths . in? . mod-dates)) ; no stored mod date
(not (equal? (map path->mod-date-value paths) (get mod-dates paths)))))) ; data has changed (not (equal? (map path->mod-date-value rest-paths) (get mod-dates rest-paths))))) ; data has changed
(module+ test (module+ test
(reset-mod-dates) (reset-mod-dates)
(let ([path (build-path (current-directory) (->path "regenerate.rkt"))]) (let ([path (build-path (current-directory) (->path "regenerate.rkt"))])
(store-refresh-in-mod-dates (list path)) (store-refresh-in-mod-dates path)
(check-false (source-needs-refresh? (list path))) (check-false (mod-date-expired? path))
(reset-mod-dates) (reset-mod-dates)
(check-true (source-needs-refresh? (list path))))) (check-true (mod-date-expired? path))))
;; convenience function for external modules to use ;; convenience function for external modules to use
@ -91,7 +92,7 @@
;; this will catch pmap (pollen map) files ;; this will catch pmap (pollen map) files
[(pmap-source? path) (let ([pmap (dynamic-require path 'main)]) [(pmap-source? path) (let ([pmap (dynamic-require path 'main)])
(regenerate-with-pmap pmap #:force force))] (regenerate-with-pmap pmap #:force force))]
[else (error "Regenerate: no handler for" (->string (file-name-from-path path)))]))) [else (message "Regenerate: passing through" (->string (file-name-from-path path)))])))
;; todo: write tests ;; todo: write tests
@ -128,7 +129,7 @@
;; you can trigger a refresh just by deleting the file ;; you can trigger a refresh just by deleting the file
(not (file-exists? output-path)) (not (file-exists? output-path))
;; 3) file otherwise needs refresh (e.g., it changed) ;; 3) file otherwise needs refresh (e.g., it changed)
(source-needs-refresh? source-path)) (mod-date-expired? source-path))
;; use single quotes to escape spaces in pathnames ;; use single quotes to escape spaces in pathnames
(let ([command (format "~a '~a' > '~a'" RACKET_PATH source-path output-path)]) (let ([command (format "~a '~a' > '~a'" RACKET_PATH source-path output-path)])
(regenerating-message (format "~a from ~a" (regenerating-message (format "~a from ~a"
@ -147,92 +148,111 @@
;; todo: write tests ;; todo: write tests
;; utility function for regenerate-with-template
;;;;;;;;;;;;;;; (define/contract (handle-source-rerequire source-path)
;; todo next (path? . -> . boolean?)
;;;;;;;;;;;;;;;
(define (regenerate-with-template path [template-name empty] #:force [force #f])
; take full path or filename
; return full path of templated file
(define source-path (->complete-path ;; dynamic-rerequire watches files to see if they change.
(if (pollen-source? path) ;; if so, then it reloads them.
path ;; therefore, it's useful in a development environment
(make-pollen-source-path path)))) ;; because it reloads as needed, but otherwise not.
(define-values (source-dir source-name ignored) (split-path source-path)) (define-values (source-dir source-name _) (split-path source-path))
;; need to require source file (to retrieve template name, which is in metas)
;; but use dynamic-rerequire now to force refresh for dynamic-require later,
;; otherwise the source file will cache
;; by default, rerequire reports reloads to error port.
;; set up a port to catch messages from dynamic-rerequire
;; and then examine this message to find out if anything was reloaded.
(define port-for-catching-file-info (open-output-string))
; get body out of source file (to retrieve template name) ;; parameterize needed because source files have relative requires in project directory
; use dynamic-rerequire to force refresh for dynamic-require, ;; parameterize is slow, IIRC
; otherwise it will cache
; parameterize needed because source files have relative requires
(define file-was-reloaded-port (open-output-string))
(parameterize ([current-directory source-dir] (parameterize ([current-directory source-dir]
[current-error-port file-was-reloaded-port]) [current-error-port port-for-catching-file-info])
; by default, rerequire reports reloads to error port.
; so capture this message to find out if anything was reloaded.
(dynamic-rerequire source-path)) (dynamic-rerequire source-path))
(define file-was-reloaded? ;; if the file needed to be reloaded, there will be a message in the port
(> (string-length (get-output-string file-was-reloaded-port)) 0)) ;; this becomes the return value
(->boolean (> (len (get-output-string port-for-catching-file-info)) 0)))
;; apply template
(define/contract (regenerate-with-template x [template-name #f] #:force [force #f])
((pathish?) (path? #:force boolean?) . ->* . void?)
;; set up information about source
(define source-path (->complete-path (make-pollen-source-path (->path x))))
(define-values (source-dir source-name _) (split-path source-path))
; set template, regenerate, get data ;; find out whether source had to be reloaded
; first, if no template name provided, look it up (define source-reloaded? (handle-source-rerequire source-path))
(when (or (empty? template-name) (not (file-exists? (build-path source-dir template-name))))
; get template name out of meta fields. ;; Then the rest:
; todo: template file in body may not refer to a file that exists. ;; set the template, regenerate the source file with template, and catch the output.
; todo: consider whether file-was-reloaded could change metas ;; 1) Set the template.
; (because here, I'm retrieving them from existing source) (define template-path
(build-path source-dir
;;;;;;;;;;;;;; ;; if template name provided and exists
;; todo: next (if (and template-name (file-exists? (build-path source-dir template-name)))
;;;;;;;;;;;;;; ;; OK to use template-name as given
template-name
(define metas (dynamic-require source-path 'metas)) ;; Otherwise — try to get template name out of meta fields.
(set! template-name (if (TEMPLATE_META_KEY . in? . metas) ;; todo: consider that template file in metas
(get metas TEMPLATE_META_KEY) ;; may not refer to a file that exists.
DEFAULT_TEMPLATE))) ;; what then?
(define template-path (build-path source-dir template-name)) (let ([source-metas (dynamic-require source-path 'metas)])
; refresh template (it might have its own p file) (if (TEMPLATE_META_KEY . in? . source-metas)
(get source-metas TEMPLATE_META_KEY)
DEFAULT_TEMPLATE)))))
;; refresh template (it might have its own preprocessor file)
(regenerate template-path #:force force) (regenerate template-path #:force force)
; calculate new path for generated file: ;; calculate new path for generated file: base from source + ext from template
; base from source + ext from template (define output-path (make-pollen-output-path source-path (get-ext template-path)))
(define generated-path (build-path source-dir (add-ext (remove-ext source-name) (get-ext template-path))))
; do we need to refresh? ;; 2) Regenerate the source file with template, if needed.
(when (or force ;; Regenerate is expensive, so we avoid it when we can.
(not (file-exists? generated-path)) ;; Four conditions where we regenerate:
(source-needs-refresh? source-path template-path) (if (or force ; a) it's explicitly demanded
file-was-reloaded?) (not (file-exists? output-path)) ; b) output file does not exist
(store-refresh-in-mod-dates source-path template-path) ;; c) mod-dates indicates refresh is needed
(mod-date-expired? source-path template-path)
; Templates are part of the compile operation. ;; d) dynamic-rerequire indicates the source had to be reloaded
; Therefore no way to arbitrarily invoke template at run-time. source-reloaded?)
; This routine creates a new namespace and compiles the template within it. (begin
; Todo: performance improvement would be to make a macro (store-refresh-in-mod-dates source-path template-path)
; that pre-compiles all known templates into their own functions. (let ([page-result (render-source-with-template source-path template-path)])
; then apply-template can either look for one of those functions, (display-to-file #:exists 'replace page-result output-path)
; if the template exists, (regenerated-message (file-name-from-path output-path))))
; or if not found, use the eval technique. (message "File is up to date:" (->string (file-name-from-path output-path)))))
(define page-result
; parameterize current-directory to make file requires work
(parameterize ([current-namespace (make-base-empty-namespace)]
[current-directory source-dir]
[current-output-port (open-output-nowhere)])
(namespace-require 'racket) ; use namespace-require for FIRST require, then eval after
(eval '(require (planet mb/pollen/template)) (current-namespace))
; import source into eval space,
; automatically sets up main & metas & here
(eval `(require ,(path->string source-name)) (current-namespace))
(eval `(include-template #:command-char ,TEMPLATE_FIELD_DELIMITER ,template-name) (current-namespace))))
(display-to-file #:exists 'replace page-result generated-path)
(regenerated-message generated-path)))
(define/contract (render-source-with-template source-path template-path)
(complete-path? complete-path? . -> . string?)
;; set up information about source and template paths
(define-values (source-dir source-name _) (split-path source-path))
(define-values (template-dir template-name __) (split-path template-path))
;; Templates are part of the compile operation.
;; Therefore no way to arbitrarily invoke template at run-time.
;; This routine creates a new namespace and compiles the template within it.
;; parameterize current-directory to make file requires work
;; the below expression will evaluate to a string
;; that represents the output of the operation.
(parameterize ([current-namespace (make-base-empty-namespace)]
[current-directory source-dir]
[current-output-port (open-output-nowhere)])
(namespace-require 'racket) ; use namespace-require for FIRST require, then eval after
(eval '(require (only-in web-server/templates include-template)) (current-namespace))
(eval '(require (planet mb/pollen/pmap)) (current-namespace))
; import source into eval space,
; automatically sets up main & metas & here
(eval `(require ,(path->string source-name)) (current-namespace))
(eval `(include-template #:command-char ,TEMPLATE_FIELD_DELIMITER ,(->string template-name)) (current-namespace))))
;; regenerate files listed in a pmap file ;; regenerate files listed in a pmap file
(define/contract (regenerate-with-pmap pmap #:force [force #f]) (define/contract (regenerate-with-pmap pmap #:force [force #f])

@ -0,0 +1 @@
◊(put-as-html main)

@ -0,0 +1 @@
put-as-htmlroot-functionptopicclasssmallBibliographyplcThis is not, by any measure, a comprehensive bibliography. Rather, its a selection of favorites from my own bookshelf that I consult most frequently in my work as a writer and a typographer.psubheadwritingpBryan A. Garner, bookGarners Modern American Usage, 3rd ed. (New York: Oxford University Press, 2009).pindentedstyleposition:relative;top:-0.4emLong before he agreed to write the foreword for my book bookTypography for Lawyers, Bryan Garner was a hero of mine. Garner thinks and writes about American English in a way thats rigorous, convincing, and accessible. He is stern but not shrill; authoritative but not authoritarian. He is a vigorous advocate for clear, simple writing. His work should be mandatory reading for all writers.psubheadTypographypMatthew Butterick, bookTypography for Lawyers (Houston: Jones McClure Publishing, 2010).pindentedstyleposition:relative;top:-0.4emThe precursor to bookButtericks Practical Typography. Lawyer or not, consider buying a copy, because its a virtuous act. See xrefhow to pay for this book.pJan Middendorp, bookShaping Text (Amsterdam: BIS Publishers, 2012).pindentedstyleposition:relative;top:-0.4emIf you get a second book on typography, get this one. Middendorps beautifully written and illustrated book is full of careful details and lucid explanations.pCarolina de Bartolo, bookExplorations in Typography (slinkexplorationsintypography.comhttp://explorationsintypography.com, 2011).pindentedstyleposition:relative;top:-0.4emUsing a Spiekermann essay from bookStop Stealing Sheep (see below), de Bartolo shows how different typesetting choices change the effect of the text.pCyrus Highsmith, bookInside Paragraphs (Boston: Font Bureau, 2012).pindentedstyleposition:relative;top:-0.4emHighsmiths charmingly hand-illustrated book focuses on the paragraph as a unit of typographic interest.pRobert Bringhurst, bookThe Elements of Typographic Style, 3rd ed. (Vancouver: Hartley and Marks Publishers, 2004).pindentedstyleposition:relative;top:-0.4emBringhursts book has become something of a standard reference guide among professional typographers, bringing together the history, theory, and practice of typography.pEllen Lupton, bookThinking With Type, 2nd ed. (New York: Princeton Architectural Press, 2010).pindentedstyleposition:relative;top:-0.4emIntended as an introduction to typography for design students, Luptons book is more accessible than Bringhursts. It includes full-color illustrations from every era of typography.psubheadFontspErik Spiekermann and E. M. Ginger, bookStop Stealing Sheep & Find Out How Type Works, 2nd ed. (Berkeley, California: Adobe Press, 2002).pindentedstyleposition:relative;top:-0.4emGinger & Spiekermann, a self-described typomaniac (and author of the xrefforeword) explain how fonts work, and how they differ in appearance and in function. My font xrefHermes is among those featured.pStephen Coles, bookThe Anatomy of Type (London: Quid Publishing Ltd., 2012).pindentedstyleposition:relative;top:-0.4emExplores the major categories of fonts and their characteristic qualities by examining 100 fonts in detail.psubheadDesign principlespEdward Tufte, bookEnvisioning Information, 4th printing ed. (Cheshire, Connecticut: Graphics Press, 1990).pEdward Tufte, bookThe Visual Display of Quantitative Information, 2nd ed. (Cheshire, Connecticut: Graphics Press, 2001).pindentedstyleposition:relative;top:-0.4emThese are two of my favorite books of all time. Tufte makes an eloquent and compelling case for why design matters. Both books are fantastically interesting and beautifully illustrated with examples of information design from many historical periods.pWilliam Lidwell, Kritina Holden, and Jill Butler, bookUniversal Principles of Design, 2nd ed. (Beverly, Massachusetts: Rockport Publishers, 2010).pindentedstyleposition:relative;top:-0.4emAn excellent and accessible introduction to design principles that apply not only to printed documents, but to all objects that we interact with.
Loading…
Cancel
Save