Idiomatic Pollen? #55

Closed
opened 9 years ago by malcolmstill · 3 comments
malcolmstill commented 9 years ago (Migrated from github.com)

I've just started playing around with Pollen and I'm trying to implement a static blog. In posts I want the option of including a table of contents.

I have some code that seems to work but it feels like a hack. Is this idiomatic Pollen?

In directory-require.rkt I have the following code:

(define toc '())

(define (make-toc)
  (set! toc '(nav))
  `(meta ((toc? "true"))))

(define (make-heading level)
  (string->symbol
   (string-append "h" (number->string (+ 1 level)))))

(define (make-nav level text)
  `(div [[class ,(string-append "nav" (number->string (+ 1 level)))]]
        (a [[href ,(string-append "#" text)]] ,text)))

(define (section level text)
  (if (null? toc)
      `(,(make-heading level) [[id ,text]] ,text)
      (begin
        (set! toc (reverse (cons (make-nav level text) (reverse toc))))
        `(,(make-heading level) [[id ,text]] ,text (meta ((toc ,(->html toc))))))))

If I want to include a table of contents I call ◊make-toc{} at the top of the markup file and any call to ◊section[<level>]{<text>} overwrites meta to include all of the appropriate HTML up to that point for the ToC at the top of the page:

#lang pollen

◊headline{This is my title}
◊publish-date{Saturday, 13th June, 2015}
◊make-toc{}

◊section[1]{Introduction}

Blah blah

◊section[2]{Subsection of Introduction}

Blah blah blah

In template.html:

<div id='content'>
  <h1>◊(select 'headline metas)</h1>
  ◊when/block[(select-from-metas 'toc? metas)]{<h2>Table of Contents</h2>◊(select 'toc metas)}
  ◊(->html doc #:splice #t)
</div>

Is there a more idiomatic way of doing this. I initially tried to hold a txepxr containing the ToC in meta but it can only include symbols/strings (hence having to use ->html in section)? Is there another way of passing data between a .pm and a template other than meta?

I've just started playing around with Pollen and I'm trying to implement a static blog. In posts I want the option of including a table of contents. I have some code that seems to work but it feels like a hack. Is this idiomatic Pollen? In `directory-require.rkt` I have the following code: ``` (define toc '()) (define (make-toc) (set! toc '(nav)) `(meta ((toc? "true")))) (define (make-heading level) (string->symbol (string-append "h" (number->string (+ 1 level))))) (define (make-nav level text) `(div [[class ,(string-append "nav" (number->string (+ 1 level)))]] (a [[href ,(string-append "#" text)]] ,text))) (define (section level text) (if (null? toc) `(,(make-heading level) [[id ,text]] ,text) (begin (set! toc (reverse (cons (make-nav level text) (reverse toc)))) `(,(make-heading level) [[id ,text]] ,text (meta ((toc ,(->html toc)))))))) ``` If I want to include a table of contents I call `◊make-toc{}` at the top of the markup file and any call to `◊section[<level>]{<text>}` overwrites meta to include all of the appropriate HTML up to that point for the ToC at the top of the page: ``` #lang pollen ◊headline{This is my title} ◊publish-date{Saturday, 13th June, 2015} ◊make-toc{} ◊section[1]{Introduction} Blah blah ◊section[2]{Subsection of Introduction} Blah blah blah ``` In `template.html`: ``` <div id='content'> <h1>◊(select 'headline metas)</h1> ◊when/block[(select-from-metas 'toc? metas)]{<h2>Table of Contents</h2>◊(select 'toc metas)} ◊(->html doc #:splice #t) </div> ``` Is there a more idiomatic way of doing this. I initially tried to hold a txepxr containing the ToC in meta but it can only include symbols/strings (hence having to use `->html` in `section`)? Is there another way of passing data between a `.pm` and a template other than meta?
mbutterick commented 9 years ago (Migrated from github.com)

I wouldn’t hold myself out as the arbiter between “hack” and “idiom.” Everyone should write programs however they want ;)

Is there another way of passing data between a .pm and a template other than meta?

Yes. You can extract anything from the doc part of a .pm file using select-from-doc. Thus, when setting up a template, you don’t need to use doc as a single indivisible thing. You can instead use it as a container for other things.

Along those lines, here’s how I’d approach this problem.

The source file needs to do three things: a) provide the content of the document, b) provide a summary of the document's headings for an optional TOC, and c) toggle the TOC with a meta setting. So I would change doc to have two parts: a body tag that contains the main content, and a toc-entries tag that has the TOC entries.

The source file will look the same:

#lang pollen

◊headline{This is my title}
◊publish-date{Saturday, 13th June, 2015}
◊make-toc{}

◊section[1]{Introduction}

Blah blah

◊section[2]{Subsection of Introduction}

Blah blah blah

directory-require.rkt will be different. In this case, the TOC entries are generated whether or not the TOC has been requested in the source file. (Side benefit: if you’re working in DrRacket, it’s easier to see what’s going on when you run the file.) Notice that the TOC-gathering logic is now separated from the section function:

#lang racket/base
(provide (all-defined-out))
(require txexpr racket/string)

;; delete `toc` variable

;; `make-toc` is the same
(define (make-toc) '(meta ((toc "true"))))

;; `section` doesn't toggle its behavior based on `toc` variable
;; `gensym` is a handy Racket function for id fields
;; because it avoids problems of escaping characters, etc. 
(define (section level text)
  (define heading-tag-numbered (string->symbol (format "h~a" (+ 1 level))))
  `(,heading-tag-numbered [[id ,(symbol->string (gensym))]] ,text))

;; use `root` as the place to gather the headings for toc-entries, 
;; because it executes last, and thus all the headings will be available
(define (root . xs)
  ;; gather the headings (using `split-txexpr` utility function in txexpr library)
  (define-values (_ headings)
    (splitf-txexpr `(root ,@xs)
                   (λ(x) (and (txexpr? x) (member (car x) '(h1 h2 h3 h4 h5 h6 h7))))))
  ;; covert these headings into toc entries using helper function
  (define toc-entries (map heading->toc-entry headings))
  ;; package the content into a `body` tag, and the toc-entries into a `toc-entries` tag
  `(root (body ,@xs) (toc-entries ,@toc-entries)))

;; helper function for `root`
(define (heading->toc-entry heading)
  `(div [[class ,(string-replace (symbol->string (get-tag heading)) "h" "nav")]]
        (a [[href ,(string-append "#" (attr-ref heading 'id))]] ,@(get-elements heading))))

Finally, rather than using doc directly, the template will now look like this, using select-from-doc to extract toc-entries for the TOC, and body for the main content. The toc in metas is used to determine whether to show or hide the TOC entries:

<div id='content'>
  <h1>◊(select-from-doc 'headline here)</h1>
  ◊when/block[(select-from-metas 'toc here)]{
◊(->html `(div (h2 "Table of Contents") ,@(select-from-doc 'toc-entries here)))}
  ◊(map ->html (select-from-doc 'body here))
</div>
I wouldn’t hold myself out as the arbiter between “hack” and “idiom.” Everyone should write programs however they want ;) > Is there another way of passing data between a .pm and a template other than meta? Yes. You can extract anything from the `doc` part of a `.pm` file using [`select-from-doc`](http://pkg-build.racket-lang.org/doc/pollen/Template.html?q=select#%28def._%28%28lib._pollen%2Ftemplate..rkt%29._select-from-doc%29%29). Thus, when setting up a template, you don’t need to use `doc` as a single indivisible thing. You can instead use it as a container for other things. Along those lines, here’s how I’d approach this problem. The source file needs to do three things: a) provide the content of the document, b) provide a summary of the document's headings for an optional TOC, and c) toggle the TOC with a `meta` setting. So I would change `doc` to have two parts: a `body` tag that contains the main content, and a `toc-entries` tag that has the TOC entries. The source file will look the same: ``` racket #lang pollen ◊headline{This is my title} ◊publish-date{Saturday, 13th June, 2015} ◊make-toc{} ◊section[1]{Introduction} Blah blah ◊section[2]{Subsection of Introduction} Blah blah blah ``` `directory-require.rkt` will be different. In this case, the TOC entries are generated whether or not the TOC has been requested in the source file. (Side benefit: if you’re working in DrRacket, it’s easier to see what’s going on when you run the file.) Notice that the TOC-gathering logic is now separated from the `section` function: ``` racket #lang racket/base (provide (all-defined-out)) (require txexpr racket/string) ;; delete `toc` variable ;; `make-toc` is the same (define (make-toc) '(meta ((toc "true")))) ;; `section` doesn't toggle its behavior based on `toc` variable ;; `gensym` is a handy Racket function for id fields ;; because it avoids problems of escaping characters, etc. (define (section level text) (define heading-tag-numbered (string->symbol (format "h~a" (+ 1 level)))) `(,heading-tag-numbered [[id ,(symbol->string (gensym))]] ,text)) ;; use `root` as the place to gather the headings for toc-entries, ;; because it executes last, and thus all the headings will be available (define (root . xs) ;; gather the headings (using `split-txexpr` utility function in txexpr library) (define-values (_ headings) (splitf-txexpr `(root ,@xs) (λ(x) (and (txexpr? x) (member (car x) '(h1 h2 h3 h4 h5 h6 h7)))))) ;; covert these headings into toc entries using helper function (define toc-entries (map heading->toc-entry headings)) ;; package the content into a `body` tag, and the toc-entries into a `toc-entries` tag `(root (body ,@xs) (toc-entries ,@toc-entries))) ;; helper function for `root` (define (heading->toc-entry heading) `(div [[class ,(string-replace (symbol->string (get-tag heading)) "h" "nav")]] (a [[href ,(string-append "#" (attr-ref heading 'id))]] ,@(get-elements heading)))) ``` Finally, rather than using `doc` directly, the template will now look like this, using `select-from-doc` to extract `toc-entries` for the TOC, and `body` for the main content. The `toc` in `metas` is used to determine whether to show or hide the TOC entries: ``` html <div id='content'> <h1>◊(select-from-doc 'headline here)</h1> ◊when/block[(select-from-metas 'toc here)]{ ◊(->html `(div (h2 "Table of Contents") ,@(select-from-doc 'toc-entries here)))} ◊(map ->html (select-from-doc 'body here)) </div> ```
malcolmstill commented 9 years ago (Migrated from github.com)

Excellent reply, thanks!

Excellent reply, thanks!
mbutterick commented 9 years ago (Migrated from github.com)

PS. Check out the gregor package for handling date / time fields.

PS. Check out the `gregor` package for handling date / time fields.
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: mbutterick/pollen#55
Loading…
There is no content yet.