commit 410d9ce298074ba4061e8b154c40ba772a63b7aa Author: lacarmen Date: Thu Dec 4 11:38:48 2014 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36d76e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/target +/classes +/checkouts +pom.xml +pom.xml.asc +*.jar +*.class +/.lein-* +/.nrepl-port +.DS* \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7689f30 --- /dev/null +++ b/LICENSE @@ -0,0 +1,214 @@ +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and +documentation distributed under this Agreement, and + +b) in the case of each subsequent Contributor: + +i) changes to the Program, and + +ii) additions to the Program; + +where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from +a Contributor if it was added to the Program by such Contributor itself or +anyone acting on such Contributor's behalf. Contributions do not include +additions to the Program which: (i) are separate modules of software +distributed in conjunction with the Program under their own license +agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly perform, +distribute and sublicense the Contribution of such Contributor, if any, and +such derivative works, in source code and object code form. + +b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under +Licensed Patents to make, use, sell, offer to sell, import and otherwise +transfer the Contribution of such Contributor, if any, in source code and +object code form. This patent license shall apply to the combination of the +Contribution and the Program if, at the time the Contribution is added by the +Contributor, such addition of the Contribution causes such combination to be +covered by the Licensed Patents. The patent license shall not apply to any +other combinations which include the Contribution. No hardware per se is +licensed hereunder. + +c) Recipient understands that although each Contributor grants the licenses +to its Contributions set forth herein, no assurances are provided by any +Contributor that the Program does not infringe the patent or other +intellectual property rights of any other entity. Each Contributor disclaims +any liability to Recipient for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a condition to +exercising the rights and licenses granted hereunder, each Recipient hereby +assumes sole responsibility to secure any other intellectual property rights +needed, if any. For example, if a third party patent license is required to +allow Recipient to distribute the Program, it is Recipient's responsibility +to acquire that license before distributing the Program. + +d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright license +set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and + +b) its license agreement: + +i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title +and non-infringement, and implied warranties or conditions of merchantability +and fitness for a particular purpose; + +ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; + +iii) states that any provisions which differ from this Agreement are offered +by that Contributor alone and not by any other party; and + +iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable manner on +or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and + +b) a copy of this Agreement must be included with each copy of the Program. + +Contributors may not remove or alter any copyright notices contained within +the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if any, in a manner that reasonably allows subsequent Recipients to identify +the originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a +manner which does not create potential liability for other Contributors. +Therefore, if a Contributor includes the Program in a commercial product +offering, such Contributor ("Commercial Contributor") hereby agrees to defend +and indemnify every other Contributor ("Indemnified Contributor") against any +losses, damages and costs (collectively "Losses") arising from claims, +lawsuits and other legal actions brought by a third party against the +Indemnified Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program in +a commercial product offering. The obligations in this section do not apply +to any claims or Losses relating to any actual or alleged intellectual +property infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor tocontrol, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim +at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON +AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER +EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR +CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A +PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all risks +associated with its exercise of rights under this Agreement , including but +not limited to the risks and costs of program errors, compliance with +applicable laws, damage to or loss of data, programs or equipment, and +unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and +does not cure such failure in a reasonable period of time after becoming +aware of such noncompliance. If all Recipient's rights under this Agreement +terminate, Recipient agrees to cease use and distribution of the Program as +soon as reasonably practicable. However, Recipient's obligations under this +Agreement and any licenses granted by Recipient relating to the Program shall +continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of +the Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this +Agreement, whether expressly, by implication, estoppel or otherwise. All +rights in the Program not expressly granted under this Agreement are +reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial +in any resulting litigation. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a883ef8 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# cryogen-core + +[Cryogen](https://github.com/lacarmen/cryogen)'s compiler. + +## License + +Copyright © 2014 Carmen La + +Distributed under the Eclipse Public License either version 1.0 or (at +your option) any later version. diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..55f20f5 --- /dev/null +++ b/project.clj @@ -0,0 +1,16 @@ +(defproject cryogen-core "0.1.0" + :description "Cryogen's compiler" + :url "https://github.com/lacarmen/cryogen-core" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :dependencies [[org.clojure/clojure "1.6.0"] + [clj-rss "0.1.9"] + [me.raynes/fs "1.4.4"] + [crouton "0.1.2"] + [cheshire "5.3.1"] + [clj-text-decoration "0.0.2"] + [io.aviso/pretty "0.1.12"] + [hiccup "1.0.5"] + [selmer "0.7.3"] + [markdown-clj "0.9.58" + :exclusions [com.keminglabs/cljx]]]) diff --git a/src/cryogen/compiler.clj b/src/cryogen/compiler.clj new file mode 100644 index 0000000..af2e6bd --- /dev/null +++ b/src/cryogen/compiler.clj @@ -0,0 +1,243 @@ +(ns cryogen.compiler + (:require [selmer.parser :refer [cache-off! render-file]] + [cryogen.io :refer + [get-resource find-assets create-folder wipe-public-folder copy-resources]] + [cryogen.sitemap :as sitemap] + [cryogen.rss :as rss] + [io.aviso.exception :refer [write-exception]] + [clojure.java.io :refer [copy file reader writer]] + [clojure.string :as s] + [text-decoration.core :refer :all] + [markdown.core :refer [md-to-html-string]] + [cryogen.toc :refer [generate-toc]] + [cryogen.sass :as sass])) + +(cache-off!) + +(defn root-path [config k] + (if-let [root (k config)] + (str "/" root "/") "/")) + +(def public "resources/public") + +(defn find-md-assets [] + (find-assets "templates" ".md")) + +(defn find-posts [{:keys [post-root]}] + (find-assets (str "templates/md" post-root) ".md")) + +(defn find-pages [{:keys [page-root]}] + (find-assets (str "templates/md" page-root) ".md")) + +(defn parse-post-date [file-name date-fmt] + (let [fmt (java.text.SimpleDateFormat. date-fmt)] + (.parse fmt (.substring file-name 0 10)))) + +(defn post-uri [file-name {:keys [blog-prefix post-root]}] + (str blog-prefix post-root (s/replace file-name #".md" ".html"))) + +(defn page-uri [page-name {:keys [blog-prefix page-root]}] + (str blog-prefix page-root (s/replace page-name #".md" ".html"))) + +(defn read-page-meta [page rdr] + (try + (read rdr) + (catch Exception _ + (throw (IllegalArgumentException. (str "Malformed metadata on page: " page)))))) + +(defn parse-content [rdr] + (md-to-html-string + (->> (java.io.BufferedReader. rdr) + (line-seq) + (s/join "\n")) + :heading-anchors true)) + +(defn parse-page [is-post? page config] + (with-open [rdr (java.io.PushbackReader. (reader page))] + (let [page-name (.getName page) + file-name (s/replace page-name #".md" ".html") + page-meta (read-page-meta page-name rdr) + content (parse-content rdr)] + (merge + (update-in page-meta [:layout] #(str (name %) ".html")) + {:file-name file-name + :content content + :toc (if (:toc page-meta) (generate-toc content))} + (if is-post? + (let [date (parse-post-date file-name (:post-date-format config)) + archive-fmt (java.text.SimpleDateFormat. "yyyy MMMM" (java.util.Locale. "en")) + formatted-group (.format archive-fmt date)] + {:date date + :formatted-archive-group formatted-group + :parsed-archive-group (.parse archive-fmt formatted-group) + :uri (post-uri file-name config) + :tags (set (:tags page-meta))}) + {:uri (page-uri file-name config) + :page-index (:page-index page-meta)}))))) + +(defn read-posts [config] + (->> (find-posts config) + (map #(parse-page true % config)) + (sort-by :date) + reverse)) + +(defn read-pages [config] + (->> (find-pages config) + (map #(parse-page false % config)) + (sort-by :page-index))) + +(defn tag-post [tags post] + (reduce (fn [tags tag] + (update-in tags [tag] (fnil conj []) (select-keys post [:uri :title]))) + tags (:tags post))) + +(defn group-by-tags [posts] + (reduce tag-post {} posts)) + +(defn group-for-archive [posts] + (->> posts + (map #(select-keys % [:title :uri :date :formatted-archive-group :parsed-archive-group])) + (group-by :formatted-archive-group) + (map (fn [[group posts]] + {:group group + :parsed-group (:parsed-archive-group (get posts 0)) + :posts (map #(select-keys % [:title :uri :date]) posts)})) + (sort-by :parsed-group) + reverse)) + +(defn tag-info [{:keys [blog-prefix tag-root]} tag] + {:name (name tag) + :uri (str blog-prefix tag-root (name tag) ".html")}) + +(defn add-prev-next [pages] + (map (fn [[prev target next]] + (assoc target + :prev (if prev (select-keys prev [:title :uri]) nil) + :next (if next (select-keys next [:title :uri]) nil))) + (partition 3 1 (flatten [nil pages nil])))) + +(defn group-pages [pages] + (let [{navbar-pages true + sidebar-pages false} (group-by #(boolean (:navbar? %)) pages)] + (map (partial sort-by :page-index) [navbar-pages sidebar-pages]))) + +(defn compile-pages [default-params pages {:keys [blog-prefix page-root]}] + (when-not (empty? pages) + (println (blue "compiling pages")) + (create-folder (str blog-prefix page-root)) + (doseq [{:keys [uri] :as page} pages] + (println "\t-->" (cyan uri)) + (spit (str public uri) + (render-file "templates/html/layouts/page.html" + (merge default-params + {:servlet-context "../" + :page page})))))) + +(defn compile-posts [default-params posts {:keys [blog-prefix post-root disqus-shortname]}] + (when-not (empty? posts) + (println (blue "compiling posts")) + (create-folder (str blog-prefix post-root)) + (doseq [post posts] + (println "\t-->" (cyan (:uri post))) + (spit (str public (:uri post)) + (render-file (str "templates/html/layouts/" (:layout post)) + (merge default-params + {:servlet-context "../" + :post post + :disqus-shortname disqus-shortname})))))) + +(defn compile-tags [default-params posts-by-tag {:keys [blog-prefix tag-root] :as config}] + (when-not (empty? posts-by-tag) + (println (blue "compiling tags")) + (create-folder (str blog-prefix tag-root)) + (doseq [[tag posts] posts-by-tag] + (let [{:keys [name uri]} (tag-info config tag)] + (println "\t-->" (cyan uri)) + (spit (str public uri) + (render-file "templates/html/layouts/tag.html" + (merge default-params {:servlet-context "../" + :name name + :posts posts}))))))) + +(defn compile-index [default-params {:keys [blog-prefix disqus?]}] + (println (blue "compiling index")) + (spit (str public blog-prefix "/index.html") + (render-file "templates/html/layouts/home.html" + (merge default-params + {:home true + :disqus? disqus? + :post (get-in default-params [:latest-posts 0])})))) + +(defn compile-archives [default-params posts {:keys [blog-prefix]}] + (println (blue "compiling archives")) + (spit (str public blog-prefix "/archives.html") + (render-file "templates/html/layouts/archives.html" + (merge default-params + {:archives true + :groups (group-for-archive posts)})))) + +(defn tag-posts [posts config] + (map #(update-in % [:tags] (partial map (partial tag-info config))) posts)) + +(defn read-config [] + (let [config (-> "templates/config.edn" + get-resource + slurp + read-string + (update-in [:blog-prefix] (fnil str "")) + (update-in [:rss-name] (fnil str "rss.xml")) + (update-in [:sass-src] (fnil str "css")) + (update-in [:sass-dest] (fnil str "css")) + (update-in [:post-date-format] (fnil str "yyyy-MM-dd")) + (update-in [:keep-files] (fnil seq [])))] + (merge + config + {:page-root (root-path :page-root config) + :post-root (root-path :post-root config) + :tag-root (root-path :tag-root config)}))) + +(defn compile-assets [] + (println (green "compiling assets...")) + (let [{:keys [site-url blog-prefix rss-name recent-posts sass-src sass-dest keep-files] :as config} (read-config) + posts (add-prev-next (read-posts config)) + pages (add-prev-next (read-pages config)) + [navbar-pages sidebar-pages] (group-pages pages) + posts-by-tag (group-by-tags posts) + posts (tag-posts posts config) + default-params {:title (:site-title config) + :tags (map (partial tag-info config) (keys posts-by-tag)) + :latest-posts (->> posts (take recent-posts) vec) + :navbar-pages navbar-pages + :sidebar-pages sidebar-pages + :archives-uri (str blog-prefix "/archives.html") + :index-uri (str blog-prefix "/index.html") + :rss-uri (str blog-prefix "/" rss-name)}] + + (wipe-public-folder keep-files) + (println (blue "copying resources")) + (copy-resources config) + (compile-pages default-params pages config) + (compile-posts default-params posts config) + (compile-tags default-params posts-by-tag config) + (compile-index default-params config) + (compile-archives default-params posts config) + (println (blue "generating site map")) + (spit (str public blog-prefix "/sitemap.xml") (sitemap/generate site-url)) + (println (blue "generating rss")) + (spit (str public blog-prefix "/" rss-name) (rss/make-channel config posts)) + (println (blue "compiling sass")) + (sass/compile-sass->css! sass-src sass-dest))) + +(defn compile-assets-timed [] + (time + (try + (compile-assets) + (catch Exception e + (if + (or (instance? IllegalArgumentException e) + (instance? clojure.lang.ExceptionInfo e)) + (println (red "Error:") (yellow (.getMessage e))) + (write-exception e)))))) + +(defn -main [] + (compile-assets-timed)) diff --git a/src/cryogen/github.clj b/src/cryogen/github.clj new file mode 100644 index 0000000..74af491 --- /dev/null +++ b/src/cryogen/github.clj @@ -0,0 +1,37 @@ +(ns cryogen.github + (:require [cheshire.core :as json]) + (:import (org.apache.commons.codec.binary Base64 StringUtils))) + +(defn get-gist [gist-uri] + (let [gist-id (last (clojure.string/split gist-uri #"/+")) ;;just need id for git api + gist-resp (try (slurp (str "https://api.github.com/gists/" gist-id)) + (catch Exception e {:error (.getMessage e)}))] + + (when-not (:error gist-resp) + (if-let [gist (-> (json/parse-string gist-resp) + (get "files") + first ;;todo: optionally get all gist files? + val)] + + {:content (get gist "content") + :language (get gist "language") + :name (get gist "filename") + :id gist-id})))) + +(defn get-src [git-file] + (let [git-re (re-find #"github.com/(.*)/blob/(.+?)/(.+)" git-file) ;;want second and last now (user/repo,file) for git api + git-res (str "https://api.github.com/repos/" (second git-re) "/contents/" (last git-re)) + git-resp (try (slurp git-res) + (catch Exception e {:error (.getMessage e)}))] + (when-not (:error git-resp) + (if-let [git-src (json/parse-string git-resp)] + {:content (String. (Base64/decodeBase64 (get git-src "content")) "UTF-8") + :name (get git-src "name") + :uri (get (get git-src "_links") "html")})))) + + +(defn get-gits-ex [] + [(get-gist "https://gist.github.com/viperscape/cec68f0791687f5959f1") + (get-src "https://github.com/viperscape/kuroshio/blob/master/examples/pubsub.clj")]) + +;(prn (get-gits-ex)) diff --git a/src/cryogen/io.clj b/src/cryogen/io.clj new file mode 100644 index 0000000..0735166 --- /dev/null +++ b/src/cryogen/io.clj @@ -0,0 +1,39 @@ +(ns cryogen.io + (:require [clojure.java.io :refer [file]] + [me.raynes.fs :as fs])) + +(def public "resources/public") + +(defn get-resource [resource] + (-> (Thread/currentThread) + (.getContextClassLoader) + (.getResource resource) + (.toURI) + (file))) + +(defn find-assets [f ext] + (->> (get-resource f) + file-seq + (filter (fn [file] (-> file .getName (.endsWith ext)))))) + +(defn create-folder [folder] + (let [loc (file (str public folder))] + (when-not (.exists loc) + (.mkdirs loc)))) + +(defn wipe-public-folder [keep-files] + (let [filenamefilter (reify java.io.FilenameFilter (accept [this _ filename] (not (some #{filename} keep-files))))] + (doseq [path (.listFiles (file public) filenamefilter)] + (fs/delete-dir path)))) + +(defn copy-resources [{:keys [blog-prefix resources]}] + (doseq [resource resources] + (let [src (str "resources/templates/" resource) + target (str public blog-prefix "/" resource)] + (cond + (not (.exists (file src))) + (throw (IllegalArgumentException. (str "resource " src " not found"))) + (.isDirectory (file src)) + (fs/copy-dir src target) + :else + (fs/copy src target))))) diff --git a/src/cryogen/rss.clj b/src/cryogen/rss.clj new file mode 100644 index 0000000..ad60e60 --- /dev/null +++ b/src/cryogen/rss.clj @@ -0,0 +1,28 @@ +(ns cryogen.rss + (:require [clj-rss.core :as rss] + [clojure.xml :refer [emit]]) + (:import java.util.Date)) + + +(defn posts-to-items [site-url author posts] + (map + (fn [{:keys [uri title content date]}] + (let [link (str (if (.endsWith site-url "/") (apply str (butlast site-url)) site-url) uri)] + {:guid link + :link link + :title title + :description content + :pubDate date + :author author})) + posts)) + +(defn make-channel [config posts] + (apply + (partial rss/channel-xml + false + {:title (:site-title config) + :link (:site-url config) + :description (:description config) + :lastBuildDate (Date.) + :author (:author config)}) + (posts-to-items (:site-url config) (:author config) posts))) \ No newline at end of file diff --git a/src/cryogen/sass.clj b/src/cryogen/sass.clj new file mode 100644 index 0000000..01564c0 --- /dev/null +++ b/src/cryogen/sass.clj @@ -0,0 +1,55 @@ +(ns cryogen.sass + (:require [clojure.java.shell :refer [sh]] + [clojure.java.io :as io])) + +(defn sass-installed? + "Checks for the installation of Sass." + [] + (= 0 (:exit (sh "sass" "--version")))) + +(defn find-sass-files + "Given a Diretory, gets files, filtered to those having scss or sass extention" + [dir] + (->> (.list (io/file dir)) + (seq) + (filter (comp not nil? (partial re-find #"(?i:s[ca]ss$)"))))) + +(defn compile-sass-file! + "Given a sass file which might be in src-sass directory, + output the resulting css in dest-sass. All error handling is + done by sh / launching the sass command." + [sass-file + src-sass + dest-sass] + (sh "sass" + "--update" + (str src-sass "/" sass-file) + (str dest-sass "/" ))) + + +(defn compile-sass->css! + "Given a directory src-sass, looks for all sass files and compiles them into +dest-sass. Prompts you to install sass if he finds sass files and can't find +the command. Shows you any problems it comes across when compiling. " + [src-sass + dest-sass] + + (let [sass-files (find-sass-files src-sass)] + (if (seq sass-files) + ;; I found sass files, + ;; If sass is installed + (if (sass-installed?) + ;; I compile all files + (doseq [a-file sass-files] + + (println "Compiling Sass File:" a-file) + (let [result (compile-sass-file! a-file src-sass dest-sass)] + (if (zero? (:exit result)) + ;; no problems in sass compilation + (println "Successfully compiled:" a-file) + ;; else I show the error + (println (:err result))))) + ;; Else I prompt to install Sass + (println "Sass seems not to be installed, but you have scss / sass files in " + src-sass + " - You might want to install it here: sass-lang.com"))))) diff --git a/src/cryogen/sitemap.clj b/src/cryogen/sitemap.clj new file mode 100644 index 0000000..cafb11b --- /dev/null +++ b/src/cryogen/sitemap.clj @@ -0,0 +1,28 @@ +(ns cryogen.sitemap + (:require [clojure.xml :refer [emit]] + [cryogen.io :refer [get-resource find-assets]]) + (:import java.util.Date)) + +;;generate sitemaps using the sitemap spec +;;http://www.sitemaps.org/protocol.html + +(defn format-date [date] + (let [fmt (java.text.SimpleDateFormat. "yyyy-MM-dd")] + (.format fmt date))) + +(defn loc [f] + (-> f (.getAbsolutePath) (.split "resources/public/") second)) + +(defn generate [site-url] + (with-out-str + (emit + {:tag :urlset + :attrs {:xmlns "http://www.sitemaps.org/schemas/sitemap/0.9"} + :content + (for [f (find-assets "public" ".html")] + {:tag :url + :content + [{:tag :loc + :content [(str site-url (loc f))]} + {:tag :lastmod + :content [(-> f (.lastModified) (Date.) format-date)]}]})}))) diff --git a/src/cryogen/toc.clj b/src/cryogen/toc.clj new file mode 100644 index 0000000..003323b --- /dev/null +++ b/src/cryogen/toc.clj @@ -0,0 +1,28 @@ +(ns cryogen.toc + (:require [crouton.html :as html] + [hiccup.core :as hiccup])) + +(defn get-headings [content] + (reduce + (fn [headings {:keys [tag attrs content] :as elm}] + (if (some #{tag} [:h1 :h2 :h3]) + (conj headings elm) + (if-let [more-headings (get-headings content)] + (into headings more-headings) + headings))) + [] content)) + +(defn make-links [headings] + (into [:ol.contents] + (for [{[{{name :name} :attrs} title] :content} headings] + [:li [:a {:href (str "#" name)} title]]))) + +(defn generate-toc [html] + (-> html + (.getBytes) + (java.io.ByteArrayInputStream.) + (html/parse) + :content + (get-headings) + (make-links) + (hiccup/html))) diff --git a/src/cryogen/watcher.clj b/src/cryogen/watcher.clj new file mode 100644 index 0000000..9012e65 --- /dev/null +++ b/src/cryogen/watcher.clj @@ -0,0 +1,21 @@ +(ns cryogen.watcher + (:require [clojure.java.io :refer [file]])) + +(defn get-assets [root] + (file-seq (file root))) + +(defn sum-times [path] + (->> (get-assets path) (map #(.lastModified %)) (reduce +))) + +(defn watch-assets [root action] + (loop [times (sum-times root)] + (Thread/sleep 300) + (let [new-times (sum-times root)] + (when-not (= times new-times) + (action)) + (recur new-times)))) + +(defn start-watcher! [root action] + (doto (Thread. #(watch-assets root action)) + (.setDaemon true) + (.start)))