~subsetpark/bagatto

f6126c8f000a2a45ec2d4397414f07ea241ed478 — Zach Smith 9 months ago 7ffceb3
Do some error tracking
M bagatto.janet => bagatto.janet +34 -2
@@ 10,6 10,11 @@
(import src/writers)
(import src/threads)

(defn- set-error-context!
  [k v]
  (let [error-context (dyn :error-context)
        updated (merge error-context @{k v})]
    (setdyn :error-context updated)))
#
# API
#


@@ 183,13 188,16 @@
  See the [Temple](https://git.sr.ht/~bakpakin/temple) site for more details.
  ```
  [template site &opt item]

  (def render-context @{:current-template template})
  (set-error-context! :render-context render-context)
  
  (comptime (def- bufsize (core-* 12 1024)))
  (let [res (buffer/new bufsize)
        env (require template)
        render-dict ((env 'render-dict) :value)
        attrs (merge site {:_item item})]
    
    (with-dyns [:out res]
    (with-dyns [:out res :cxt render-context]
      (render-dict attrs))
    res))



@@ 399,6 407,30 @@
# TEMPLATE
#

(defmacro include
  ````
  Require and render the named template.

  In addition to being a convenient wrapper around

  ```
  {$ (import foo) $}
  {% (foo/render-dict args) %}
  ```

  `include` will update the error-tracking context with the name of
  the included template. This makes it much easier to trace rendering
  errors in nested templates.

  ````
  [template]
  ~(do
     # Write to the template context to track currently rendered template
     (put (dyn :cxt) :current-template ,template)
     (let [env (require ,template)
           render-dict ((env 'render-dict) :value)]
       (render-dict args))))

(defn markdown->html
  "Render a markdown string into HTML."
  [md]

M demo/templates/post.temple => demo/templates/post.temple +2 -4
@@ 1,5 1,4 @@
{$ (import ./base_top) $}
{% (base_top/render-dict args) %}
{% bagatto/include "templates/base_top" %}
      
      <h1>{{ (get-in args [:_item :title]) }}</h1>
      <p class="post-info">


@@ 7,5 6,4 @@
      </p>
      {- (bagatto/mmarkdown->html (get-in args [:_item :contents])) -}

{$ (import ./base_bottom :as base_bottom) $}
{% (base_bottom/render-dict args) %}
{% bagatto/include "templates/base_bottom" %}

M demo/templates/posts.temple => demo/templates/posts.temple +2 -4
@@ 1,5 1,4 @@
{$ (import ./base_top) $}
{% (base_top/render-dict args) %}
{% (bagatto/include "templates/base_top") %}

<h2>{{ (get-in args [:config-json "subtitle"]) }}</h2>
<h3>Author: {{ (get-in args [:config-file :author]) }}</h3>


@@ 41,5 40,4 @@
                   [(post :title)]))))) %}
</ul>

{$ (import ./base_bottom :as base_bottom) $}
{% (base_bottom/render-dict args) %}
{% (bagatto/include "templates/base_bottom") %}

M src/core.janet => src/core.janet +25 -13
@@ 8,12 8,17 @@
(defn- struct->table [s]
  (->> (or s @{}) (kvs) (splice) (table)))

(defn- maybe-apply [f args args-type ret-type]
  (if (function? f)
    (try
      (f ;args)
      ([err fib] (error/path-content-error err f args-type ret-type)))
    f))
(defn- maybe-apply [f args]
  (if (function? f) (f ;args) f))

(defn- apply-path [f args args-type]
  (try (maybe-apply f args)
       ([err fib] (error/path-error err f args-type))))

(defn- apply-renderer [f args args-type]
  (try (maybe-apply f args)
       ([err fib]
        (error/renderer-error err f args-type))))

(defn- set-defaults [spec]
  (table/setproto (struct->table spec)


@@ 44,6 49,9 @@
  
  (let [data-pairs (pairs data-spec)
        jobs (seq [[spec-name spec] :in data-pairs]
                        
                  (setdyn :error-context {:spec-name spec-name})

                  (let [with-defaults (set-defaults spec)
                        transform-f (or (spec :transform) identity)]
                    (match with-defaults


@@ 72,10 80,13 @@
  (defn push-writer [type path contents]
    (array/push writers [type path contents]))
  
  (loop [[_spec-name spec] :pairs site]
  (loop [[spec-name spec] :pairs site]
    (let [with-defaults (set-defaults spec)
          filter (spec :filter)]
      (default filter (fn [_site _item] true))
      
      (setdyn :error-context {:spec-name spec-name})
      
      (match with-defaults
        
        {:each site-selector


@@ 83,8 94,8 @@
         :out renderer}
        (loop [item :in (data site-selector)]
          (if-let [_should-read (filter data item)
                   path (maybe-apply path-generator [data item] :di :p)
                   contents (maybe-apply renderer [data item] :di :c)]
                   path (apply-path path-generator [data item] :di)
                   contents (apply-renderer renderer [data item] :di)]
            (push-writer :write path contents)))
        
        {:each site-selector


@@ 92,20 103,21 @@
        (loop [item :in (data site-selector)]
          (if-let [_should-read (filter data item)
                   from (item :path)
                   to (maybe-apply path-generator [data item] :di :p)]
                   to (apply-path path-generator [data item] :di)]
            (push-writer :copy from to)))
        
        {:dest path-generator
         :out renderer}
        (if-let [path (maybe-apply path-generator [data] :d :p)
                 contents (maybe-apply renderer [data] :d :c)]
        (if-let [path (apply-path path-generator [data] :d)
                 contents (apply-renderer renderer [data] :d)]
          (push-writer :write path contents))
        
        # TODO: When is this necessary? 
        {:some site-selector
         :dest path-generator}
        (if-let [item (data site-selector)
                 from (item :path)
                 to (maybe-apply path-generator [data] :d :p)]
                 to (apply-path path-generator [data] :d)]
          (push-writer :copy from to))

        _ (error/site-error with-defaults)))) 

M src/error.janet => src/error.janet +74 -20
@@ 1,24 1,76 @@
(defn- f-name [f] (or (disasm f :name) "f"))

(defn- error-context [k] ((dyn :error-context) k))
(defn- spec-name [] (string (error-context :spec-name)))

(defn- format-renderer-context
  []
  (let [cxt (error-context :render-context)]
    (if (any? [cxt])
      (string/format
       ```
       
Current render context:
%q
       ```
       cxt)
      "")))

(defn eval-error
  [sym filename]
  (error
   (string "Expected symbol " sym "; couldn't be found after evaluating " filename)))

(defn path-content-error
  [err f args-type ret-type]
  (error (string/format
          "%s;\nExpected %s function signature:\n(defn %s %s\n %s)"
          err
          (case ret-type :p "dest" :c "out")
          (f-name f)
          (case args-type :d "[data]" :di "[data item]")
          (case ret-type :p `"foo/bar/..."` :c `"<html>..."`))))
(defn path-error
  [err f args-type]
  (let [err (string/format
             ```

Encountered error generating path for site spec %s:
             
%s.

Expected path function signature:
(defn %s %s "foo/bar/...")
             ```
             (spec-name)
             err
             (f-name f)
             (case args-type :d "[data]" :di "[data item]"))]
    (error err)))

(defn renderer-error
  [err f args-type]
  (let [err (string/format
             ```

Encountered error rendering output for site spec %s:

%s.
%s
Expected renderer function signature:
(defn %s %s "<html>...")
             ```
             (spec-name)
             err
             (format-renderer-context)
             (f-name f)
             (case args-type :d "[data]" :di "[data item]"))]
    (error err)))

(defn attrs-error
  [err attrs-f]
  (error (string/format
          "%s;\nRequired parse function signature:\n(defn %s [src attrs]\n ...\n attrs)"
          ```

Encountered error getting attrs for data spec %s:

%s.

Expected parse function signature:
(defn %s [src attrs] attrs)
          ```
          (spec-name)
          err
          (or (disasm attrs-f :name) "f"))))



@@ 26,11 78,12 @@
  [spec]
  (error (string/format
          ```
          Received invalid data spec: %q

          Specification can be one of the following:
          {:src (loader|path) :attrs parser)}
          {:attrs attrs}
