Racket webserver/templates include-template нельзя использовать с переменной

Я пишу небольшой блог, используя веб-сервер 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

template-path в вызове (include-template 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, Но я хочу, чтобы это было значением данной процедуры. В противном случае я не могу написать процедуру, выполняющую эту работу.

Также я хочу, чтобы значения ключевых слов, представленных в процедуре, были видны шаблону. Я не уверен, если это происходит автоматически, или если мне нужно поставить let какой-то вокруг include-template вызов, потому что я не мог заставить код для запуска еще, чтобы проверить это.

Как я могу написать такую ​​процедуру?

В качестве примера идеальной процедуры я хотел бы иметь:

  • Jinja2-х render_template

Я могу предоставить любой аргумент ключевого слова, который я хочу, и сделать любой шаблон, который я хочу отобразить. Я тоже не очень понимаю, почему в том числе что-то вроде "rm -rf /" может повредить что угодно. Мне кажется, веб-сервер должен просто проверить, существует ли файл с таким именем. Очевидно, что он не будет существовать, поэтому выведите ошибку. Как это может привести к нежелательному ущербу? Следовательно, я не понимаю причин, ограничивающих то, что можно использовать в качестве пути к шаблону, к строкам (за исключением обходных путей). Тем не менее, это может быть слишком много для одного SO вопроса и, возможно, следует поставить другой вопрос о "почему" вещей.

1 ответ

Решение

Если вы хотите подать заявку 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 и рассмотрите следующее для соответствующих прочтений:

Другие вопросы по тегам