Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -13,6 +13,6 @@ *.ltx *.aux *.log *.xml *.toc - +*.mark Index: LICENSE.md ================================================================== --- LICENSE.md +++ LICENSE.md @@ -1,23 +1,48 @@ -All images, recordings, writings and other files in this repository, unless stated otherwise, belong -to their authors. You **do not** have permission to reuse or redistribute any of them in any form, -except for files that specifically provide licensing terms. - -The _source code files_ (which include `.rkt`, `.pp`, and `.p` files) generally include notices -stating that they are licensed for reuse under the terms of The Local Yarn License 1.0.0, the text -of which is included below. By the same token, the license below applies only to files that -designate it as their license, not to every file in this repository. Other supplementary files (such -as `makefile`) may specify different license terms. - -# The Local Yarn License 1.0.0 - -**Required Credit:** Joel Dueck () +# Licensing + +This project includes both writing and code. I want people to be able to use the code safely and +without restriction, but for authors to have tighter control over their writing, images and other +content. + +Accordingly: + +* Each file is licensed separately. The header of each file, or its reference in `NOTICES.txt`, + determines how you are allowed to use the contents of that file. +* The source code files generally use a very permissive license, the Blue Oak Model license. A copy + of that license is included below). +* Other files, such as those containing the content of the website and books, are included here by + permission of their authors, and are generally not licensed for reuse unless specifically + provided. + +Note that nothing here applies to files that aren’t included in [this project’s Fossil +repository](https://thelocalyarn.com/cgi-bin/yarncode/dir?ci=tip). + +## Giving Credit + +Please consider giving public credit to this project if you use significant amounts of its code in +any software or system, or if you publish any work using code from this project. This credit should +be a short notice that, at a minimum, includes this project’s name *The Local Yarn* and its domain +name `thelocalyarn.com`. + +Suggested examples: + +* If users access your system or work by running a program you provide, include the notice in the + program’s *About* screen (for a graphical or interactive program) or in the output of the + program’s “version” argument (for command-line programs). +* If users access your system or work via the World Wide Web, put the notice or a link to the notice + at the bottom of the home page. +* If your work is published as a printed book, include the notice in the book’s front matter. + +# Blue Oak Model License + +Version 1.0.0 ## Purpose This license gives everyone as much permission to work with this software as possible, while -protecting contributors from liability, and ensuring contributors are given clear, public credit. +protecting contributors from liability. ## Acceptance In order to receive this license, you must agree to its rules. The rules of this license are both obligations under that agreement and conditions to your license. You must not do anything with this @@ -26,40 +51,20 @@ ## Copyright Each contributor licenses you to do everything with this software that would otherwise infringe that contributor's copyright in it. -## Credit - -If you run or combine this software with other software in any larger system, or if you publish any -work using this software, you must give us credit. - -To give credit, give users of your system or published work a notice listing all the names in -_Required Credit_ above, saying that they contributed to this software and that this software was -used as part of your system or to publish your work, and make sure this notice is easy for people to -find when using your system or published work. - -For example: - -* If users access your system or work by running a program you provide, include the notice in the - program’s *About* screen (for a graphical or interactive program) or in the output of the - program’s “version” argument (for command-line programs). -* If users access your system or work via the World Wide Web, put the notice or a link to the notice - at the bottom of the home page. -* If your work is published as a printed book, include the notice in the book’s front matter. - ## Notices You must ensure that everyone who gets a copy of any part of this software from you, with or without -changes, also gets the text of this license, the _Required Credit_ notice above, and a copy of the -`NOTICE.txt` file. +changes, also gets the text of this license or a link to . ## Excuse -If anyone notifies you in writing that you have not complied with [Notices](#notices) or -[Credit](#credit), you can keep your license by taking all practical steps to comply within 30 days -after the notice. If you do not do so, your license ends immediately. +If anyone notifies you in writing that you have not complied with [Notices](#notices), you can keep +your license by taking all practical steps to comply within 30 days after the notice. If you do not +do so, your license ends immediately. ## Patent Each contributor licenses you to do everything with this software that would otherwise infringe any patent claims they can license or become able to license. ADDED blog.rkt Index: blog.rkt ================================================================== --- blog.rkt +++ blog.rkt @@ -0,0 +1,75 @@ +#lang pollen/mode racket/base + +;; Copyright (c) 2019 Joel Dueck. +;; +;; Licensed under the Apache License, Version 2.0 (the "License"); +;; you may not use this file except in compliance with the License. +;; A copy of the License is included with this source code, in the +;; file "LICENSE.txt". +;; You may also obtain a copy of the License at +;; +;; http://www.apache.org/licenses/LICENSE-2.0 +;; +;; Unless required by applicable law or agreed to in writing, software +;; distributed under the License is distributed on an "AS IS" BASIS, +;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +;; See the License for the specific language governing permissions and +;; limitations under the License. +;; +;; Author contact information: +;; joel@jdueck.net +;; https://joeldueck.com +;; ------------------------------------------------------------------------- + +;; Builds the paginated “blog” HTML files (blog-pg1.html ...) from the SQLite cache +;; The files will be written out every time this module is evaluated! (see end) + +(require "crystalize.rkt" + "snippets-html.rkt" + racket/file + sugar/list) + +(provide main) + +;; How many items per blog page +(define per-page 5) + +;; Returns a string containing the entire HTML contents of a given blog page +(define (blog-page posts-str pagenum total-pages) + (define page-nav (html$-paginate-navlinks pagenum total-pages "blog")) + ◊string-append{ + + + ◊html$-page-head[(format "The Local Yarn: Blog, p. ~a" pagenum)] + ◊html$-page-body-open[] + + + + + + ◊posts-str + + + + ◊html$-page-body-close[] + }) + +;; Grabs all the articles+notes from the cache and writes out all the blog page files +(define (build-blog) + (spell-of-summoning!) ; Turn on the DB + + (define articles+notes (slice-at (list/articles+notes 'listing_full_html #:series #f) per-page)) + (define pagecount (length articles+notes)) + + (for ([pagenum (in-range 1 (+ 1 pagecount))] + [page (in-list articles+notes)]) + (define filename (format "blog-pg~a.html" pagenum)) + (displayln (format "Writing: ~a" filename)) + (display-to-file (blog-page (apply string-append page) pagenum pagecount) + filename + #:mode 'text + #:exists 'replace))) + +(define (main) + ;; Do it! + (build-blog)) Index: code-docs/crystalize.scrbl ================================================================== --- code-docs/crystalize.scrbl +++ code-docs/crystalize.scrbl @@ -28,23 +28,17 @@ it in various pieces in a SQLite cache. Individual articles save chunks of rendered HTML to the cache when their individual pages are rendered. The SQLite cache is then referenced by any page that collects multiple articles and notes together. This is much faster than fetching docs and metas through Pollen’s cache and re-converting them to HTML. -This module only provides three functions; almost all its code is private. - @defproc[(spell-of-summoning!) void?] Initializes the SQLite database cache file. This involves creating the file (@filepath{vitreous.sqlite}) if it does not exist, and running queries to create tables in the database if they do not exist. -This function should be called near the beginning of any HTML template used to render an individual -article. - -I could have made it so the function is called automatically every time Pollen is run. But that -would mean it gets run even when the target is not HTML, which is unnecessary. +This function is called automatically in @filepath{pollen.rkt} whenever HTML is the target output. @defproc[(crystalize-article! [pagenode pagenode?] [doc txexpr?]) non-empty-string?] Returns a string containing the HTML of @racket[_doc]. @margin-note{This is one function that breaks my convention of using a prefix of @tt{html$-} for functions that return strings of HTML.} @@ -51,10 +45,85 @@ Privately, it does a lot of other work. The article is saved to the SQLite cache. If the article specifies a @racket['series] meta, information about that series is fetched and used in the rendering of the article. If there are @racket[note]s in the doc, they are parsed and saved individually to the SQLite cache. 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. + +@deftogether[(@defproc[(list/articles [type (or/c 'listing_full_html + 'listing_short_html + 'listing_excerpt_html)] + [#:series series (or/c string? boolean?) #t] + [#:limit limit stringish? -1] + [order string? "DESC"]) (listof string?)] + @defproc[(list/articles+notes [type (or/c 'listing_full_html + 'listing_short_html + 'listing_excerpt_html)] + [#:series series (or/c string? boolean?) #t] + [#:limit limit stringish? -1] + [order string? "DESC"]) (listof string?)])] + +Fetches the HTML for all articles from the SQLite cache and returns a list of strings containing the +HTML for each. The articles will be ordered by publish date according to @racket[_order] and +optionally limited to the series specified in @racket[_series]. + +If @racket[_series] expression evaluates to @racket[#f], articles will not be filtered by series. If +it evaluates to @racket[#t] (the default), articles will be filtered by those that specify the +current output of @racket[here-output-path] in their @tt{series_pagenode} column in the SQLite +cache. If a string is supplied, articles will be filtered by those containing that exact value in +their @tt{series_pagenode} column in the SQLite cache. + +The @racket[_order] expression must evaluate to either @racket["ASC"] or @racket["DESC"] and the +@racket[_limit] expressions must evaluate to a value suitable for use in the @tt{LIMIT} clause of +@ext-link["https://sqlite.org/lang_select.html"]{a SQLite @tt{SELECT} statement}. An expression that +evaluates to a negative integer (the default) is the same as having no limit. + +@deftogether[(@defproc[(listing<>-short/articles [#:series series (or/c string? boolean?) #t] + [#:limit limit stringish? -1] + [order string? "DESC"]) txexpr?] + @defproc[(listing<>-full/articles [#:series series (or/c string? boolean?) #t] + [#:limit limit stringish? -1] + [order string? "DESC"]) txexpr?])] + +@margin-note{Notice how the functions that start with @tt{list/} return lists and the functions that +start with @tt{listing<>} return fenced HTML strings. Maybe this is ugly, but it helps me keep these +otherwise too-similar sets of functions straight in my head.} + +Fetches the HTML for all articles from the SQLite cache and returns the HTML strings fenced inside +a @racket['style] tagged X-expression. The articles will be ordered by publish date according to +@racket[_order] and optionally limited to the series specified in @racket[_series]. + +If @racket[_series] expression evaluates to @racket[#f], articles will not be filtered by series. If +it evaluates to @racket[#t] (the default), articles will be filtered by those that specify the +current output of @racket[here-output-path] in their @tt{series_pagenode} column in the SQLite +cache. If a string is supplied, articles will be filtered by those containing that exact value in +their @tt{series_pagenode} column in the SQLite cache. + +The @racket[_order] expression must evaluate to either @racket["ASC"] or @racket["DESC"] and the +@racket[_limit] expressions must evaluate to a value suitable for use in the @tt{LIMIT} clause of +@ext-link["https://sqlite.org/lang_select.html"]{a SQLite @tt{SELECT} statement}. An expression that +evaluates to a negative integer (the default) is the same as having no limit. + +The reason for enclosing the results in a @racket['style] txexpr is to prevent the HTML from being +escaped by @racket[->html] in the template. This tag was picked for the job because there will +generally never be a need to include any actual CSS information inside a @tt{"] removed. +The contents of the style tags are left intact. + +Use this with strings returned from @racket[->html] when called on docs containing +@racket[listing<>-full/articles] or its siblings. @defproc[(article-plain-title [pagenode pagenode?]) non-empty-string?] Fetches the “plain” title (i.e., with no HTML markup) for the given article from the SQLite cache. If the article did not specify a title, a default title is supplied. If the article contained Index: code-docs/dust.scrbl ================================================================== --- code-docs/dust.scrbl +++ code-docs/dust.scrbl @@ -37,30 +37,43 @@ @defthing[default-authorname string? #:value "Joel Dueck"] Used as the default author name for @code{note}s, and (possibly in the future) for articles generally. -@deftogether[(@defthing[articles-path path-string? #:value "articles"] - @defthing[series-path path-string? #:value "series"])] +@deftogether[(@defthing[articles-folder path-string? #:value "articles"] + @defthing[series-folder path-string? #:value "series"])] -The path of the folder that contains the Pollen source documents for Articles and Series +The names of the folders that contain the Pollen source documents for Articles and Series respectively, relative to the project’s document root. @deftogether[(@defproc[(articles-pagetree) pagetree?] @defproc[(series-pagetree) pagetree?])] These are project-wide pagetrees: @racket[articles-pagetree] contains a pagenode for every Pollen -document contained in @racket[articles-path], and @racket[series-pagetree] contains a pagenode for -every Pollen document in @racket[series-path]. The pagenodes themselves point to the rendered +document contained in @racket[articles-folder], and @racket[series-pagetree] contains a pagenode for +every Pollen document in @racket[series-folder]. The pagenodes themselves point to the rendered @tt{.html} targets of the source documents. -@section{Metas and @code{txexpr}s} +@defproc[(here-output-path) path?] + +Returns the path to the current output file, relative to @racket[current-project-root]. If no metas +are available, raises an error. This is like what you can get from the @tt{here} variable that Pollen +provides, except it is available outside templates. + +@defproc[(here-id [suffix (or/c (listof string?) string? #f) #f]) string?] + +Returns the 8-character prefix of the SHA1 hash of the current document’s output path. If no metas +are available, raises an error. If @racket[_suffix] evaluates to a string or a list of strings, they +are appended verbatim to the end of the hash. -@defproc[(attr-present? [name symbol?] [attrs (listof pair?)]) boolean?] +This ID is used when creating URL fragment links within an article, such as for footnotes and index +entries. As long as the web version of the article is not moved to a new URL, the ID will remain the +same, which ensures deep links using the ID don’t break. The ID also ensures each article’s internal +links will be unique, so that links do not collide when multiple articles are being shown on +a single HTML page. -Shortsightedly redundant to @code{attrs-have-key?}. Returns @code{#t} if @racket[_name] is one of -the attributes present in @racket[_attrs], otherwise returns @code{#f}. +@section{Metas and @code{txexpr}s} @defproc[(maybe-attr [key symbol?] [attrs txexpr-attrs?] [missing-expr any/c ""]) any/c] Find the value of @racket[_key] in the supplied list of attributes, returning the value of @racket[_missing-expr] if it’s not there. @@ -154,10 +167,24 @@ @defproc[(series-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[""]. + +@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, +returning @racket[#t] on success or @racket[#f] on failure. If either precondition is not true, +returns @|void-const|. + +When an article is being rendered, that means the article has changed, and if the article has +changed, its series page (if any) should be updated as well. Touching the @filepath{.poly.pm} file +for a series page triggers a re-render of that page when running @tt{make web} to rebuild the web +content (see @repo-file{makefile}). + +Only used in one place, @repo-file{tags-html.rkt}. @defproc[(disposition-values [str string?]) any] Given a string @racket[_str], returns two values: the portion of the string coming before the first space, and the rest of the string. Index: code-docs/pollen.scrbl ================================================================== --- code-docs/pollen.scrbl +++ code-docs/pollen.scrbl @@ -8,10 +8,11 @@ @; without any warranty. @(require "scribble-helpers.rkt") @(require (for-label "../pollen.rkt" "../dust.rkt" + "../crystalize.rkt" racket/base racket/contract racket/string txexpr pollen/tag @@ -112,10 +113,18 @@ @deftogether[(@defproc[(section [element xexpr?] ...) txexpr?] @defproc[(subsection [element xexpr?] ...) txexpr?])] Create second- and third-level headings, respectively. This is counting the article's title as the first-level header (even if the current article has no title). + +@defproc[(block [element xexpr?] ...) txexpr?] + +A container for content that should appear grouped together on larger displays. Intended for use in +Series pages, where the template is very minimal. You would want output from +@racket[listing<>-short/articles] to appear inside a @racket[block], but you would want output from +@racket[listing<>-full/articles] to appear outside it (since each article effectively supplies its own +block). Only relevant to HTML output. @deftogether[(@defproc[(link [link-id stringish?] [link-text xexpr?]) txexpr?] @defproc[(url [link-id stringish?] [url string?]) void?])] All hyperlinks are specified reference-style. So, to link some text, use the @code{link} tag with @@ -154,10 +163,46 @@ The @code{fndef} for a given id may be placed anywhere in the source document, even before it is referenced. If you create a @code{fn} reference without a corresponding @code{fndef}, a @code{"Missing footnote definition!"} message will be substituted for the footnote text. Conversely, creating a @code{fndef} that is never referenced will produce no output, warning or error. + +@deftogether[(@defproc[(dialogue [elements xexpr?] ...) txexpr?] + @defproc[(say [interlocutor string?] [elements xexpr?] ...) txexpr?])] + +Use these two tags together for transcripts of dialogue, chats, screenplays, interviews and so +forth. + +Example usage: + +@codeblock|{ + #lang pollen + + ◊dialogue{ + ◊say["Tavi"]{You also write fiction, or you used to. Do you still?} + ◊say["Lorde"]{The thing is, when I write now, it comes out as songs.} + } +}| + +@defproc[(index [heading string?] [elements xexpr?] ...) txexpr?] + +Creates an entry in the keyword index under @racket[_heading] that points back to this spot in the +document. If @racket[_elements] is not empty, the web edition of the document will use it as the +contents of an understated hyperlink to back to @racket[_heading] in the keyword index. + +The example below will create two index entries, one under the heading “compassion” and one under +the heading “cats”: + +@codeblock|{ + #lang pollen + + “I have a theory which I suspect is rather immoral,” Smiley + went on, more lightly. “Each of us has only a quantum of + ◊index["compassion"]{compassion}. That if we lavish our concern + on every stray ◊index["cats"] cat we never get to the centre of + things. What do you think of it?” +}| @defproc[(note [#:date date-str non-empty-string?] [#:author author string? ""] [#:author-url author-url string? ""] [#:disposition disp-str string? ""]) txexpr?] Index: code-docs/scribble-helpers.rkt ================================================================== --- code-docs/scribble-helpers.rkt +++ code-docs/scribble-helpers.rkt @@ -29,11 +29,12 @@ (only-in net/uri-codec uri-encode)) (provide (all-defined-out)) (define repo-url/ "https://thelocalyarn.com/cgi-bin/yarncode/") -;; Link to a ticket on the Fossil repository by specifying the ticket ID +;; Link to a ticket on the Fossil repository by specifying the ticket ID. +;; The "_parent" target breaks out of the iframe used by the Fossil repo web UI. (define (ticket id-str) (hyperlink (string-append repo-url/ "tktview?name=" id-str) "ticket " (tt id-str) #:style (style #f (list (attributes '((target . "_parent"))))))) @@ -42,15 +43,23 @@ (define (wiki title) (hyperlink (string-append repo-url/ "wiki?name=" (uri-encode title)) title #:style (style #f (list (attributes '((target . "_parent"))))))) +;; Link somewhere outside these docs or Racket docs. The `_blank` target opens in a new tab. (define (ext-link url-str . elems) (keyword-apply hyperlink '(#:style) (list (style #f (list (attributes '((target . "_blank")))))) - url-str - elems)) + url-str + elems)) + +;; Link to show contents of the latest checked-in version of a file +;; (or a file listing if a directory was specified) +(define (repo-file filename) + (hyperlink (string-append repo-url/ "file/" filename) + (tt filename) + #:style (style #f (list (attributes '((target . "_parent"))))))) (define (responsive-retina-image img-path) (image img-path #:scale 0.5 #:style (style #f (list (attributes '((style . "max-width:100%;height:auto;"))))))) Index: code-docs/snippets-html.scrbl ================================================================== --- code-docs/snippets-html.scrbl +++ code-docs/snippets-html.scrbl @@ -15,10 +15,11 @@ racket/base racket/contract racket/string pollen/template pollen/pagetree + txexpr sugar/coerce)) @title{@filepath{snippets-html.rkt}} @defmodule["snippets-html.rkt" #:packages ()] @@ -55,17 +56,23 @@ @defproc[(html$-page-head [title (or/c string? #f) #f]) non-empty-string?] Returns the @tt{} section of an HTML document. -@defproc[(html$-page-body-open) non-empty-string?] +@defproc[(html$-page-body-open [body-class string? ""]) non-empty-string?] Returns the opening @tt{} and @tt{
} tags and elements that immediately follow, such as site header, logo and navigation. -@defproc[(html$-article-open [title-specified-in-doc? boolean?] [title txexpr?] [pubdate string?]) -non-empty-string?] +If @racket[_body-class] is a non-empty string, its contents will be included in the @tt{class} +attribute of the @tt{} tag. + +@defproc[(html$-article-open [pagenode pagenode?] + [title-specified-in-doc? boolean?] + [title txexpr?] + [pubdate string?]) + non-empty-string?] Returns the opening @tt{
} tag and elements that immediately follow: permlink, publish date, and opening @tt{
} tag. The @racket[_title-specified-in-doc?] form changes the HTML markup structure used. @@ -73,10 +80,16 @@ @defproc[(html$-article-close [footertext string?]) non-empty-string?] Returns a string containing a closing @tt{
} tag, a @tt{