Implement clean URLs feature (Issue #89)

When `clean-urls?` is set in config, emit pages as subdirectories
`prefix/root/page-name/index.html` instead of
`prefix/root/page-name.html`. Links in emitted HTML then point to
`prefix/root/page-name/`. When `clean-urls?` not set, behaves as
before.

Refactor most URI generation into a new `page-uri` function.
`page-uri` replaces most calls* to `path`, all calls to `post-uri`
and all calls to the old `page-uri`.

Introduce function `create-file-recursive`. Function creates
file parent if not exists.

Introduce function `write-html`. When `clean-urls?` is set, spits
emitted HTML into subdirectories as described above; otherwise
behaves like `create-file`. Replaces most* calls to `create-file`
Calls `create-file` or `create-file-recursive`.

* Exceptions made for sitemap XML and RSS feed XML pages
This commit is contained in:
Tom L 2016-02-11 20:04:56 -06:00
parent b3bdba2804
commit c18c3d60f2
2 changed files with 104 additions and 83 deletions

View file

@ -11,7 +11,7 @@
[cryogen-core.sass :as sass] [cryogen-core.sass :as sass]
[cryogen-core.markup :as m] [cryogen-core.markup :as m]
[cryogen-core.io :refer [cryogen-core.io :refer
[get-resource find-assets create-folder create-file wipe-public-folder [get-resource find-assets create-folder create-file-recursive create-file wipe-public-folder
copy-resources copy-resources-from-theme path]] copy-resources copy-resources-from-theme path]]
[cryogen-core.sitemap :as sitemap] [cryogen-core.sitemap :as sitemap]
[cryogen-core.rss :as rss]) [cryogen-core.rss :as rss])
@ -47,15 +47,14 @@
(let [fmt (java.text.SimpleDateFormat. date-fmt)] (let [fmt (java.text.SimpleDateFormat. date-fmt)]
(.parse fmt (.substring file-name 0 10)))) (.parse fmt (.substring file-name 0 10))))
(defn post-uri
"Creates a post uri from the post file name"
[file-name {:keys [blog-prefix post-root-uri]} mu]
(path "/" blog-prefix post-root-uri (s/replace file-name (re-pattern-from-ext (m/ext mu)) ".html")))
(defn page-uri (defn page-uri
"Creates a page uri from the page file name" "Creates a URI from file name. `uri-type` is any of the uri types specified in config, e.g., `:post-root-uri`."
[page-name {:keys [blog-prefix page-root-uri]} mu] ([file-name params]
(path "/" blog-prefix page-root-uri (s/replace page-name (re-pattern-from-ext (m/ext mu)) ".html"))) (page-uri file-name nil params))
([file-name uri-type {:keys [blog-prefix clean-urls?] :as params}]
(let [page-uri (params uri-type)
uri-end (if clean-urls? (s/replace file-name #"(index)?\.html" "/") file-name)]
(path "/" blog-prefix page-uri uri-end))))
(defn read-page-meta (defn read-page-meta
"Returns the clojure map from the top of a markdown page/post" "Returns the clojure map from the top of a markdown page/post"
@ -94,7 +93,7 @@
(let [{:keys [file-name page-meta content]} (page-content page config markup)] (let [{:keys [file-name page-meta content]} (page-content page config markup)]
(merge (merge
(merge-meta-and-content file-name page-meta content) (merge-meta-and-content file-name page-meta content)
{:uri (page-uri file-name config markup) {:uri (page-uri file-name :page-root-uri config)
:page-index (:page-index page-meta)}))) :page-index (:page-index page-meta)})))
(defn parse-post (defn parse-post
@ -111,7 +110,7 @@
{:date date {:date date
:formatted-archive-group formatted-group :formatted-archive-group formatted-group
:parsed-archive-group (.parse archive-fmt formatted-group) :parsed-archive-group (.parse archive-fmt formatted-group)
:uri (post-uri file-name config markup) :uri (page-uri file-name :post-root-uri config)
:tags (set (:tags page-meta))})))) :tags (set (:tags page-meta))}))))
(defn read-posts (defn read-posts
@ -178,9 +177,9 @@
(defn tag-info (defn tag-info
"Returns a map containing the name and uri of the specified tag" "Returns a map containing the name and uri of the specified tag"
[{:keys [blog-prefix tag-root-uri]} tag] [config tag]
{:name (name tag) {:name (name tag)
:uri (path "/" blog-prefix tag-root-uri (str (name tag) ".html"))}) :uri (page-uri (str (name tag) ".html") :tag-root-uri config)})
(defn add-prev-next (defn add-prev-next
"Adds a :prev and :next key to the page/post data containing the title and uri of the prev/next "Adds a :prev and :next key to the page/post data containing the title and uri of the prev/next
@ -199,6 +198,13 @@
sidebar-pages false} (group-by #(boolean (:navbar? %)) pages)] sidebar-pages false} (group-by #(boolean (:navbar? %)) pages)]
(map (partial sort-by :page-index) [navbar-pages sidebar-pages]))) (map (partial sort-by :page-index) [navbar-pages sidebar-pages])))
(defn write-html
"When `clean-urls?` is set, appends `/index.html` before spit; otherwise just spits."
[file-uri {:keys [clean-urls?]} data]
(if clean-urls?
(create-file-recursive (path file-uri "index.html") data)
(create-file file-uri data)))
(defn compile-pages (defn compile-pages
"Compiles all the pages into html and spits them out into the public folder" "Compiles all the pages into html and spits them out into the public folder"
[{:keys [blog-prefix page-root-uri] :as params} pages] [{:keys [blog-prefix page-root-uri] :as params} pages]
@ -207,13 +213,14 @@
(create-folder (path "/" blog-prefix page-root-uri)) (create-folder (path "/" blog-prefix page-root-uri))
(doseq [{:keys [uri] :as page} pages] (doseq [{:keys [uri] :as page} pages]
(println "\t-->" (cyan uri)) (println "\t-->" (cyan uri))
(create-file uri (write-html uri
(render-file (str "/html/" (:layout page)) params
(merge params (render-file (str "/html/" (:layout page))
{:active-page "pages" (merge params
:servlet-context (path "/" blog-prefix "/") {:active-page "pages"
:page page :servlet-context (path "/" blog-prefix "/")
:uri uri})))))) :page page
:uri uri}))))))
(defn compile-posts (defn compile-posts
"Compiles all the posts into html and spits them out into the public folder" "Compiles all the posts into html and spits them out into the public folder"
@ -223,14 +230,15 @@
(create-folder (path "/" blog-prefix post-root-uri)) (create-folder (path "/" blog-prefix post-root-uri))
(doseq [post posts] (doseq [post posts]
(println "\t-->" (cyan (:uri post))) (println "\t-->" (cyan (:uri post)))
(create-file (:uri post) (write-html (:uri post)
(render-file (str "/html/" (:layout post)) params
(merge params (render-file (str "/html/" (:layout post))
{:active-page "posts" (merge params
:servlet-context (path "/" blog-prefix "/") {:active-page "posts"
:post post :servlet-context (path "/" blog-prefix "/")
:disqus-shortname disqus-shortname :post post
:uri (:uri post)})))))) :disqus-shortname disqus-shortname
:uri (:uri post)}))))))
(defn compile-tags (defn compile-tags
"Compiles all the tag pages into html and spits them out into the public folder" "Compiles all the tag pages into html and spits them out into the public folder"
@ -241,22 +249,25 @@
(doseq [[tag posts] posts-by-tag] (doseq [[tag posts] posts-by-tag]
(let [{:keys [name uri]} (tag-info params tag)] (let [{:keys [name uri]} (tag-info params tag)]
(println "\t-->" (cyan uri)) (println "\t-->" (cyan uri))
(create-file uri (write-html uri
(render-file "/html/tag.html" params
(merge params (render-file "/html/tag.html"
{:active-page "tags" (merge params
:servlet-context (path "/" blog-prefix "/") {:active-page "tags"
:name name :servlet-context (path "/" blog-prefix "/")
:posts posts :name name
:uri uri}))))))) :posts posts
:uri uri})))))))
(defn compile-tags-page [{:keys [blog-prefix] :as params}] (defn compile-tags-page [{:keys [blog-prefix] :as params}]
(println (blue "compiling tags page")) (println (blue "compiling tags page"))
(create-file (path "/" blog-prefix "tags.html") (let [uri (page-uri "tags.html" params)]
(render-file "/html/tags.html" (write-html uri
(merge params params
{:active-page "tags" (render-file "/html/tags.html"
:uri (path "/" blog-prefix "tags.html")})))) (merge params
{:active-page "tags"
:uri uri})))))
(defn content-until-more-marker (defn content-until-more-marker
[^String content] [^String content]
@ -288,11 +299,11 @@
(defn create-preview-links (defn create-preview-links
"Turn each vector of previews into a map with :prev and :next keys that contain the uri of the "Turn each vector of previews into a map with :prev and :next keys that contain the uri of the
prev/next preview page" prev/next preview page"
[previews blog-prefix] [previews params]
(mapv (fn [[prev target next]] (mapv (fn [[prev target next]]
(merge target (merge target
{:prev (if prev (path "/" blog-prefix "p" (str (:index prev) ".html")) nil) {:prev (if prev (page-uri (path "p" (str (:index prev) ".html")) params) nil)
:next (if next (path "/" blog-prefix "p" (str (:index next) ".html")) nil)})) :next (if next (page-uri (path "p" (str (:index next) ".html")) params) nil)}))
(partition 3 1 (flatten [nil previews nil])))) (partition 3 1 (flatten [nil previews nil]))))
(defn compile-preview-pages (defn compile-preview-pages
@ -300,45 +311,50 @@
[{:keys [blog-prefix posts-per-page blocks-per-preview] :as params} posts] [{:keys [blog-prefix posts-per-page blocks-per-preview] :as params} posts]
(when-not (empty? posts) (when-not (empty? posts)
(let [previews (-> (create-previews posts-per-page blocks-per-preview posts) (let [previews (-> (create-previews posts-per-page blocks-per-preview posts)
(create-preview-links blog-prefix)) (create-preview-links params))
previews (if (> (count previews) 1) (assoc-in previews [1 :prev] (path "/" blog-prefix "index.html")) previews)] previews (if (> (count previews) 1) (assoc-in previews [1 :prev] (page-uri "index.html" params)) previews)]
(create-folder (path "/" blog-prefix "p")) (create-folder (path "/" blog-prefix "p"))
(doseq [{:keys [index posts prev next]} previews (doseq [{:keys [index posts prev next]} previews
:let [index-page? (= 1 index)]] :let [index-page? (= 1 index)]]
(create-file (if index-page? (path "/" blog-prefix "index.html") (path "/" blog-prefix "p" (str index ".html"))) (write-html (if index-page? (page-uri "index.html" params) (page-uri (path "p" (str index ".html")) params))
(render-file "/html/previews.html" params
(merge params (render-file "/html/previews.html"
{:active-page "preview" (merge params
:home (when index-page? true) {:active-page "preview"
:servlet-context (path "/" blog-prefix "/") :home (when index-page? true)
:posts posts :servlet-context (path "/" blog-prefix "/")
:prev-uri prev :posts posts
:next-uri next}))))))) :prev-uri prev
:next-uri next})))))))
(defn compile-index (defn compile-index
"Compiles the index page into html and spits it out into the public folder" "Compiles the index page into html and spits it out into the public folder"
[{:keys [blog-prefix disqus?] :as params}] [{:keys [blog-prefix disqus?] :as params}]
(println (blue "compiling index")) (println (blue "compiling index"))
(create-file (path "/" blog-prefix "index.html") (let [uri (page-uri "index.html" params)]
(render-file "/html/home.html" (write-html uri
(merge params params
{:active-page "home" (render-file "/html/home.html"
:home true (merge params
:disqus? disqus? {:active-page "home"
:post (get-in params [:latest-posts 0]) :home true
:uri (path "/" blog-prefix "index.html")})))) :disqus? disqus?
:post (get-in params [:latest-posts 0])
:uri uri})))))
(defn compile-archives (defn compile-archives
"Compiles the archives page into html and spits it out into the public folder" "Compiles the archives page into html and spits it out into the public folder"
[{:keys [blog-prefix] :as params} posts] [{:keys [blog-prefix] :as params} posts]
(println (blue "compiling archives")) (println (blue "compiling archives"))
(create-file (path "/" blog-prefix "archives.html") (let [uri (page-uri "archives.html" params)]
(render-file "/html/archives.html" (write-html uri
(merge params params
{:active-page "archives" (render-file "/html/archives.html"
:archives true (merge params
:groups (group-for-archive posts) {:active-page "archives"
:uri (path "/" blog-prefix "/archives.html")})))) :archives true
:groups (group-for-archive posts)
:uri uri})))))
(defn compile-authors (defn compile-authors
"For each author, creates a page with filtered posts." "For each author, creates a page with filtered posts."
@ -347,15 +363,16 @@
(create-folder (path "/" blog-prefix author-root-uri)) (create-folder (path "/" blog-prefix author-root-uri))
;; if the post author is empty defaults to config's :author ;; if the post author is empty defaults to config's :author
(doseq [{:keys [author posts]} (group-for-author posts author)] (doseq [{:keys [author posts]} (group-for-author posts author)]
(let [uri (path "/" blog-prefix author-root-uri (str author ".html"))] (let [uri (page-uri (str author ".html") :author-root-uri params)]
(println "\t-->" (cyan uri)) (println "\t-->" (cyan uri))
(create-file uri (write-html uri
(render-file "/html/author.html" params
(merge params (render-file "/html/author.html"
{:author author (merge params
:groups (group-for-archive posts) {:author author
:servlet-context (path "/" blog-prefix "/") :groups (group-for-archive posts)
:uri uri})))))) :servlet-context (path "/" blog-prefix "/")
:uri uri}))))))
(defn tag-posts (defn tag-posts
"Converts the tags in each post into links" "Converts the tags in each post into links"
@ -414,9 +431,9 @@
:latest-posts (->> posts (take recent-posts) vec) :latest-posts (->> posts (take recent-posts) vec)
:navbar-pages navbar-pages :navbar-pages navbar-pages
:sidebar-pages sidebar-pages :sidebar-pages sidebar-pages
:archives-uri (path "/" blog-prefix "archives.html") :archives-uri (page-uri "archives.html" config)
:index-uri (path "/" blog-prefix "index.html") :index-uri (page-uri "index.html" config)
:tags-uri (path "/" blog-prefix "tags.html") :tags-uri (page-uri "tags.html" config)
:rss-uri (path "/" blog-prefix rss-name) :rss-uri (path "/" blog-prefix rss-name)
:site-url (if (.endsWith site-url "/") (.substring site-url 0 (dec (count site-url))) site-url) :site-url (if (.endsWith site-url "/") (.substring site-url 0 (dec (count site-url))) site-url)
:theme-path (str "file:resources/templates/themes/" (:theme config))})] :theme-path (str "file:resources/templates/themes/" (:theme config))})]

View file

@ -45,12 +45,16 @@
[])) []))
(defn create-folder [folder] (defn create-folder [folder]
(let [loc (io/file (str public folder))] (let [loc (io/file (path public folder))]
(when-not (.exists loc) (when-not (.exists loc)
(.mkdirs loc)))) (.mkdirs loc))))
(defn create-file [file data] (defn create-file [file data]
(spit (str public file) data)) (spit (path public file) data))
(defn create-file-recursive [file data]
(create-folder (.getParent (io/file file)))
(create-file file data))
(defn wipe-public-folder [keep-files] (defn wipe-public-folder [keep-files]
(let [filenamefilter (reify java.io.FilenameFilter (accept [this _ filename] (not (some #{filename} keep-files))))] (let [filenamefilter (reify java.io.FilenameFilter (accept [this _ filename] (not (some #{filename} keep-files))))]