From 783b2fb9fe6440536eec2b9fce5f3a0e79cefe86 Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Tue, 20 Aug 2013 19:29:42 -0700 Subject: [PATCH] brought pollen/lang/pre up to date + regeneration of preprocessor files --- command.rkt | 3 +- main-pre.rkt | 15 +++-- pollen-file-tools.rkt | 14 +++- readability.rkt | 2 +- regenerate.rkt | 146 ++++++++++++++++++++++++++---------------- tests/test.txt | 1 + tests/test.txt.pp | 5 ++ 7 files changed, 119 insertions(+), 67 deletions(-) create mode 100644 tests/test.txt create mode 100644 tests/test.txt.pp diff --git a/command.rkt b/command.rkt index 196ba3f..8955333 100644 --- a/command.rkt +++ b/command.rkt @@ -9,9 +9,10 @@ (case arg [("serve") `(require "server.rkt")] [("regenerate") `(begin + ;; todo: take extensions off the comand line (displayln "Regenerate preproc & pmap files ...") (require "regenerate.rkt" "pollen-file-tools.rkt") - (map regenerate (append-map project-files-with-ext (list POLLEN_PREPROC_EXT POLLEN_MAP_EXT))))] + (map force-regenerate (append-map project-files-with-ext (list POLLEN_PREPROC_EXT POLLEN_MAP_EXT))))] [("clone") (let ([target-path (if (> (len args) 1) (->path (get args 1)) diff --git a/main-pre.rkt b/main-pre.rkt index 67141c5..3a7a15c 100644 --- a/main-pre.rkt +++ b/main-pre.rkt @@ -1,5 +1,8 @@ #lang racket/base -(require (only-in (planet mb/pollen/tools) as-list trim-whitespace)) + +(require (only-in (planet mb/pollen/readability) ->list) + (only-in (planet mb/pollen/decode) trim) + (only-in (planet mb/pollen/predicates) whitespace?)) (provide (except-out (all-from-out racket/base) #%module-begin) (rename-out [module-begin #%module-begin])) @@ -16,17 +19,15 @@ ; doclang2_raw is a clone of scribble/doclang2 with decode disabled ; helpful because it collects & exports content via 'doc - (module pollen-inner (planet mb/pollen/doclang2_raw) - (require (planet mb/pollen/tools) - web-server/templates ; for subtemplating - (planet mb/pollen/main-helper)) ; for split-metas and get-here - (require-and-provide-extras) ; brings in the project require files + (module pollen-inner (planet mb/pollen/lang/doclang2_raw) + (require (planet mb/pollen/tools) (planet mb/pollen/main-helper)) + (require-extras #:provide #t) ; brings in the project require files expr ...) ; body of module (require 'pollen-inner) ; provides 'doc - (define text (trim-whitespace (as-list doc))) ; if single line, text will be a string + (define text (trim (->list doc) whitespace?)) ; if single line, text will be a string (provide text (all-from-out 'pollen-inner)) diff --git a/pollen-file-tools.rkt b/pollen-file-tools.rkt index 5218a41..88f0e8d 100644 --- a/pollen-file-tools.rkt +++ b/pollen-file-tools.rkt @@ -96,7 +96,7 @@ (define/contract (has-preproc-source? x) (any/c . -> . boolean?) - (file-exists? (make-preproc-in-path (->path x)))) + (file-exists? (make-preproc-source-path (->path x)))) (define/contract (has-pollen-source? x) (any/c . -> . boolean?) @@ -121,13 +121,21 @@ (any/c . -> . boolean?) (has-ext? (->path x) POLLEN_SOURCE_EXT)) +;; this is for regenerate module. +;; when we want to be friendly with inputs for functions that require a path. +;; Strings & symbols often result from xexpr parsing +;; and are trivially converted to paths. +;; so let's say close enough. +(define/contract (pathish? x) + (any/c . -> . boolean?) + (->boolean (or path? string? symbol?))) -(define/contract (make-preproc-in-path path) +(define/contract (make-preproc-source-path path) (path? . -> . path?) (add-ext path POLLEN_PREPROC_EXT)) -(define/contract (make-preproc-out-path path) +(define/contract (make-preproc-output-path path) (path? . -> . path?) (remove-ext path)) diff --git a/readability.rkt b/readability.rkt index 6a0ffc5..c3146db 100644 --- a/readability.rkt +++ b/readability.rkt @@ -39,7 +39,7 @@ ;; general way of coercing to path (define (->path thing) - ; todo: on bad input, it will pop a string error rather than symbol error + ; todo: on bad input, it will pop a string error rather than path error (string->path (->string thing))) (define (->complete-path thing) diff --git a/regenerate.rkt b/regenerate.rkt index 287b0fb..82580b5 100644 --- a/regenerate.rkt +++ b/regenerate.rkt @@ -5,6 +5,8 @@ (module+ test (require rackunit)) +(provide regenerate force-regenerate) + ;; mod-dates is a hash that takes lists of paths as keys, ;; and lists of modification times as values. ;; Reason: a templated page is a combination of two source files. @@ -30,8 +32,9 @@ ;; put list of paths into mod-dates ;; want to take list as input (rather than individual path) ;; because hash key needs to be a list -(define/contract (store-refresh-in-mod-dates paths) - ((listof path?) . -> . void?) +(define/contract (store-refresh-in-mod-dates path-or-paths) + ((or/c path? (listof path?)) . -> . void?) + (define paths (->list path-or-paths)) (hash-set! mod-dates paths (map path->mod-date-value paths))) (module+ test @@ -54,10 +57,11 @@ (check-true (= (len mod-dates) 0))) ;; how to know whether a certain combination of paths needs a refresh -(define/contract (source-needs-refresh? paths) - ((listof path?) . -> . boolean?) - (or (not (paths . in? . mod-dates)) ; no stored mod date - (not (equal? (map path->mod-date-value paths) (get mod-dates paths))))) ; data has changed +(define/contract (source-needs-refresh? path-or-paths) + ((or/c path? (listof path?)) . -> . boolean?) + (let ([paths (->list path-or-paths)]) + (or (not (paths . in? . mod-dates)) ; no stored mod date + (not (equal? (map path->mod-date-value paths) (get mod-dates paths)))))) ; data has changed (module+ test (reset-mod-dates) @@ -68,62 +72,87 @@ (check-true (source-needs-refresh? (list path))))) -; helper functions for regenerate functions -;(define pollen-file-root (current-directory)) - -; complete pollen path = -;(build-path pollen-file-root f) - - -;; regenerate files listed in a pmap file -(define/contract (regenerate-with-pmap pmap) - (pmap? . -> . void?) - (for-each regenerate (all-pages pmap))) - -;; todo: write test +;; convenience function for external modules to use +(define/contract (force-regenerate x) + (pathish? . -> . void?) + (regenerate x #:force #t)) - -;; dispatches path to the right place -(define/contract (regenerate path #:force [force #f]) - (path? . -> . void?) - (regenerating-message path) - (let ([path (->complete-path path)]) +;; dispatches path to the right regeneration function +;; use #:force to refresh regardless of cached state +(define/contract (regenerate x #:force [force #f]) + ((pathish?) (#:force boolean?) . ->* . void?) + (let ([path (->complete-path (->path x))]) (cond + ;; this will catch pp (preprocessor) files + [(needs-preproc? path) (regenerate-with-preproc path #:force force)] + ;; this will catch p files, + ;; and files without extension that correspond to p files + [(needs-template? path) (regenerate-with-template path #:force force)] + ;; this will catch pmap (pollen map) files [(pmap-source? path) (let ([pmap (dynamic-require path 'main)]) - (regenerate-with-pmap pmap))] - [(needs-preproc? path) (do-preproc path #:force force)] - [(needs-template? path) (do-template path #:force force)]))) - -;; todo: write test + (regenerate-with-pmap pmap #:force force))] + [else (error "Regenerate: no handler for" (->string (file-name-from-path path)))]))) +;; todo: write tests +;; todo: write contract & tests (define (regenerating-message path) + ;; you can actually stuff whatever string you want into path — + ;; if it's not really a path, file-name-from-path won't choke (message "Regenerating:" (->string (file-name-from-path path)))) -(define (do-preproc path #:force [force #f]) - ; set up preproc-in-path & preproc-out-path values - (let-values - ([(preproc-in-path preproc-out-path) - (if (preproc-source? path) - (values path (make-preproc-out-path path)) - (values (make-preproc-in-path path) path))]) - - (when (and (file-exists? preproc-in-path) - (or force - (not (file-exists? preproc-out-path)) - (source-needs-refresh? preproc-in-path))) - (store-refresh-in-mod-dates preproc-in-path) - ; use single quotes to escape spaces in pathnames - (define command - (format "~a '~a' > '~a'" RACKET_PATH preproc-in-path preproc-out-path)) - ; discard output using open-output-nowhere - (parameterize ([current-output-port (open-output-nowhere)]) - (system command)) - (regenerate-message preproc-out-path)))) - - -(define (do-template path [template-name empty] #:force [force #f]) +;; todo: write contract & tests +(define (regenerated-message path) + (message "Regenerated:" (->string (file-name-from-path path)))) + + +(define/contract (regenerate-with-preproc x #:force [force #f]) + ((pathish?) (#:force boolean?) . ->* . void?) + (define path (->path x)) + ;; path might be either a preproc-source path or preproc-output path + ;; figure out which, then compute the other + (define-values (source-path output-path) (if (preproc-source? path) + (values path (make-preproc-output-path path)) + (values (make-preproc-source-path path) path))) + + ;; Computing the source-path doesn't validate whether it exists. + ;; Which is important, of course. + (if (file-exists? source-path) + ;; Three conditions under which we refresh: + (if (or + ;; 1) explicitly forced refresh + force + ;; 2) output file doesn't exist (so it definitely won't appear in mod-dates) + ;; also, this is convenient for development: + ;; you can trigger a refresh just by deleting the file + (not (file-exists? output-path)) + ;; 3) file otherwise needs refresh (e.g., it changed) + (source-needs-refresh? source-path)) + ;; use single quotes to escape spaces in pathnames + (let ([command (format "~a '~a' > '~a'" RACKET_PATH source-path output-path)]) + (regenerating-message (format "~a from ~a" + (file-name-from-path output-path) + (file-name-from-path source-path))) + (store-refresh-in-mod-dates source-path) + ;; discard output using open-output-nowhere + (parameterize ([current-output-port (open-output-nowhere)]) + (system command)) + (regenerated-message output-path)) + ;; otherwise, skip file because there's no trigger for refresh + (message "File is up to date:" (->string (file-name-from-path output-path)))) + ;; source-path doesn't exist + (message "Preprocessor file not found:" (->string (file-name-from-path source-path))))) + +;; todo: write tests + + + +;;;;;;;;;;;;;;; +;; todo next +;;;;;;;;;;;;;;; + +(define (regenerate-with-template path [template-name empty] #:force [force #f]) ; take full path or filename ; return full path of templated file @@ -201,7 +230,14 @@ (display-to-file #:exists 'replace page-result generated-path) - (regenerate-message generated-path))) + (regenerated-message generated-path))) -(provide regenerate regenerate-all-files) \ No newline at end of file + +;; regenerate files listed in a pmap file +(define/contract (regenerate-with-pmap pmap #:force [force #f]) + (pmap? . -> . void?) + ;; pass force parameter through + (for-each (λ(i) (regenerate i #:force force)) (all-pages pmap))) + +;; todo: write test diff --git a/tests/test.txt b/tests/test.txt new file mode 100644 index 0000000..8bd19c6 --- /dev/null +++ b/tests/test.txt @@ -0,0 +1 @@ +This will print bar: bar \ No newline at end of file diff --git a/tests/test.txt.pp b/tests/test.txt.pp new file mode 100644 index 0000000..6606cdb --- /dev/null +++ b/tests/test.txt.pp @@ -0,0 +1,5 @@ +#lang planet mb/pollen/pre + +◊(define foo "bar") + +This will print bar: ◊foo \ No newline at end of file