Index: cache.rkt ================================================================== --- cache.rkt +++ cache.rkt @@ -7,31 +7,26 @@ db/base db/sqlite3 threading pollen/setup racket/match - (rename-in racket/list - (group-by group-list-by)) "dust.rkt" (except-in pollen/core select)) (provide init-cache-db! cache-conn ; The most eligible bachelor in Neo Yokyo (schema-out cache:article) (schema-out cache:note) - (schema-out cache:series) (schema-out cache:index-entry) (schema-out listing) delete-article! delete-notes! articles articles+notes listing-htmls fenced-listing - unfence - preheat-series! - series-grouped-list) + unfence) ;; Cache DB and Schemas (define DBFILE (build-path (current-project-root) "vitreous.sqlite")) (define cache-conn (make-parameter (sqlite3-connect #:database DBFILE #:mode 'create))) @@ -47,11 +42,11 @@ [author string/f] [conceal string/f] [series-page symbol/f] [noun-singular string/f] [note-count integer/f] - [content-html string/f] + [content-html string/f] [disposition string/f] [disp-html-anchor string/f] [listing-full-html string/f] ; full content but without notes [listing-excerpt-html string/f] ; Not used for now [listing-short-html string/f])) ; Date and title only @@ -71,18 +66,10 @@ [conceal string/f] [listing-full-html string/f] [listing-excerpt-html string/f] ; Not used for now [listing-short-html string/f])) ; Date and title only -(define-schema cache:series #:table "series" - ([id id/f #:primary-key #:auto-increment] - [page symbol/f] - [title string/f] - [published string/f] - [noun-plural string/f] - [noun-singular string/f])) - (define-schema cache:index-entry #:table "index_entries" ([id id/f #:primary-key #:auto-increment] [entry string/f] [subentry string/f] [page symbol/f] @@ -98,11 +85,10 @@ [html string/f])) (define (init-cache-db!) (create-table! (cache-conn) 'cache:article) (create-table! (cache-conn) 'cache:note) - (create-table! (cache-conn) 'cache:series) (create-table! (cache-conn) 'cache:index-entry)) (define (delete-article! page) (query-exec (cache-conn) (~> (from cache:article #:as a) @@ -208,32 +194,5 @@ ;; Remove "" introduced by using ->html on docs containing output from ;; listing functions (define (unfence html-str) (regexp-replace* #px"<[\\/]{0,1}style>" html-str "")) - -;; -;; ~~~ Fetching series ~~~ -;; -(define (series-grouped-list) - (~> (for/list ([row (in-entities (cache-conn) - (order-by (from cache:series #:as s) - ([s.noun-plural #:asc])))]) row) - (group-list-by cache:series-noun-plural _ string-ci=?))) - -;; Preloads the SQLite cache with info about each series. -;; I may not actually need this but I’m leaving it for now. -(define (preheat-series!) - (query-exec (cache-conn) - (~> (from cache:series #:as s) - (where 1) - delete)) - (define series-rows - (for/list ([series-pagenode (in-list (cdr (series-pagetree)))]) - (define series-metas (get-metas series-pagenode)) - (make-cache:series - #:page series-pagenode - #:title (hash-ref series-metas 'title) - #:published (hash-ref series-metas 'published "") - #:noun-plural (hash-ref series-metas 'noun-plural "") - #:noun-singular (hash-ref series-metas 'noun-singular "")))) - (void (apply insert! (cache-conn) series-rows))) Index: code-docs/cache.scrbl ================================================================== --- code-docs/cache.scrbl +++ code-docs/cache.scrbl @@ -42,16 +42,10 @@ in the project root folder) by running queries to create tables in the database if they do not exist. } -@defproc[(preheat-series!) void?]{ - -Save info about each series in @racket[series-folder] to the cache. - -} - @section{Retrieving cached data} Some of this looks a little wacky, but it’s a case of putting a little extra complextity into the back end to make things simple on the front end. These functions are most commonly used inside the @emph{body} of a Pollen document (i.e., series pages). @@ -144,17 +138,10 @@ Use this in templates with strings returned from @racket[->html] when called on docs that use the @racket[fenced-listing] tag function. } -@defproc[(series-grouped-list) (listof (listof cache:series?))]{ - -Return a list of lists of all @racket[cache:series] in the cache database. The series are grouped so -that series using the same value in the @tt{noun_plural} column appear together. - -} - @section{Deleting records} @deftogether[(@defproc[(delete-article! [page stringish?]) void?] @defproc[(delete-notes! [page stringish?]) void?])]{ @@ -218,22 +205,10 @@ [listing-short-html string/f]) #:constructor-name make-cache:note]{ Table holding cached information on notes. -} - -@defstruct*[cache:series ([id id/f] - [page symbol/f] - [title string/f] - [published string/f] - [noun-plural string/f] - [noun-singular string/f]) - #:constructor-name make-cache:series]{ - -Table holding cached information on series. - } @defstruct*[cache:index-entry ([id id/f] [entry string/f] [subentry string/f] Index: code-docs/crystalize.scrbl ================================================================== --- code-docs/crystalize.scrbl +++ code-docs/crystalize.scrbl @@ -43,15 +43,6 @@ article. If there are @racket[note]s or @racket[index] tags in the doc, they are parsed and saved individually to the SQLite cache (using @racket[make-cache:note] and @racket[make-cache:index-entry]). If any of the notes use the @code{#:disposition} attribute, information about the disposition is parsed out and used in the rendering of the article. -} - -@defproc[(cache-series!) void?]{ - -Attempts to look up certain values in @racket[current-metas] which we expect to be defined on -a typical series page, and saves them to the cache using @racket[make-cache:series]. If @tt{title} -is not defined in the current metas, you’ll get an error. If any of the others are missing, an empty -string is used. - } Index: code-docs/dust.scrbl ================================================================== --- code-docs/dust.scrbl +++ code-docs/dust.scrbl @@ -7,10 +7,11 @@ scribble/example) @(require (for-label "../pollen.rkt" "../dust.rkt" "../cache.rkt" + "../series-list.rkt" racket/base racket/contract txexpr sugar/coerce pollen/tag @@ -182,24 +183,31 @@ "individual as Queequeg circulating among the polite society of a civilized " "town, that astonishment soon departed upon taking my first daylight " "stroll through the streets of New Bedford…"))) (default-title (get-elements doc))] -@defproc[(metas-series-pagenode) pagenode?] +@defproc[(current-series-pagenode) pagenode?] If @code{(current-metas)} has the key @racket['series], converts its value to the pagenode pointing to that series, otherwise returns @racket['||]. -@defproc[(series-metas-noun) string?] - -If @code{(current-metas)} has the key @racket['series], and if the corresponding series defines a meta -value for @racket['noun-singular], then return it, otherwise return @racket[""]. - -@defproc[(series-metas-title) string?] - -If @code{(current-metas)} has the key @racket['series], and if the corresponding series defines a meta -value for @racket['title], then return it, otherwise return @racket[""]. +@examples[#:eval dust-eval +(require pollen/core) +(parameterize ([current-metas (hash 'series "marquee-fiction")]) + (current-series-pagenode))] + +@defproc[(current-series-noun) string?] + +If @code{(current-metas)} has the key @racket['series] and if there is a corresponding +@racket[series] in the @racket[series-list], return its @racket[series-noun-singular] value; +otherwise return @racket[""]. + +@defproc[(current-series-title) string?] + +If @code{(current-metas)} has the key @racket['series] and if there is a corresponding +@racket[series] in the @racket[series-list], return its @racket[series-title] value; +otherwise return @racket[""]. @defproc[(invalidate-series) (or/c void? boolean?)] If the current article specifies a @racket['series] meta, and if a corresponding @filepath{.poly.pm} file exists in @racket[series-folder], attempts to “touch” the last-modified timestamp on that file, Index: code-docs/main.scrbl ================================================================== --- code-docs/main.scrbl +++ code-docs/main.scrbl @@ -49,10 +49,11 @@ @local-table-of-contents[] @include-section["tour.scrbl"] @include-section["design.scrbl"] @include-section["pollen.scrbl"] @; pollen.rkt +@include-section["series.scrbl"] @include-section["dust.scrbl"] @; dust.rkt @include-section["snippets-html.scrbl"] @; you get the idea @include-section["cache.scrbl"] @include-section["crystalize.scrbl"] @include-section["other-files.scrbl"] ADDED code-docs/series.scrbl Index: code-docs/series.scrbl ================================================================== --- code-docs/series.scrbl +++ code-docs/series.scrbl @@ -0,0 +1,74 @@ +#lang scribble/manual + +@; SPDX-License-Identifier: BlueOak-1.0.0 +@; This file is licensed under the Blue Oak Model License 1.0.0. + +@(require "scribble-helpers.rkt" + scribble/example) + +@(require (for-label "../pollen.rkt" + "../series-list.rkt" + "../dust.rkt" + "../cache.rkt" + pollen/core + racket/base + racket/contract)) + +@title{Defining Series} + +To create a new series: + +@itemlist[#:style 'ordered + + @item{Create a file @filepath{my-key.poly.pm} inside @racket[series-folder] and include a call + to @racket[fenced-listing] to list all the articles and notes that will be included in the series: + @codeblock|{ +#lang pollen + +◊(define-meta title "My New Series") + +◊block{Here’s what we call a bunch of similar articles: + +◊(fenced-listing (articles 'short)) + +} + }| + } + + @item{Add an entry for @racket[_my-key] to @racket[series-list] in @filepath{series-list.rkt}} + + @item{Use @racket[(define-meta series "my-key")] in articles to add them to the series.} + + @item{If @racket[series-ptree-ordered?] is @racket[#t], create a @seclink["Pagetree" #:doc '(lib + "pollen/scribblings/pollen.scrbl")]{pagetree} file in @racket[series-folder] named + @filepath{my-key.ptree}.} + + ] + +@section{Series list} + +@defmodule["series-list.rkt" #:packages ()] + +This module contains the most commonly used bits of meta-info about @tech{series}. Storing these +bits in a hash table of structs makes them faster to retrieve than when they are stored inside the +metas of the Pollen documents for the series themselves. + +@defthing[series-list hash?]{ + +An immutable hash containing all the title and noun info for each @tech{series}. Each key is +a string and each value is a @racket[series] struct. + +} + +@defstruct[series ([key string?] + [title string?] + [noun-plural string?] + [noun-singular string?] + [ptree-ordered? boolean?])]{ + +Struct for holding metadata for a @tech{series}. The @racket[_ptree-ordered?] value should be +@racket[#t] if there is a @filepath{@italic{key}.ptree} file in @racket[series-folder] that provides +information on how articles in the series are ordered. + +} + Index: crystalize.rkt ================================================================== --- crystalize.rkt +++ crystalize.rkt @@ -14,12 +14,11 @@ (except-in pollen/core select) ; avoid conflict with deta ) (require "dust.rkt" "cache.rkt" "snippets-html.rkt") -(provide parse-and-cache-article! - cache-series!) +(provide parse-and-cache-article!) (define current-title (make-parameter #f)) (define current-excerpt (make-parameter #f)) (define current-notes (make-parameter '())) (define current-disposition (make-parameter "")) @@ -53,11 +52,11 @@ (current-disposition) (current-disp-id))] [title-html (->html title-tx #:splice? #t)] [title-plain (tx-strs title-tx)] [header (html$-article-open pagenode title-specified? title-tx pubdate)] - [series-node (metas-series-pagenode)] + [series-node (current-series-pagenode)] [footertext (make-article-footertext pagenode series-node (current-disposition) (current-disp-id) (length (current-notes)))] @@ -79,11 +78,11 @@ #:published pubdate #:updated (maybe-meta 'updated) #:author (maybe-meta 'author default-authorname) #:conceal (maybe-meta 'conceal) #:series-page series-node - #:noun-singular (maybe-meta 'noun (series-metas-noun)) + #:noun-singular (maybe-meta 'noun (current-series-noun)) #:note-count (length (current-notes)) #:content-html doc-html #:disposition (current-disposition) #:disp-html-anchor (current-disp-id) #:listing-full-html listing-full @@ -119,14 +118,14 @@ `(title ,@title-elems ,disposition-part)) ;; Convert a bunch of information about an article into some nice English and links. (define (make-article-footertext pagenode series disposition disp-note-id note-count) (define series-part - (match (series-metas-title) + (match (current-series-title) [(? non-empty-string? s-title) (format "This is ~a, part of ‘~a’." - (series-metas-noun) + (current-series-noun) series s-title)] [_ ""])) (define disp-part (cond [(non-empty-string? disposition) @@ -202,11 +201,11 @@ #:title-plain (tx-strs title-tx) #:published note-date #:author author #:author-url author-url #:disposition disposition-attr - #:series-page (metas-series-pagenode) + #:series-page (current-series-pagenode) #:conceal (or (maybe-attr 'conceal attrs #f) (maybe-meta 'conceal)) #:content-html content-html #:listing-full-html listing-full #:listing-excerpt-html listing-full #:listing-short-html "")) @@ -253,22 +252,5 @@ (unless (null? entry-txs) (void (apply insert! (cache-conn) (for/list ([etx (in-list entry-txs)]) (txexpr->index-entry etx pagenode)))))) - - -;; Save the current article to the `series` table of the SQLite cache -;; Should be called from a template for series pages -(define (cache-series!) - (define here-page (path->string (here-output-path))) - (query-exec (cache-conn) - (delete (~> (from cache:series #:as s) - (where (= s.page ,here-page))))) - (void - (insert-one! (cache-conn) - (make-cache:series - #:page (string->symbol here-page) - #:title (hash-ref (current-metas) 'title) - #:published (hash-ref (current-metas) 'published "") - #:noun-plural (hash-ref (current-metas) 'noun-plural "") - #:noun-singular (hash-ref (current-metas) 'noun-singular ""))))) Index: dust.rkt ================================================================== --- dust.rkt +++ dust.rkt @@ -2,14 +2,16 @@ ; SPDX-License-Identifier: BlueOak-1.0.0 ; This file is licensed under the Blue Oak Model License 1.0.0. (require pollen/core + "series-list.rkt" pollen/pagetree pollen/setup pollen/file net/uri-codec + threading file/sha1 gregor txexpr racket/list racket/match @@ -23,13 +25,13 @@ maybe-attr ; Return an attribute’s value or a default ("") if not available here-output-path here-source-path here-id listing-context - series-metas-noun ; Retrieve noun-singular from current 'series meta, or "" - series-metas-title ; Retrieve title of series in current 'series meta, or "" - metas-series-pagenode + current-series-noun ; Retrieve noun-singular from current 'series meta, or #f + current-series-title ; Retrieve title of series in current 'series meta, or #f + current-series-pagenode invalidate-series checked-in? make-tag-predicate tx-strs ymd->english @@ -79,28 +81,30 @@ (define listing-context (make-parameter "")) ;; Checks current-metas for a 'series meta and returns the pagenode of that series, ;; or '|| if no series is specified. -(define (metas-series-pagenode) - (define maybe-series (or (select-from-metas 'series (current-metas)) "")) - (cond - [(non-empty-string? maybe-series) - (->pagenode (format "~a/~a.html" series-folder maybe-series))] - [else '||])) - -(define (series-metas-noun) - (define series-pnode (metas-series-pagenode)) - (case series-pnode - ['|| ""] ; no series specified - [else (or (select-from-metas 'noun-singular series-pnode) "")])) - -(define (series-metas-title) - (define series-pnode (metas-series-pagenode)) - (case series-pnode - ['|| ""] ; no series specified - [else (or (select-from-metas 'title series-pnode) "")])) +(define (current-series-pagenode) + (or (and~> (current-metas) + (hash-ref 'series #f) + (format "~a/~a.html" series-folder _) + ->pagenode) + '||)) + +(define (current-series-noun) + (or (and~> (current-metas) + (hash-ref 'series #f) + (hash-ref series-list _ #f) + series-noun-singular) + "")) + +(define (current-series-title) + (or (and~> (current-metas) + (hash-ref 'series #f) + (hash-ref series-list _ #f) + series-title) + "")) (define article-ids (make-hash)) ;; Generates a short ID for the current article (define (here-id [suffix #f]) Index: makefile ================================================================== --- makefile +++ makefile @@ -5,11 +5,11 @@ # ~~~ Variables used by rules ~~~ # core-files := pollen.rkt dust.rkt -html-deps := snippets-html.rkt tags-html.rkt crystalize.rkt cache.rkt +html-deps := snippets-html.rkt tags-html.rkt crystalize.rkt cache.rkt series-list.rkt article-sources := $(wildcard articles/*.poly.pm) articles-html := $(patsubst %.poly.pm, %.html, $(article-sources)) articles-pdf := $(patsubst %.poly.pm, %.pdf, $(article-sources)) Index: pollen.rkt ================================================================== --- pollen.rkt +++ pollen.rkt @@ -31,16 +31,18 @@ (define-runtime-path tags-html.rkt "tags-html.rkt") (define-runtime-path snippets-html.rkt "snippets-html.rkt") (define-runtime-path dust.rkt "dust.rkt") (define-runtime-path crystalize.rkt "crystalize.rkt") (define-runtime-path cache.rkt "cache.rkt") + (define-runtime-path series-list.rkt "series-list.rkt") (define cache-watchlist (map resolve-module-path (list tags-html.rkt snippets-html.rkt dust.rkt cache.rkt + series-list.rkt crystalize.rkt)))) ;; Macro for defining tag functions that automatically branch based on the ;; current output format and the list of poly-targets in the setup module. ;; Use this macro when you know you will need keyword arguments. ADDED series-list.rkt Index: series-list.rkt ================================================================== --- series-list.rkt +++ series-list.rkt @@ -0,0 +1,33 @@ +#lang racket/base + +; SPDX-License-Identifier: BlueOak-1.0.0 +; This file is licensed under the Blue Oak Model License 1.0.0. + +;; Provides fast metadata for series. +;; TO MAKE A NEW SERIES: +;; 1. Create series/my-key.poly.pm +;; 2. Add an entry for my-key to series-list below +;; 3. Use ◊define-meta[series "my-key"] in articles to add them to the series. +;; 4. If ptree-ordered is #t, also create series/my-key.ptree + +(require racket/list) +(struct series (key title noun-plural noun-singular ptree-ordered?)) + +(define series-list + (make-immutable-hash + (list + ;; ------- DEFINE SERIES HERE ----------- + ; Key Title plural noun singular noun phrase + (+series "marquee-fiction" "Marquee Fiction" "inventions" "an invention" #f) + ))) + +(define (series-grouped-list) + (group-by series-noun-plural (hash-values series-list))) + +;; Quick macro to save a little typing +(define-syntax-rule (+series key title plural singular ptree) + (cons key (series key title plural singular ptree))) + +(provide (struct-out series) + series-list + series-grouped-list) Index: series/template.html.p ================================================================== --- series/template.html.p +++ series/template.html.p @@ -1,8 +1,7 @@ -◊cache-series![] ◊html$-page-head[(select-from-metas 'title metas)] ◊html$-page-body-open["series-page"] ◊(unfence (->html doc #:splice? #t)) Index: snippets-html.rkt ================================================================== --- snippets-html.rkt +++ snippets-html.rkt @@ -10,10 +10,11 @@ racket/string racket/function racket/list txexpr "cache.rkt" + "series-list.rkt" "dust.rkt") (provide html$-page-head html$-page-body-open html$-series-list @@ -238,13 +239,13 @@ (page-func (+ pagenum 1) "Older →" "nav-text"))) (string-join `(,prev-link ,@page-group ,next-link))) (define (series->txpr s) - `(li (a [[href ,(string-append web-root (symbol->string (cache:series-page s)))]] - (i ,(cache:series-title s))))) + `(li (a [[href ,(string-append web-root (format "~a/~a.html" series-folder (series-key s)))]] + (i ,(series-title s))))) (define (html$-series-list) (define series-list-items (for/list ([group (in-list (series-grouped-list))]) - `(div (h2 ,(cache:series-noun-plural (first group))) (ul ,@(map series->txpr group))))) + `(div (h2 ,(series-noun-plural (first group))) (ul ,@(map series->txpr group))))) (->html `(section [[class "column-list"] [style "margin-top: 1.3rem"]] ,@series-list-items))) Index: util/init.rkt ================================================================== --- util/init.rkt +++ util/init.rkt @@ -7,10 +7,9 @@ (provide main) (define (main) (init-cache-db!) - (preheat-series!) (display-to-file (html$-page-footer) (build-path (current-project-root) "scribbled" "site-footer.html") #:exists 'replace))