Received invalid data spec: %q

Specification can be one of the following:
{:src (loader|path) :attrs parser)}
{:attrs attrs}
          ```
          spec)))



@@ 38,12 91,13 @@
  [spec]
  (error (string/format
          ```
          Received invalid site spec: %q

          Specification can be one of the following:
          {:each site-selector :dest path-generator :out renderer} (write)
          {:each site-selector :dest path-generator} (copy)
          {:dest (path|path-generator) :out (contents|renderer)} (write)
          {:some site-selector :dest (path|path-generator)} (copy)
Received invalid site spec: %q

Specification can be one of the following:
{:each site-selector :dest path-generator :out renderer} (write)
{:each site-selector :dest path-generator} (copy)
{:dest (path|path-generator) :out (contents|renderer)} (write)
{:some site-selector :dest (path|path-generator)} (copy)
          ```
          spec)))

M src/writers.janet => src/writers.janet +9 -0
@@ 1,6 1,15 @@
(import path)
(import src/util)

(defn- print
  ```
  Mimic the behaviour of `print`, but concatenate the newline to the
  string and then write, rather than writing both in sequence. Ensures
  that threaded output won 't be interleaved.
  ```
  [s]
  (prin (string s "\n")))

(defn handle-writes
  [output-dir]
  (fn [msg]