    _ filterer FILTER   code "predicate to use for selecting entries (default: `:content`)"
    r renderer RENDERER sym  "page renderer (fully qualified symbol which resolves to a function)"
    m meta     META     edn  "metadata to set on each entry"]
-  (let [tmp     (boot/tmp-dir!)
-        options (merge +render-defaults+ *opts*)]
-    (boot/with-pre-wrap fileset
-      (pod/with-call-in @render-pod
-        (io.perun.render/update!))
-      (let [files (filter (:filterer options) (perun/get-meta fileset))
-            updated-files (doall
-                           (for [{:keys [path] :as file} files]
-                             (let [entry         (merge meta file)
+  (let [options (merge +render-defaults+ *opts*)]
+    (letfn [(render-paths [fileset options]
+              (let [entries (filter (:filterer options) (perun/get-meta fileset))
+                    paths (reduce
+                           (fn [result {:keys [path] :as entry*}]
+                             (let [entry (merge meta entry*)
                                    render-data   {:meta    (perun/get-global-meta fileset)
-                                                  :entries (vec files)
+                                                  :entries (vec entries)
                                                   :entry   entry}
-                                   html          (render-in-pod @render-pod renderer render-data)
                                    page-filepath (perun/create-filepath
                                                   (:out-dir options)
                                                   ; If permalink ends in slash, append index.html as filename
-                                                  (or (some-> (:permalink file)
+                                                  (or (some-> (:permalink entry)
                                                               (string/replace #"/$" "/index.html")
                                                               perun/url-to-path)
                                                       (string/replace path #"(?i).[a-z]+$" ".html")))]
                                (perun/report-debug "render" "rendered page for path" path)
-                               (perun/create-file tmp page-filepath html)
-                               entry)))]
-        (perun/report-info "render" "rendered %s pages" (count files))
-        (perun/merge-meta (commit fileset tmp) updated-files)))))
+                               (assoc result page-filepath {:render-data render-data
+                                                            :entry       entry})))
+                           {}
+                           entries)]
+                (perun/report-info "render" "rendered %s pages" (count paths))
+                paths))]
+      (render-pre-wrap render-paths options :io.perun/render))))
+
+(defn- grouped-paths
+  "Produces path maps of the shape required by `render-to-paths`, based
+  on the provided `fileset` and `options`."
+  [task-name fileset options]
+  (let [global-meta (perun/get-global-meta fileset)
+        grouper (:grouper options)]
+    (->> fileset
+         perun/get-meta
+         (filter (:filterer options))
+         grouper
+         (reduce
+          (fn [result [path {:keys [entries group-meta]}]]
+            (let [sorted        (sort-by (:sortby options) (:comparator options) entries)
+                  page-filepath (perun/create-filepath (:out-dir options) path)
+                  new-entry     (merge {:path          page-filepath
+                                        :canonical-url (str (:base-url global-meta) path)
+                                        :date-build    (:date-build global-meta)}
+                                       group-meta)
+                  render-data   {:meta    global-meta
+                                 :entry   new-entry
+                                 :entries (vec sorted)}]
+              (perun/report-info task-name (str "rendered " task-name " " path))
+              (assoc result page-filepath {:render-data render-data
+                                           :entry       new-entry})))
+          {}))))
 
 (def ^:private +collection-defaults+
   {:out-dir "public"
    :filterer :content
-   :groupby (fn [data] "index.html")
    :sortby (fn [file] (:date-published file))
    :comparator (fn [i1 i2] (compare i2 i1))})
 

   c comparator COMPARATOR code "sort by comparator function"
   p page       PAGE       str  "collection result page path"
   m meta       META       edn  "metadata to set on each collection entry"]
  (let [tmp       (boot/tmp-dir!)
        options   (merge +collection-defaults+ *opts* (if-let [p (:page *opts*)]
                                                        {:groupby (fn [_] p)}))]
    (cond (not (fn? (:comparator options)))
          (u/fail "collection task :comparator option should implement IFn\n")
          (not (ifn? (:filterer options)))
          (u/fail "collection task :filterer option value should implement IFn\n")
          (and (:page options) (:groupby *opts*))
          (u/fail "using the :page option will render any :groupby option setting effectless\n")
          (not (ifn? (:groupby options)))
          (u/fail "collection task :groupby option value should implement IFn\n")
          (not (ifn? (:sortby options)))
          (u/fail "collection task :sortby option value should implement IFn\n")
          :else
            (boot/with-pre-wrap fileset
              (pod/with-call-in @render-pod
                (io.perun.render/update!))

              (let [files          (perun/get-meta fileset)
                    filtered-files (filter (:filterer options) files)
                    grouped-files  (group-by (:groupby options) filtered-files)
                    global-meta    (perun/get-global-meta fileset)
                    new-files      (doall
                                    (map
                                      (fn [[page page-files]]
                                        (do
                                          (let [sorted        (sort-by (:sortby options) (:comparator options) page-files)
                                                render-data   {:meta    global-meta
                                                               :entries (vec sorted)}
                                                html          (render-in-pod @render-pod renderer render-data)
                                                page-filepath (perun/create-filepath (:out-dir options) page)
                                                new-entry     (merge
                                                               meta
                                                               {:path page-filepath
                                                                :canonical-url (str (:base-url global-meta) page)
                                                                :content html
                                                                :date-build (:date-build global-meta)})]
                                            (perun/create-file tmp page-filepath html)
                                            (perun/report-info "collection" "rendered collection %s" page)
                                            new-entry)))
                                      grouped-files))
                    updated-files    (apply conj files (trace :io.perun/collection new-files))
                    updated-fileset  (perun/set-meta fileset updated-files)]
                  (commit updated-fileset tmp))))))

(deftask inject-scripts
  "Inject JavaScript scripts into html files.


(def render-pod (delay (create-pod' render-deps)))

(defn render-to-paths
  "Renders paths in `data`, using `renderer` in `pod`, and writes
  the result to `tmp`.

  `data` should be a map with keys that are fileset paths, and
  values that are themselves maps with these keys:
   - `:render-data` the map argument that `renderer` will be called with
   - `:entry` the metadata for the item being rendered

  All `:entry`s will be returned, after having their `:content` set to the
  rendering result"
  [data renderer tmp tracer]
  (pod/with-call-in @render-pod
    (io.perun.render/update!))
  (doall
   (trace tracer
          (for [[path {:keys [render-data entry]}] data]
            (let [content (render-in-pod @render-pod renderer render-data)]
              (perun/create-file tmp path content)
              (assoc entry :content content))))))

(defn render-pre-wrap
  "Handles common rendering task orchestration

  `render-paths-fn` takes two arguments: a fileset, and a map of task options.
  `options` is a map that must have a `:renderer` key, and any other keys
  that are required by `render-paths-fn`.

  Returns a boot `with-pre-wrap` result"
  [render-paths-fn options tracer]
  (let [tmp  (boot/tmp-dir!)]
    (boot/with-pre-wrap fileset
      (let [new-metadata (-> fileset
                             (render-paths-fn options)
                             (render-to-paths (:renderer options) tmp tracer))]
        (-> fileset
            (perun/merge-meta new-metadata)
            (commit tmp))))))

(deftask render
  "Render individual pages for entries in perun data.

