Skip to content

Commit e49b4c9

Browse files
committed
Improve various aspects of the Scribble layer
* In the renderer, use an `attributes` abstraction that allows merging certain properties more intelligently (currently just `class`). * Add support for rendering scribble tables. * Package up the blog post language as a proper #lang. ...plus a few other miscellaneous minor improvements.
1 parent 031b05e commit e49b4c9

File tree

8 files changed

+352
-63
lines changed

8 files changed

+352
-63
lines changed

blog/build.rkt

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
#lang racket/base
22

3-
(require racket/class
3+
(require (for-syntax racket/base)
4+
racket/class
45
racket/date
56
racket/file
67
racket/format
8+
racket/lazy-require
79
racket/list
810
racket/match
911
racket/path
1012
racket/promise
13+
racket/runtime-path
1114
racket/sequence
1215
racket/serialize
1316
scribble/core
1417
scribble/xref
1518
setup/xref
19+
syntax/parse/define
1620
threading
1721
(only-in xml write-xexpr)
1822

@@ -55,10 +59,25 @@
5559

5660
(define (markdown-post file-name)
5761
(define path (build-path posts-dir file-name))
58-
(define main-part-promise (delay (~> (call-with-input-file* path parse-markdown-post)
59-
ensure-top-tag
60-
(set-blog-tag-prefix file-name))))
61-
(post-dep path main-part-promise))
62+
(post-dep path
63+
(delay (~> (call-with-input-file* path parse-markdown-post)
64+
ensure-top-tag
65+
(set-blog-tag-prefix file-name)))))
66+
67+
(define (make-scribble-post-dep file-name)
68+
(define path (build-path posts-dir file-name))
69+
(post-dep path
70+
(delay (~> (dynamic-require path 'doc)
71+
ensure-top-tag
72+
(set-blog-tag-prefix file-name)))))
73+
74+
(define-syntax-parser scribble-post
75+
[(_ mod-path:string)
76+
; add compile-time dependency
77+
(syntax-local-lift-require
78+
#`(for-label (only #,(string-append "posts/" (syntax-e #'mod-path))))
79+
#'#f)
80+
#'(make-scribble-post-dep 'mod-path)])
6281

6382
(define all-post-deps
6483
(map markdown-post '("2015-07-18-automatically-deploying-a-frog-powered-blog-to-github-pages.md"

blog/build/render/scribble.rkt

Lines changed: 195 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
racket/match
1010
racket/path
1111
racket/serialize
12+
racket/string
1213

1314
scribble/base-render
1415
scribble/core
15-
scribble/html-properties
16+
(except-in scribble/html-properties attributes attributes-assoc)
17+
(prefix-in scribble: scribble/html-properties)
1618
(prefix-in scribble: scribble/html-render)
1719
(only-in scribble/private/render-utils part-style?)
1820

@@ -23,8 +25,8 @@
2325
threading
2426
(only-in xml write-xexpr xexpr/c)
2527

28+
"../../lang/base.rkt"
2629
"../../lang/metadata.rkt"
27-
"../../lang/post-language.rkt"
2830
"../../paths.rkt"
2931
"../metadata.rkt"
3032
"highlight/pygments.rkt"
@@ -42,39 +44,89 @@
4244

4345
;; -----------------------------------------------------------------------------
4446

45-
(define (attributes-union attrs1 attrs2)
46-
(hash-union attrs1 attrs2 #:combine/key
47-
(λ (k a b)
48-
(match k
49-
['class (string-append a " " b)]
50-
[_ (error 'merge-attributes
51-
(string-append "duplicate values for attribute\n"
52-
" attribute: ~e\n"
53-
" values:\n"
54-
" ~e\n"
55-
" ~e")
56-
k a b)]))))
47+
(struct attributes (assoc styles) #:transparent
48+
#:name make-attributes
49+
#:constructor-name make-attributes)
50+
51+
(define empty-attributes (make-attributes (hasheq) (hasheq)))
52+
53+
(define (attributes-empty? attrs)
54+
(and (hash-empty? (attributes-assoc attrs))
55+
(hash-empty? (attributes-styles attrs))))
56+
57+
(define (attributes #:styles [styles (hasheq)] . pairs)
58+
(if (and (empty? pairs) (hash-empty? styles))
59+
empty-attributes
60+
(make-attributes (apply hasheq pairs) styles)))
61+
62+
(define (list->attributes [pairs '()] #:styles [styles '()])
63+
(if (and (empty? pairs) (empty? styles))
64+
empty-attributes
65+
(make-attributes (make-immutable-hasheq pairs) (make-immutable-hasheq styles))))
66+
67+
(define (attributes-union #:allow-override? [allow-override? #f] attrs . attrss)
68+
(define (multiple-values-error what k a b)
69+
(error 'attributes-union
70+
(~a "multiple values for " what "\n"
71+
" " what ": ~e\n"
72+
" values:\n"
73+
" ~e\n"
74+
" ~e")
75+
k a b))
76+
77+
(for/fold ([attrs attrs])
78+
([new-attrs (in-list attrss)])
79+
(make-attributes
80+
(hash-union (attributes-assoc attrs)
81+
(attributes-assoc new-attrs)
82+
#:combine/key
83+
(λ (k a b)
84+
(match k
85+
['class (string-append a " " b)]
86+
[_ (if allow-override?
87+
b
88+
(multiple-values-error "attribute" k a b))])))
89+
(hash-union (attributes-styles attrs)
90+
(attributes-styles new-attrs)
91+
#:combine/key
92+
(λ (k a b) (multiple-values-error "CSS property" k a b))))))
5793

5894
(define (attributes->list attrs)
59-
(for/list ([(k v) (in-hash attrs)])
95+
(define all-attrs
96+
(cond
97+
[(hash-empty? (attributes-styles attrs))
98+
(attributes-assoc attrs)]
99+
[(hash-ref (attributes-assoc attrs) 'style #f)
100+
=> (λ (existing-styles)
101+
(raise-arguments-error 'render (~a "conflicting value for ‘style’ attribute;\n"
102+
" values were given via both ‘attributes’ and ‘css-styles’ properties")
103+
"attribute value" existing-styles
104+
"css-styles properties" (attributes-styles attrs)))]
105+
[else
106+
(define style-strs (for/list ([(k v) (in-hash (attributes-styles attrs))])
107+
(~a k ":" v)))
108+
(hash-set (attributes-assoc attrs) 'style (string-join style-strs ";"))]))
109+
(for/list ([(k v) (in-immutable-hash all-attrs)])
60110
(list k v)))
61111

62112
(define (style->tag-name+attributes s ri)
63113
(for/fold ([tag-name #f]
64114
[attrs (if (string? (style-name s))
65-
(hasheq 'class (style-name s))
66-
(hasheq))])
115+
(attributes 'class (style-name s))
116+
(attributes))])
67117
([prop (in-list (style-properties s))])
68118
(match prop
69119
['div
70120
(values 'div attrs)]
71121
[(alt-tag name)
72122
(values (string->symbol name) attrs)]
73-
[(attributes attrs*)
74-
(values tag-name (attributes-union attrs (make-immutable-hasheq attrs*)))]
123+
[(scribble:attributes assoc)
124+
(values tag-name (attributes-union attrs (list->attributes assoc)))]
125+
[(css-styles assoc)
126+
(values tag-name (attributes-union attrs (list->attributes #:styles assoc)))]
75127
[(link-target tag)
76128
(values tag-name (attributes-union attrs
77-
(hasheq 'id (tag->anchor-name (resolve-tag tag ri)))))]
129+
(attributes 'id (tag->anchor-name (resolve-tag tag ri)))))]
78130
[_
79131
(values tag-name attrs)])))
80132

@@ -267,6 +319,7 @@
267319

268320
(inherit get-dest-directory
269321

322+
render-block
270323
render-flow
271324
render-part
272325

@@ -318,6 +371,114 @@
318371
,@(for/list ([blocks (in-list (itemization-blockss e))])
319372
`(li ,@(render-flow blocks part ri #t))))])
320373

374+
(define/override (render-table e part ri starting-item?)
375+
(define cellss (table-blockss e))
376+
(define num-columns
377+
(cond
378+
[(empty? cellss) 0]
379+
[else
380+
(define num-columns (length (first cellss)))
381+
(for ([cells (in-list (rest cellss))])
382+
(unless (= (length cells) num-columns)
383+
(raise-arguments-error 'render-table "table rows have inconsistent lengths"
384+
"first row length" num-columns
385+
"other row length" (length cells)
386+
"first row..." (first cellss)
387+
"other row..." cells)))
388+
num-columns]))
389+
390+
(define t-style (table-style e))
391+
(define col-styles (and~> (findf table-columns? (style-properties t-style))
392+
table-columns-styles))
393+
(define row-styles (and~> (findf table-rows? (style-properties t-style))
394+
table-rows-styles))
395+
(define cell-styless (and~> (findf table-cells? (style-properties t-style))
396+
table-cells-styless))
397+
398+
(define (check-count what in-what in-v given expected)
399+
(unless (= given expected)
400+
(raise-argument-error 'render-table (~a (if (< given expected) "not enough" "too many")
401+
" " what " styles for " in-what)
402+
(~a what " count") expected
403+
"style count" given
404+
(~a in-what "...") in-v)))
405+
(when col-styles
406+
(check-count "column" "table" e (length col-styles) num-columns))
407+
(when row-styles
408+
(check-count "row" "table" e (length row-styles) (length cellss)))
409+
(when cell-styless
410+
(check-count "row" "table" e (length cell-styless) (length cellss))
411+
(for ([cells (in-list cellss)]
412+
[cell-styles (in-list cell-styless)])
413+
(check-count "cell" "row" cells (length cell-styles) num-columns)))
414+
415+
(define-values [tag-name attrs] (style->tag-name+attributes t-style ri))
416+
`[(,(or tag-name 'table)
417+
,(attributes->list attrs)
418+
,@(for/list ([cells (in-list cellss)]
419+
[row-style (if row-styles
420+
(in-list row-styles)
421+
(in-cycle (in-value plain)))]
422+
[cell-styles (if cell-styless
423+
(in-list cell-styless)
424+
(in-cycle (in-value #f)))])
425+
(define-values [tag-name attrs] (style->tag-name+attributes row-style ri))
426+
(list* (or tag-name 'tr)
427+
(attributes->list attrs)
428+
(let loop ([cells cells]
429+
[col-styles col-styles]
430+
[cell-styles cell-styles])
431+
(match cells
432+
['() '()]
433+
[(list* cell (and 'cont cont) ... cells)
434+
(define colspan (add1 (length cont)))
435+
436+
(define (split-styles ss)
437+
(if ss
438+
(values (first ss) (drop ss colspan))
439+
(values plain #f)))
440+
(define-values [col-style col-styles*] (split-styles col-styles))
441+
(define-values [cell-style cell-styles*] (split-styles cell-styles))
442+
443+
(define-values [col-tag-name col-attrs] (style->tag-name+attributes col-style ri))
444+
(define-values [cell-tag-name cell-attrs] (style->tag-name+attributes cell-style ri))
445+
(define attrs (~> (attributes-union col-attrs cell-attrs #:allow-override? #t)
446+
(attributes-union (if (= colspan 1)
447+
(attributes)
448+
(attributes 'colspan (~a colspan)))
449+
(table-cell-style->attributes col-style cell-style))))
450+
(cons (list* (or cell-tag-name col-tag-name 'td)
451+
(attributes->list attrs)
452+
(render-table-cell cell part ri))
453+
(loop cells col-styles* cell-styles*))])))))])
454+
455+
(define/private (table-cell-style->attributes col-s cell-s)
456+
(define col-props (style-properties col-s))
457+
(define cell-props (style-properties cell-s))
458+
459+
(define (alignment what which)
460+
(define (go for-what props)
461+
(define aligns (filter (λ~> (memq which)) props))
462+
(match aligns
463+
['() #f]
464+
[(list align) (~a "cell-align-" align)]
465+
[_ (raise-arguments-error 'render-table (~a for-what " has multiple " what " alignments")
466+
"alignment properties" aligns)]))
467+
(or (go "cell" cell-props)
468+
(go "column" col-props)))
469+
470+
(define h-align (alignment "horizontal" '(left right center)))
471+
(define v-align (alignment "vertical" '(top baseline bottom vcenter)))
472+
(attributes 'class (string-join (filter values (list h-align v-align)))))
473+
474+
(define/public (render-table-cell e part ri)
475+
(cond
476+
[(and (paragraph? e)
477+
(memq 'omitable (style-properties (paragraph-style e))))
478+
(render-content (paragraph-content e) part ri)]
479+
[else
480+
(render-block e part ri #f)]))
481+
321482
(define/public (render-title-link tag part ri)
322483
(define title-content
323484
(match (resolve-get part ri tag)
@@ -338,23 +499,23 @@
338499
; First, we determine what element wrappers are needed by the style.
339500

340501
; alt-tag needs a custom wrapper
341-
(define alt-tag-wrap (if tag-name (cons tag-name (hasheq)) #f))
502+
(define alt-tag-wrap (if tag-name (cons tag-name (attributes)) #f))
342503

343504
; target-url needs an 'a wrapper
344505
(define link-wrap
345506
(match (findf target-url? (style-properties style))
346507
[(target-url target)
347-
(cons 'a (hasheq 'href target))]
508+
(cons 'a (attributes 'href target))]
348509
[#f #f]))
349510

350511
; certain symbolic styles need wrappers
351512
(define style-name-wrap
352513
(match (style-name style)
353-
['bold (cons 'strong (hasheq))]
354-
[(or 'emph 'italic) (cons 'em (hasheq))]
355-
['tt (cons 'code (hasheq))]
356-
['superscript (cons 'sup (hasheq))]
357-
['subscript (cons 'sub (hasheq))]
514+
['bold (cons 'strong (attributes))]
515+
[(or 'emph 'italic) (cons 'em (attributes))]
516+
['tt (cons 'code (attributes))]
517+
['superscript (cons 'sup (attributes))]
518+
['subscript (cons 'sub (attributes))]
358519
[_ #f]))
359520

360521
; Now we combine the wrappers and mix in extra attributes.
@@ -363,7 +524,7 @@
363524
; If there are no wrappers, but we need to add attributes, add a
364525
; 'span wrapper to hold them.
365526
['()
366-
(if (hash-empty? attrs)
527+
(if (attributes-empty? attrs)
367528
'()
368529
(list (cons 'span attrs)))]
369530
; Otherwise, add the attributes to the outermost wrapper.
@@ -381,9 +542,9 @@
381542
(super render-content elem part ri)))
382543
(match elem
383544
[(target-element _ _ tag)
384-
(define anchor-name (tag->anchor-name (resolve-tag tag ri)))
385-
(define attrs* (attributes-union (hasheq 'name anchor-name) attrs))
386-
(wrap-for-style #:attrs (hasheq) `[(a ,(attributes->list attrs*) ,@rendered)])]
545+
(tag->anchor-name (resolve-tag tag ri))
546+
(cons `(a ([name ,(tag->anchor-name (resolve-tag tag ri))]))
547+
(wrap-for-style rendered))]
387548

388549
[(link-element _ _ tag)
389550
(define indirect? (memq 'indirect-link (style-properties style)))
@@ -405,8 +566,8 @@
405566
(string-append "#" (uri-encode anchor))
406567
(site-path->url-string path #:fragment anchor))]))
407568

408-
(define attrs* (attributes-union (hasheq 'href href) attrs))
409-
(wrap-for-style #:attrs (hasheq) `[(a ,(attributes->list attrs*) ,@rendered)])]
569+
(define attrs* (attributes-union (attributes 'href href) attrs))
570+
(wrap-for-style #:attrs (attributes) `[(a ,(attributes->list attrs*) ,@rendered)])]
410571

411572
[_ (wrap-for-style rendered)])]))
412573

@@ -450,7 +611,7 @@
450611
#:body (render-part part ri)))
451612
(write-xexpr xexpr)
452613
xexpr)
453-
614+
454615
(super-new)))
455616

456617
;; The renderer used for actual blog posts, with all the bells and whistles.

0 commit comments

Comments
 (0)