scrubl.scm 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. ;;; Mudsync --- Live hackable MUD
  2. ;;; Copyright © 2017 Christine Lemmer-Webber <cwebber@dustycloud.org>
  3. ;;;
  4. ;;; This file is part of Mudsync.
  5. ;;;
  6. ;;; Mudsync is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or
  9. ;;; (at your option) any later version.
  10. ;;;
  11. ;;; Mudsync is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. ;;; General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with Mudsync. If not, see <http://www.gnu.org/licenses/>.
  18. ;;; SCRUBL: S-exps Craftily/Crappily Rendering Underlying Basic Language
  19. ;;; a micro-"skribe-like" system (kinda ugly tho)
  20. ;;; Turns quasiquoted structures into something rendered.
  21. ;;;
  22. ;;; This is an immutable interface but it does use mutation under the
  23. ;;; hood for expediency.
  24. ;;; To make a new scrubl that extends an existing scrubl, use the exported
  25. ;;; scrubl-extend-fields.
  26. (define-module (mudsync scrubl)
  27. #:use-module (ice-9 match)
  28. #:use-module (srfi srfi-9)
  29. #:use-module (sxml simple)
  30. #:use-module (oop goops)
  31. #:use-module (ice-9 vlist)
  32. #:use-module (ice-9 vlist)
  33. #:use-module (ice-9 hash-table)
  34. #:use-module (web uri)
  35. #:export (make-scrubl
  36. scrubl? scrubl-extend-fields
  37. scrubl-write
  38. scrubl-sxml scrubl-sxml-simple-field))
  39. (define (order-symlist-args symlist-args)
  40. "Orders the args in a symlist so keyword pairs are at the end"
  41. (define new-args
  42. (let lp ((remaining symlist-args)
  43. (args '())
  44. (kwargs '()))
  45. (match remaining
  46. ('() (cons (reverse args)
  47. kwargs))
  48. (((? keyword? kw) val rest ...)
  49. (lp rest
  50. args
  51. (cons* kw val kwargs)))
  52. ((arg rest ...)
  53. (lp rest
  54. (cons arg args)
  55. kwargs)))))
  56. new-args)
  57. (define-record-type <scrubl>
  58. (%make-scrubl field-writers meta-write)
  59. scrubl?
  60. (field-writers scrubl-field-writers)
  61. (meta-write scrubl-meta-write))
  62. (define (make-scrubl field-writers meta-write)
  63. (%make-scrubl (alist->hashq-table field-writers)
  64. meta-write))
  65. (define (scrubl-extend-fields scrubl new-field-writers)
  66. "Returns a new <scrubl> instance extending SCRUBL's field-writers with
  67. NEW-FIELD-WRITERS."
  68. (define new-writers
  69. (let ((new-table (make-hash-table)))
  70. ;; Add old fields from hashq
  71. (hash-for-each
  72. (lambda (key val)
  73. (hashq-set! new-table key val))
  74. (scrubl-field-writers scrubl))
  75. ;; Now add the new fields
  76. (for-each
  77. (match-lambda
  78. ((key . val)
  79. (hashq-set! new-table key val)))
  80. new-field-writers)
  81. new-table))
  82. (%make-scrubl new-writers (scrubl-meta-write scrubl)))
  83. (define (scrubl-write scrubl obj . args)
  84. "Write out OBJ via SCRUBL
  85. Pass in optional extra ARGS to the main META-WRITE"
  86. (apply (scrubl-meta-write scrubl) scrubl obj args))
  87. (define* (scrubl-write-obj scrubl obj)
  88. (match obj
  89. (((? symbol? sym) args ...)
  90. (let* ((field-writers (scrubl-field-writers scrubl))
  91. (field-writer (hashq-ref field-writers sym))
  92. (ordered-args (order-symlist-args args)))
  93. (when (not field-writer)
  94. (throw 'scrubl-unknown-field
  95. #:field sym
  96. #:args args))
  97. (apply field-writer scrubl ordered-args)))
  98. ((items ...)
  99. (map (lambda (item)
  100. (scrubl-write-obj scrubl item))
  101. items))
  102. (any-obj any-obj)))
  103. ;;; SXML scrubl writer
  104. (define (scrubl-sxml-write scrubl obj)
  105. (call-with-output-string
  106. (lambda (p)
  107. (sxml->xml
  108. (scrubl-write-obj scrubl obj)
  109. p))))
  110. (define (scrubl-sxml-simple-field sym)
  111. (lambda (scrubl args)
  112. ;; sxml handles inlining automatically in case we have nested
  113. ;; lists of strings, so we don't have to worry about that...
  114. (cons sym (map (lambda (arg)
  115. (scrubl-write-obj scrubl arg))
  116. args))))
  117. (define (scrubl-sxml-pre scrubl args)
  118. `(span (@ (class "pre-ish"))
  119. ,args))
  120. ;; @@: For a text-only interface, we could put links at end of rendered
  121. ;; text, similar to how orgmode does.
  122. (define (scrubl-sxml-anchor scrubl args)
  123. (define (maybe-uri->string obj)
  124. (if (uri? obj)
  125. (uri->string obj)
  126. obj))
  127. (match args
  128. (((= maybe-uri->string href) body1 body ...)
  129. `(a (@ (href ,href))
  130. ,body1 ,@body))))
  131. (define scrubl-sxml
  132. (make-scrubl `((p . ,(scrubl-sxml-simple-field 'p))
  133. (strong . ,(scrubl-sxml-simple-field 'strong))
  134. (bold . ,(scrubl-sxml-simple-field 'strong))
  135. (b . ,(scrubl-sxml-simple-field 'strong))
  136. (em . ,(scrubl-sxml-simple-field 'em))
  137. (i . ,(scrubl-sxml-simple-field 'em))
  138. (br . ,(scrubl-sxml-simple-field 'br))
  139. (anchor . ,scrubl-sxml-anchor)
  140. (a . ,scrubl-sxml-anchor)
  141. (pre . ,scrubl-sxml-pre) ;; "pre" style whitespace handling.
  142. (ul . ,(scrubl-sxml-simple-field 'ul))
  143. (li . ,(scrubl-sxml-simple-field 'li)))
  144. scrubl-sxml-write))