Racket webserver/templates include-template 不能与变量一起使用

Racket webserver/templates include-template cannot be used with variable

我正在使用 Racket 网络服务器写一个小博客(需要 web-server/templates, web-server/servlet-env, web-server/servlet, web-server/dispatch)。每当我想渲染模板时,我都会这样做:

(define (render-homeworks-overview-page)
  (let
    ([dates
       (sort
         (get-all-homework-dates)
         #:key my-date->string
         string<?)])
    (include-template "templates/homework-overview.html")))

定义一个小程序,为模板提供所有必要的值,在本例中为 dates,然后在模板内部使用。到目前为止效果很好,但我想也许我可以摆脱所有这些渲染过程中的 let,方法是将它一次放入一个更抽象的 render-template 过程中,然后由所有渲染器调用程序。或者,对这个更抽象的过程的调用可以变得如此简单,以至于我不再需要所有的小渲染过程。我想提供值作为关键字参数,到目前为止我得到了以下代码:

(define render-template
  (make-keyword-procedure
    (lambda
      (keywords keyword-args [content "<p>no content!</p>"] [template-path "template/base.html"])
      (let
        ([content content])
        (include-template template-path)))))

这将为模板中显示的内容提供默认值,并为模板呈现和采用任意关键字参数提供默认路径,以便任何呈现过程都可以提供模板所需的任何内容,方法是将其指定为一个关键字。

但是,我不能运行这段代码,因为有一个错误:

include-at/relative-to/reader: not a pathname string, `file' form, or `lib' form for file

调用 (include-template template-path) 中的 template-path 带有红色下划线,表示存在错误。但是,当我用这样的普通字符串替换 template-path 时:

(define render-template
  (make-keyword-procedure
    (lambda
      (keywords keyword-args [content "<p>no content!</p>"] [template-path "template/base.html"])
      (let
        ([content content])
        (include-template "templates/base.html")))))

错误没有发生。似乎 Racket 以某种方式想要确保 include-template 有一个有效的路径。但我希望这是赋予程序的价值。否则我无法编写一个程序来完成这项工作。

我还希望提供给过程的关键字的值对模板可见。我不确定,如果这是自动发生的情况,或者我是否需要在 include-template 调用周围放置某种 let,因为我还无法获得 运行 的代码, 对此进行测试。

如何编写这样的程序?

作为我想要的理想程序的示例:

我可以提供我希望的任何关键字参数并呈现我希望呈现的任何模板。我也不太明白,为什么包含 "rm -rf /" 之类的东西会损坏任何东西。对我来说,网络服务器似乎应该简单地检查是否存在具有该名称的文件。显然不会存在,所以抛出错误。这怎么会导致任何不必要的损害?因此,我不明白将可用作模板路径的内容限制为字符串的原因(解决方法除外)。但是,这对于一个 SO 问题来说可能太多了,应该放在另一个关于 "why" 的问题中。

如果你想应用 include-template 变量 路径参数,你可以将渲染过程定义为:

(define (dynamic-include-template path)
  (eval #`(include-template #,path)))

该过程将任何模板路径作为参数并包含该模板。例如,(dynamic-include-template "static.html") 将呈现 static.html.

这可以扩展为接受任意数量的关键字并使它们在呈现的模板中可用,如下所示:

(define render-template
  (make-keyword-procedure
   (lambda (kws kw-args
                [path "templates/base.html"]
                [content "<p>no content!</p>"])
     (for ([i kws]
           [j kw-args])
       (namespace-set-variable-value!
        (string->symbol (keyword->string i)) j))
     (namespace-set-variable-value! 'content content)
     (dynamic-include-template path))))

在这里,在 for 块内,使用 namespace-set-variable-value! 在命名空间的顶级环境中设置具有新标识符的关键字值,这样对于关键字和值参数,如 (render-template ... #:foo 'bar),模板可用的相应标识符变为 foo(其 @ Syntax@foo),其值变为 bar.

例如,要渲染作业概览模板,您可以这样做:

(render-template "templates/homework-overview.html"
                 #:dates (sort (get-all-homework-dates) string<?))

那么在 templates/homework-overview.html 里面你会得到:

...
@dates
...

但是,在使用 eval 时要小心,并考虑以下相关阅读: