|
|
|
@ -1,11 +1,11 @@
|
|
|
|
|
#lang racket/base
|
|
|
|
|
(require racket/list racket/path racket/port racket/system
|
|
|
|
|
racket/file racket/rerequire racket/contract racket/bool)
|
|
|
|
|
(require "world.rkt" "tools.rkt" "ptree.rkt" "readability.rkt" "template.rkt")
|
|
|
|
|
(require "world.rkt" "tools.rkt" "readability.rkt" "template.rkt")
|
|
|
|
|
|
|
|
|
|
(module+ test (require rackunit))
|
|
|
|
|
|
|
|
|
|
(provide regenerate regenerate-with-session)
|
|
|
|
|
(provide render render-with-session)
|
|
|
|
|
|
|
|
|
|
;; mod-dates is a hash that takes lists of paths as keys,
|
|
|
|
|
;; and lists of modification times as values.
|
|
|
|
@ -31,7 +31,7 @@
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-false (path->mod-date-value (->path "foobarfoo.rkt")))
|
|
|
|
|
(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 "render.rkt"))))))
|
|
|
|
|
|
|
|
|
|
;; put list of paths into mod-dates
|
|
|
|
|
;; need list as input (rather than individual path)
|
|
|
|
@ -53,20 +53,20 @@
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(reset-mod-dates)
|
|
|
|
|
(store-refresh-in-mod-dates (build-path (current-directory) (->path "regenerate.rkt")))
|
|
|
|
|
(store-refresh-in-mod-dates (build-path (current-directory) (->path "render.rkt")))
|
|
|
|
|
(check-true (= (len mod-dates) 1))
|
|
|
|
|
(reset-mod-dates))
|
|
|
|
|
|
|
|
|
|
;; when you want to generate everything fresh,
|
|
|
|
|
;; but without having to #:force everything.
|
|
|
|
|
;; Regenerate functions will always go when no mod-date is found.
|
|
|
|
|
;; render functions will always go when no mod-date is found.
|
|
|
|
|
(define/contract (reset-mod-dates)
|
|
|
|
|
(-> void?)
|
|
|
|
|
(set! mod-dates (make-hash)))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(reset-mod-dates)
|
|
|
|
|
(store-refresh-in-mod-dates (build-path (current-directory) (->path "regenerate.rkt")))
|
|
|
|
|
(store-refresh-in-mod-dates (build-path (current-directory) (->path "render.rkt")))
|
|
|
|
|
(reset-mod-dates)
|
|
|
|
|
(check-true (= (len mod-dates) 0)))
|
|
|
|
|
|
|
|
|
@ -80,7 +80,7 @@
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(reset-mod-dates)
|
|
|
|
|
(let ([path (build-path (current-directory) (->path "regenerate.rkt"))])
|
|
|
|
|
(let ([path (build-path (current-directory) (->path "render.rkt"))])
|
|
|
|
|
(store-refresh-in-mod-dates path)
|
|
|
|
|
(check-false (mod-date-expired? path))
|
|
|
|
|
(reset-mod-dates)
|
|
|
|
@ -88,80 +88,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; convenience function for external modules to use
|
|
|
|
|
(define/contract (regenerate-with-session . xs)
|
|
|
|
|
(define/contract (render-with-session . xs)
|
|
|
|
|
(() #:rest (listof pathish?) . ->* . void?)
|
|
|
|
|
;; This will trigger regeneration of all files.
|
|
|
|
|
;; Why not pass #:force #t through with regenerate?
|
|
|
|
|
;; This will trigger rendering of all files.
|
|
|
|
|
;; Why not pass #:force #t through with render?
|
|
|
|
|
;; Because certain files will pass through multiple times (e.g., templates)
|
|
|
|
|
;; And with #:force, they would be regenerated repeatedly.
|
|
|
|
|
;; And with #:force, they would be rendered repeatedly.
|
|
|
|
|
;; Using reset-mod-dates is sort of like session control:
|
|
|
|
|
;; setting a state that persists through the whole operation.
|
|
|
|
|
(reset-mod-dates)
|
|
|
|
|
(for-each regenerate xs))
|
|
|
|
|
(for-each render xs))
|
|
|
|
|
|
|
|
|
|
;; dispatches path to the right regeneration function
|
|
|
|
|
;; dispatches path to the right rendering function
|
|
|
|
|
;; use #:force to refresh regardless of cached state
|
|
|
|
|
(define/contract (regenerate #:force [force #f] . xs)
|
|
|
|
|
(define/contract (render #:force [force #f] . xs)
|
|
|
|
|
(() (#:force boolean?) #:rest (listof pathish?) . ->* . void?)
|
|
|
|
|
(define (®enerate x)
|
|
|
|
|
(define (&render x)
|
|
|
|
|
(let ([path (->complete-path (->path x))])
|
|
|
|
|
; (message "Regenerating" (->string path))
|
|
|
|
|
(cond
|
|
|
|
|
;; this will catch pp (preprocessor) files
|
|
|
|
|
[(needs-preproc? path) (regenerate-with-preproc path #:force force)]
|
|
|
|
|
;; this will catch p files,
|
|
|
|
|
;; this will catch preprocessor files
|
|
|
|
|
[(needs-preproc? path) (render-with-preproc path #:force force)]
|
|
|
|
|
;; this will catch pollen source files,
|
|
|
|
|
;; and files without extension that correspond to p files
|
|
|
|
|
[(needs-template? path) (regenerate-with-template path #:force force)]
|
|
|
|
|
[(needs-template? path) (render-with-template path #:force force)]
|
|
|
|
|
;; this will catch ptree files
|
|
|
|
|
[(ptree-source? path) (let ([ptree (dynamic-require path 'main)])
|
|
|
|
|
(regenerate-with-ptree ptree #:force force))]
|
|
|
|
|
(render-ptree-files ptree #:force force))]
|
|
|
|
|
[(equal? FALLBACK_TEMPLATE_NAME (->string (file-name-from-path path)))
|
|
|
|
|
(message "Regenerate: using fallback template")]
|
|
|
|
|
(message "Render: using fallback template")]
|
|
|
|
|
[(file-exists? path) 'pass-through]
|
|
|
|
|
[else (error "Regenerate couldn't find" (->string (file-name-from-path path)))])))
|
|
|
|
|
(for-each ®enerate xs))
|
|
|
|
|
[else (error "Render couldn't find" (->string (file-name-from-path path)))])))
|
|
|
|
|
(for-each &render xs))
|
|
|
|
|
|
|
|
|
|
;; todo: write tests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(define/contract (regenerating-message path)
|
|
|
|
|
(define/contract (rendering-message path)
|
|
|
|
|
(any/c . -> . void?)
|
|
|
|
|
;; 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))))
|
|
|
|
|
(message "Rendering" (->string (file-name-from-path path))))
|
|
|
|
|
|
|
|
|
|
(define/contract (regenerated-message path)
|
|
|
|
|
(define/contract (rendered-message path)
|
|
|
|
|
(any/c . -> . void?)
|
|
|
|
|
(message "Regenerated:" (->string (file-name-from-path path))))
|
|
|
|
|
(message "Rendered" (->string (file-name-from-path path))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(define/contract (complete-preproc-source-path x)
|
|
|
|
|
(pathish? . -> . complete-path?)
|
|
|
|
|
(let ([path (->path x)])
|
|
|
|
|
(->complete-path (if (preproc-source? path)
|
|
|
|
|
path
|
|
|
|
|
(make-preproc-source-path path)))))
|
|
|
|
|
|
|
|
|
|
;; todo: tests
|
|
|
|
|
|
|
|
|
|
(define/contract (complete-preproc-output-path x)
|
|
|
|
|
(pathish? . -> . complete-path?)
|
|
|
|
|
(let ([path (->path x)])
|
|
|
|
|
(->complete-path (if (preproc-source? path)
|
|
|
|
|
(make-preproc-output-path path)
|
|
|
|
|
path))))
|
|
|
|
|
;; todo: tests
|
|
|
|
|
(define/contract (up-to-date-message path)
|
|
|
|
|
(any/c . -> . void?)
|
|
|
|
|
(message "File is up to date:" (->string (file-name-from-path path))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(define/contract (regenerate-with-preproc x #:force [force #f])
|
|
|
|
|
(define/contract (render-with-preproc x #:force [force #f])
|
|
|
|
|
(((and/c pathish?
|
|
|
|
|
(flat-named-contract 'file-exists
|
|
|
|
|
(λ(x) (file-exists? (complete-preproc-source-path x)))))) (#:force boolean?) . ->* . void?)
|
|
|
|
|
(λ(x) (file-exists? (->complete-path (->preproc-source-path x))))))) (#:force boolean?) . ->* . void?)
|
|
|
|
|
|
|
|
|
|
;; x might be either a preproc-source path or preproc-output path
|
|
|
|
|
(define source-path (complete-preproc-source-path x))
|
|
|
|
|
(define source-path (->complete-path (->preproc-source-path x)))
|
|
|
|
|
(define-values (source-dir source-name _) (split-path source-path))
|
|
|
|
|
(define output-path (complete-preproc-output-path x))
|
|
|
|
|
(define output-path (->complete-path (->output-path x)))
|
|
|
|
|
|
|
|
|
|
;; Three conditions under which we refresh:
|
|
|
|
|
(if (or
|
|
|
|
@ -175,7 +160,7 @@
|
|
|
|
|
(mod-date-expired? 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"
|
|
|
|
|
(rendering-message (format "~a from ~a"
|
|
|
|
|
(file-name-from-path output-path)
|
|
|
|
|
(file-name-from-path source-path)))
|
|
|
|
|
(store-refresh-in-mod-dates source-path)
|
|
|
|
@ -183,14 +168,14 @@
|
|
|
|
|
(parameterize ([current-directory source-dir]
|
|
|
|
|
[current-output-port (open-output-nowhere)])
|
|
|
|
|
(system command))
|
|
|
|
|
(regenerated-message output-path))
|
|
|
|
|
(rendered-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)))))
|
|
|
|
|
(up-to-date-message output-path)))
|
|
|
|
|
|
|
|
|
|
;; todo: write tests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; utility function for regenerate-with-template
|
|
|
|
|
;; utility function for render-with-template
|
|
|
|
|
(define/contract (handle-source-rerequire source-path)
|
|
|
|
|
((and/c path? file-exists?) . -> . boolean?)
|
|
|
|
|
|
|
|
|
@ -219,10 +204,10 @@
|
|
|
|
|
(->boolean (> (len (get-output-string port-for-catching-file-info)) 0)))
|
|
|
|
|
|
|
|
|
|
(define (complete-pollen-source-path x)
|
|
|
|
|
(->complete-path (make-pollen-source-path (->path x))))
|
|
|
|
|
(->complete-path (->pollen-source-path (->path x))))
|
|
|
|
|
|
|
|
|
|
;; apply template
|
|
|
|
|
(define/contract (regenerate-with-template x [template-name #f] #:force [force #f])
|
|
|
|
|
(define/contract (render-with-template x [template-name #f] #:force [force #f])
|
|
|
|
|
(((and/c pathish?
|
|
|
|
|
(flat-named-contract 'file-exists
|
|
|
|
|
(λ(x) (file-exists? (complete-pollen-source-path x))))))
|
|
|
|
@ -237,13 +222,13 @@
|
|
|
|
|
(define source-reloaded? (handle-source-rerequire source-path))
|
|
|
|
|
|
|
|
|
|
;; Then the rest:
|
|
|
|
|
;; set the template, regenerate the source file with template, and catch the output.
|
|
|
|
|
;; set the template, render the source file with template, and catch the output.
|
|
|
|
|
;; 1) Set the template.
|
|
|
|
|
(define template-path
|
|
|
|
|
(or
|
|
|
|
|
;; Build the possible paths and use the first one
|
|
|
|
|
;; that either exists, or has a preproc source that exists.
|
|
|
|
|
(ormap (λ(p) (if (ormap file-exists? (list p (make-preproc-source-path p))) p #f))
|
|
|
|
|
(ormap (λ(p) (if (ormap file-exists? (list p (->preproc-source-path p))) p #f))
|
|
|
|
|
(filter-not false?
|
|
|
|
|
(list
|
|
|
|
|
;; path based on template-name
|
|
|
|
@ -253,22 +238,23 @@
|
|
|
|
|
(and (TEMPLATE_META_KEY . in? . source-metas)
|
|
|
|
|
(build-path source-dir
|
|
|
|
|
(get source-metas TEMPLATE_META_KEY))))
|
|
|
|
|
;; path using default template name
|
|
|
|
|
(build-path source-dir DEFAULT_TEMPLATE))))
|
|
|
|
|
;; path using default template name =
|
|
|
|
|
;; "-main" + extension from output path (e.g. foo.xml.p -> -main.xml)
|
|
|
|
|
(build-path source-dir (add-ext DEFAULT_TEMPLATE_PREFIX (get-ext (->output-path source-path)))))))
|
|
|
|
|
;; if none of these work, make temporary template file
|
|
|
|
|
(let ([tp (build-path source-dir FALLBACK_TEMPLATE_NAME)])
|
|
|
|
|
(display-to-file #:exists 'replace fallback-template-data tp)
|
|
|
|
|
tp)))
|
|
|
|
|
|
|
|
|
|
;; refresh template (it might have its own preprocessor file)
|
|
|
|
|
(regenerate template-path #:force force)
|
|
|
|
|
(render template-path #:force force)
|
|
|
|
|
|
|
|
|
|
;; calculate new path for generated file
|
|
|
|
|
(define output-path (make-pollen-output-path source-path))
|
|
|
|
|
(define output-path (->output-path source-path))
|
|
|
|
|
|
|
|
|
|
;; 2) Regenerate the source file with template, if needed.
|
|
|
|
|
;; Regenerate is expensive, so we avoid it when we can.
|
|
|
|
|
;; Four conditions where we regenerate:
|
|
|
|
|
;; 2) render the source file with template, if needed.
|
|
|
|
|
;; render is expensive, so we avoid it when we can.
|
|
|
|
|
;; Four conditions where we render:
|
|
|
|
|
(if (or force ; a) it's explicitly demanded
|
|
|
|
|
(not (file-exists? output-path)) ; b) output file does not exist
|
|
|
|
|
;; c) mod-dates indicates refresh is needed
|
|
|
|
@ -277,12 +263,11 @@
|
|
|
|
|
source-reloaded?)
|
|
|
|
|
(begin
|
|
|
|
|
(store-refresh-in-mod-dates source-path template-path)
|
|
|
|
|
(message "Rendering source" (->string source-path)
|
|
|
|
|
"with template" (->string template-path))
|
|
|
|
|
(message "Rendering source" (->string (file-name-from-path source-path)) "with template" (->string (file-name-from-path template-path)))
|
|
|
|
|
(let ([page-result (render-source-with-template source-path template-path)])
|
|
|
|
|
(display-to-file #:exists 'replace page-result output-path)
|
|
|
|
|
(regenerated-message (file-name-from-path output-path))))
|
|
|
|
|
(message "Regenerate with template: file is up to date:" (->string (file-name-from-path output-path))))
|
|
|
|
|
(rendered-message output-path)))
|
|
|
|
|
(up-to-date-message output-path))
|
|
|
|
|
|
|
|
|
|
;; delete fallback template if needed
|
|
|
|
|
(let ([tp (build-path source-dir FALLBACK_TEMPLATE_NAME)])
|
|
|
|
@ -316,11 +301,13 @@
|
|
|
|
|
(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 ptree file
|
|
|
|
|
(define/contract (regenerate-with-ptree ptree #:force [force #f])
|
|
|
|
|
;; render files listed in a ptree file
|
|
|
|
|
(define/contract (render-ptree-files ptree #:force [force #f])
|
|
|
|
|
((ptree?) (#:force boolean?) . ->* . void?)
|
|
|
|
|
;; pass force parameter through
|
|
|
|
|
(for-each (λ(i) (regenerate i #:force force)) (all-pages ptree)))
|
|
|
|
|
(for-each (λ(i) (render i #:force force))
|
|
|
|
|
;; use dynamic-require to avoid requiring ptree.rkt every time render.rkt is required
|
|
|
|
|
((dynamic-require "ptree.rkt" 'all-pages) ptree)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|