scribble-arg-processing.rkt:requirethis in yourscribble/textmodule to get access to the arguments you're passing in to be used in the template - which you can access by index or as a key/value store,generate-string-from-scribble.rkt: this has the functiongenerate-string-from-scribble-modulewhere you specify your scribble module's path and the arguments you want it to have access to and it returns the processed text template as a string,command-line-serialization.rkt: helper moduletemplate-test.c.indexed.scrbl: a test template (a C program that will takes 3 arguments, in a sequence, to be accessed by index)template-test.c.dictionary.scrbl: a test template (same C program as above, but this time accesses the arguments by key)template-test.rkt: test driver to expand the previous two templates
Last active
September 11, 2026 23:22
-
-
Save david-bakin/e373c69d6405c1c65733de1965cf9ca7 to your computer and use it in GitHub Desktop.
For the Racket language: Use a `scribble/text` module as a text-template system
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #lang racket/base | |
| ;; For marshalling things through the command-line argument interface. That interface, via parameter | |
| ;; `current-command-line-arguments`, wants a vector of strings. So we have a pair of serdes routines | |
| ;; that'll round-trip (nearly) anything by transforming it first into a singleton vector holding the | |
| ;; `write` version of the datum, and then back via `read`. | |
| (require racket/contract) | |
| (provide | |
| (contract-out | |
| ;; serialize | |
| [any->vstring (-> any/c (and/c (vectorof string?) singleton? immutable?))] | |
| ;; deserialize | |
| [vstring->any (-> (and/c (vectorof string?) singleton?) any/c)])) | |
| ;; -------------------------------------------------------------------------------------------------- | |
| ;; implementation | |
| (require racket/port) | |
| ;; -------------------------------------------------------------------------------------------------- | |
| ;; private helpers | |
| (define (singleton? vec) | |
| (= 1 (vector-length vec))) | |
| ;; -------------------------------------------------------------------------------------------------- | |
| ;; provided (exported) functions | |
| ;; serialize anything to a length-1 vector holding a string (that can be round-tripped) | |
| (define/contract (any->vstring arg) | |
| (-> any/c (and/c (vectorof string?) singleton? immutable?)) | |
| (let ((s (with-output-to-string (λ () (write arg))))) | |
| (vector->immutable-vector (make-vector 1 s)))) | |
| ;; deserialize from a length-1 vector holding a string to anything | |
| (define/contract (vstring->any str) | |
| (-> (and/c (vectorof string?) singleton?) any/c) | |
| (with-input-from-string (vector-ref str 0) read)) | |
| (module+ test | |
| (require rackunit) | |
| (check-equal? (any->vstring '()) #("()")) | |
| (check-equal? (vstring->any (any->vstring '())) '()) | |
| (check-equal? (any->vstring #hash(("abc" . "x123") ("def" . "y321"))) | |
| #("#hash((\"abc\" . \"x123\") (\"def\" . \"y321\"))")) | |
| (check-equal? (vstring->any (any->vstring #hash(("abc" . "x123") ("def" . "y321")))) | |
| #hash(("abc" . "x123") ("def" . "y321")))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #lang racket/base | |
| ;; Use a scribble/text module as a text template expander. You can pass it arguments that can then | |
| ;; be used from within the module: Those arguments can be either a sequence of datums that can be | |
| ;; referenced by (0-based) index, or they can be in a hash-table (key/value store). | |
| ;; Identify the scribble/text module you want to use by some sort of module path. | |
| ;; Works with scribble-arg-processing.rkt. | |
| (require racket/contract) | |
| (provide | |
| (contract-out | |
| [generate-string-from-scribble-module | |
| (->* [(or/c module-path? resolved-module-path?)] [(or/c sequence? hash?)] string?)])) | |
| ;; -------------------------------------------------------------------------------------------------- | |
| ;; implementation | |
| (require "command-line-serialization.rkt") | |
| (require racket/port) | |
| ;; -------------------------------------------------------------------------------------------------- | |
| ;; private helpers | |
| (define-namespace-anchor ns-anchor) | |
| ;; Run a function in a new namespace that has racket/base available (since this is a new namespace | |
| ;; you can load modules in it that are already loaded in your running program (namespace) and they'll | |
| ;; get reinitialized and everything. The new namespace lasts only for the duration of this call (of | |
| ;; course). | |
| (define/contract (with-local-namespace proc) | |
| (-> (-> any) any) | |
| (let ((temp-ns (make-base-namespace)) ; racket/base attached and `require`d | |
| (src-ns (namespace-anchor->namespace ns-anchor))) ; namespace of _this_ module | |
| (parameterize ((current-namespace temp-ns)) ; now move to new racket/base namespace | |
| (proc)))) | |
| ;; -------------------------------------------------------------------------------------------------- | |
| ;; provided (exported) functions | |
| (define (generate-string-from-scribble-module module-path [command-line-args #()]) | |
| (let ((serialized-args (any->vstring command-line-args))) | |
| (with-local-namespace | |
| (λ () | |
| (with-output-to-string | |
| (λ () | |
| (parameterize ([current-command-line-arguments serialized-args]) | |
| (dynamic-require module-path #f) | |
| ))))))) | |
| ;; -------------------------------------------------------------------------------------------------- | |
| ;; Massive help from Philip McGrath (via Discord) to do the most difficult and Racket-specific part: | |
| #;(require racket/runtime-path) | |
| #;(define-runtime-module-path-index mpi "thats-c-baby-c-main-template.scrbl") | |
| #;(define/contract (generate-string [args #("a" "b" "c")]) | |
| (->* [] [(vectorof string?)] string?) | |
| (with-output-to-string | |
| (λ () | |
| (parameterize ([current-command-line-arguments args]) | |
| (dynamic-require mpi #f) | |
| )))) | |
| ;; Yet more massive help from ThePuzzlemaker's project "weave", see | |
| ;; https://codeberg.org/ThePuzzlemaker/weave/src/branch/main/private/weave.rkt | |
| ;; for how to create a namespace and attach&require modules into it (though I'm not using that | |
| ;; latter feature here). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #lang racket/base | |
| ;; In a scribble module provide a nice syntax for fetching arguments from the command line: | |
| ;; a) by index - use `@arg-n[2]` (0-based of course) | |
| ;; b) by name (assuming arguments are passed as name/value pairs) - use `@arg-kv["keyword"]` | |
| ;; Also a capability to _dump_ the command line arguments so you can see what you're doing. | |
| ;; Start each document with: | |
| ;; ``` | |
| ;; #lang scribble/text | |
| ;; @require["scribble-arg-processing.rkt"] | |
| ;; ... | |
| ;; ``` | |
| ;; (Of course all normal scribble/text facilities are still available for your template.) | |
| ;; Works with generate-string-from-scribble.rkt. | |
| (require racket/contract) | |
| (provide | |
| (contract-out | |
| ;; access argument by key name (treating arguments as a dictionary/hash-table) | |
| [arg-kv (-> string? any)] | |
| ;; access argument by (0-based) index | |
| [arg-n (-> exact-nonnegative-integer? any)] | |
| ;; dump arguments into document (debugging, typically) | |
| [arg-dump-raw (-> void?)] | |
| [arg-dump-actual (-> void?)] | |
| )) | |
| ;; -------------------------------------------------------------------------------------------------- | |
| ;; implementation | |
| (require "command-line-serialization.rkt") | |
| (require racket/sequence) | |
| ;; -------------------------------------------------------------------------------------------------- | |
| ;; private helpers | |
| (define raw-args (current-command-line-arguments)) | |
| (define actual-args 'uncached) | |
| (define (get-actual-args) | |
| (if (eq? 'uncached actual-args) | |
| (set! actual-args (vstring->any raw-args)) | |
| #t) | |
| actual-args) | |
| ;; -------------------------------------------------------------------------------------------------- | |
| ;; provided (exported) functions | |
| ;; Returns the nth actual argument (0-based) | |
| (define (arg-n n) | |
| (*arg-n n (get-actual-args))) | |
| (define/contract (*arg-n n args) | |
| (-> exact-nonnegative-integer? (or/c sequence? hash?) any/c) | |
| (cond ((not (sequence? args)) | |
| (error (format "arg-n: actual arguments are not a sequence (have arguments: ~a)" args))) | |
| ((>= n (sequence-length args)) | |
| (error (format "arg index to high: ~a (have ~a arguments: ~a)" | |
| n | |
| (sequence-length args) | |
| args))) | |
| (#t (sequence-ref args n)))) | |
| (module+ test | |
| (require rackunit) | |
| (check-exn exn:fail? (λ () (*arg-n 2 #hash(("abc"."def"))))) | |
| (check-exn exn:fail? (λ () (*arg-n 2 #("abc" "def")))) | |
| (check-equal? "abc" (*arg-n 0 #("abc" "def"))) | |
| (check-equal? "def" (*arg-n 1 '("abc" "def")))) | |
| ;; Returns the value accessed by a key, when treating the actual args as a dictionary (hash table) | |
| (define (arg-kv key) | |
| (*arg-kv key (get-actual-args))) | |
| (define/contract (*arg-kv key args) | |
| (-> any/c (or/c sequence? hash?) any/c) | |
| (cond ((not (hash? args)) | |
| (error (format "arg-kv: actual arguments are not a hash table (have arguments: ~a)" args))) | |
| (#t (hash-ref args | |
| key | |
| (λ () (error | |
| (format "arg-kv: key not found: ~a (have ~a k/v arguments: ~a)" | |
| key | |
| (hash-count args) | |
| args))))))) | |
| (module+ test | |
| (check-exn exn:fail? (λ () (*arg-kv "abc" "abcdef"))) | |
| (check-exn exn:fail? (λ () (*arg-kv "ghi" #hash(("aaa"."foo") ("bbb"."bar"))))) | |
| (check-equal? (*arg-kv "aaa" #hash(("aaa"."foo") ("bbb"."bar"))) "foo") | |
| (check-equal? (*arg-kv "bbb" #hash(("aaa"."foo") ("bbb"."bar"))) "bar")) | |
| ;; dump into the document the raw arguments straight from the environment | |
| (define (arg-dump-raw) | |
| (*arg-dump-raw (current-command-line-arguments))) | |
| (define/contract (*arg-dump-raw args) | |
| (-> any/c void?) | |
| (printf "arg-dump-raw: 『~a』\n" args)) | |
| (define (arg-dump-actual) | |
| (*arg-dump-actual (get-actual-args))) | |
| (define (*arg-dump-actual args) | |
| (printf "arg-dump-actual: 『~a』\n" args)) | |
| ;; -------------------------------------------------------------------------------------------------- |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #lang scribble/text | |
| @require["scribble-arg-processing.rkt"] | |
| /* | |
| * "can return all integer values from 0 to 767 depending ..." | |
| */ | |
| unsigned return_value(void) { | |
| unsigned m = 0; | |
| m += (((char)-1) < 0) ? 1 : 0; | |
| m += (s.f < 0) ? 2 : 0; | |
| m += (sizeof(S) < sizeof(L)) ? 4 : 0; | |
| #ifndef @arg-kv["macro-defn"] | |
| m += @arg-kv["incr-value"]; | |
| m += (sizeof("??-") != 4) ? @arg-kv["incr-value"] : 0; | |
| #endif | |
| return m; | |
| } | |
| /* | |
| * @arg-kv["comment-hdr"]: | |
| * Write a C function that returns an integer to the caller, with the | |
| * following constraints: | |
| */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #lang scribble/text | |
| @require["scribble-arg-processing.rkt"] | |
| /* | |
| * "can return all integer values from 0 to 767 depending ..." | |
| */ | |
| unsigned return_value(void) { | |
| unsigned m = 0; | |
| m += (((char)-1) < 0) ? 1 : 0; | |
| m += (s.f < 0) ? 2 : 0; | |
| m += (sizeof(S) < sizeof(L)) ? 4 : 0; | |
| #ifndef @arg-n[0] | |
| m += @arg-n[1]; | |
| m += (sizeof("??-") != 4) ? @arg-n[1] : 0; | |
| #endif | |
| return m; | |
| } | |
| /* | |
| * @arg-n[2]: | |
| * Write a C function that returns an integer to the caller, with the | |
| * following constraints: | |
| */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #lang racket | |
| (require "generate-string-from-scribble.rkt") | |
| (define test-template-indexed-module (build-path (current-directory) "template-test.c.indexed.scrbl")) | |
| (define test-template-dictionary-module (build-path (current-directory) "template-test.c.dictionary.scrbl")) | |
| (define/contract (expand-test-template-indexed arguments) | |
| (-> (or/c sequence? hash?) string?) | |
| (generate-string-from-scribble-module | |
| test-template-indexed-module | |
| arguments)) | |
| (define/contract (expand-test-template-dictionary arguments) | |
| (-> (or/c sequence? hash?) string?) | |
| (generate-string-from-scribble-module | |
| test-template-dictionary-module | |
| arguments)) | |
| (module+ test | |
| (require rackunit) | |
| (check-equal? (expand-test-template-indexed '("__STRICT_ANSI__" "1234321" "TODO follows")) | |
| (expand-test-template-dictionary | |
| #hash(("macro-defn" . "__STRICT_ANSI__") | |
| ("comment-hdr" . "TODO follows") | |
| ("incr-value" . "1234321"))))) | |
| (define/contract (print-expanded-test-templates) | |
| (-> void?) | |
| (display (expand-test-template-indexed '("__STRICT_ANSI__" "1234321" "TODO follows"))) | |
| (display "//////////////////////////////////////////////////////////////////////////////////////") | |
| (display (expand-test-template-dictionary | |
| #hash(("macro-defn" . "__STRICT_ANSI__") | |
| ("comment-hdr" . "TODO follows") | |
| ("incr-value" . "1234321"))))) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hmm. I seem to have forgotten to add any license information anywhere here. Well, consider it all SPDX MIT-0.