Permalink
Browse files

Add create-blog task. Closes #1

 * Copy leiningen.new.templates & tweak ever so slightly.
 * Add basic examples.
  • Loading branch information...
1 parent 188632a commit 64748b9cf71b1fb8820d663afc4944229b182758 @abscondment committed Jan 18, 2012
View
@@ -2,6 +2,7 @@
:repositories {"scala-tools" "http://scala-tools.org/repo-releases/"}
:dependencies [[org.clojure/clojure "1.3.0"]
[org.clojure/tools.cli "0.2.0"]
+ [stencil "0.2.0"]
[enlive "1.0.0"]
[clj-glob "1.0.0"]
[clj-yaml "0.3.1"]
View
@@ -1,20 +1,6 @@
(ns clog.config
(:require [clojure.java.io :as java-io]))
-(comment
- (def *config*
- {:title "Quod erat faciendum"
- :subtitle "A technical blog by Brendan Ribera"
- :author "Brendan Ribera"
- :domain "threebrothers.org"
- :root-url "/brendan/"
- :page-size 7
- :path "/home/brendan/code/threebrothers.org"
- :static-paths ["/brendan/"
- "/brendan/about/"
- "/brendan/software/"]
- }))
-
(def ^{:dynamic true :private false} *config* nil)
(defn do-read-config
View
@@ -2,7 +2,10 @@
(:require [clojure.string])
(:use
[clojure.java.io :only [file]]
- [clog config helpers]))
+ [clog config helpers]
+ [leiningen.new templates]))
+
+(def render (renderer "default"))
(defn- pretty-url [title]
(-> title
@@ -28,15 +31,35 @@ created_at: " (date-adjust-timezone
published: false
"))))
-
-(defn create-blog [path]
- (println "testing:" path))
+(defn create-blog
+ "Create a new blog."
+ [name]
+ (let [data {:name name
+ :sanitized (sanitize name)
+ :placeholder "{{sanitized}}"}]
+ (println "Generating new blog.")
+ (->files data
+ ["config.clj" (render "config.clj" data)]
+ [".gitignore" (render "gitignore" data)]
+ ["public/2012/01/example-post/post.yaml" (render "example-post.yaml" data)]
+ ["public/2012/01/example-post/post.markdown" (render "example-post.markdown" data)]
+ ["public/page/all/.gitkeep" (render "gitkeep" data)]
+ ["public/css/main.css" (render "main.css" data)]
+ ["templates/atom.template" (render "atom.template" data)]
+ ["templates/index.template" (render "index.template" data)]
+ ["templates/list.template" (render "list.template" data)]
+ ["templates/post.template" (render "post.template" data)]
+ ["templates/sitemap.template" (render "sitemap.template" data)]
+ ["templates/banner.snippet" (render "banner.snippet" data)]
+ ["templates/footer.snippet" (render "footer.snippet" data)]
+ ["templates/head.snippet" (render "head.snippet" data)]
+ ["templates/menu.snippet" (render "menu.snippet" data)]
+ ["templates/post-footer.snippet" (render "post-footer.snippet" data)])))
(defn post [title]
(let [url (pretty-url title)
- dir (file (:path *config*) "public" "blog"
- url)]
+ dir (file (:path *config*) "public" current-year current-month url)]
(if (.exists dir)
;; Directory already exists. Bail.
(do (println "A post appears to exist at" (str "/" url) "already!")
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <title></title>
+ <subtitle></subtitle>
+ <link href="http://threebrothers.org/brendan/blog/atom.xml" rel="self" />
+ <link href="http://threebrothers.org/brendan/" />
+ <id>tag:threebrothers.org,2010-01-07:/brendan/blog</id>
+ <updated></updated>
+ <author>
+ <name>Brendan Ribera</name>
+ <email>brendan.ribera+blogatom@gmail.com</email>
+ </author>
+ <entry>
+ <title></title>
+ <link></link>
+ <id></id>
+ <updated></updated>
+ <summary></summary>
+ <content></content>
+ </entry>
+</feed>
@@ -0,0 +1 @@
+Edit banner.snippet to change this.
@@ -0,0 +1,10 @@
+(def *config*
+ {:title "Example Blog with Text"
+ :subtitle "And Subtext"
+ :author "Marcus Aurelius"
+ :domain "example.org"
+ :page-size 7
+ :static-paths ["/magic/"
+ "/dwarves/"
+ "/stoicism/"]
+ })
@@ -0,0 +1,3 @@
+# Example Post
+
+Replace me with something real!
@@ -0,0 +1,4 @@
+---
+title: "Example Post"
+created_at: "2012-01-18 12:00:00.000Z"
+published: true
@@ -0,0 +1 @@
+Edit footer.snippet to change this. &ndash; This work is licensed under <a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/us/"><img alt="Creative Commons License" style="border-width:0" src="http://i.creativecommons.org/l/by-sa/3.0/us/80x15.png" /></a>
@@ -0,0 +1,6 @@
+public/index.html
+public/*.xml
+public/sitemap.*
+public/*/*/*.html
+public/*/*/*.md5sum
+public/page/*
No changes.
@@ -0,0 +1,3 @@
+<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
+<link href="/css/main.css" rel="stylesheet" type="text/css" />
+<title></title>
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head></head>
+ <body>
+ <div class="envelope">
+ <div class="lbar">
+ <div id='menu'></div>
+ </div>
+ <div class="banner"><h1 class="title"></h1></div>
+ <div class="content">
+ <div>
+ <ul class="posts"><li class="post"></li></ul>
+ <hr/>
+ <div class="nextLinks">
+ <div class="left"></div>
+ <div class="right"></div>
+ <div class="middle">
+ <h3></h3>
+ </div>
+ <br style="clear:both;"/>
+ </div>
+ </div>
+ </div>
+ <div class="footer"></div>
+ </div>
+ </body>
+</html>
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head></head>
+ <body>
+ <div class="envelope">
+ <div class="lbar">
+ <div id='menu'></div>
+ </div>
+ <div class="banner"><h1 class="title"></h1></div>
+ <div class="content">
+ <div>
+ <h1>Recent posts</h1>
+ <div class="years">
+ <div class="year"><b></b></div>
+ <ul class="posts"><li class="post"><a></a></li></ul>
+ </div>
+ </div>
+ </div>
+ <div class="footer"></div>
+ </div>
+ </body>
+</html>
@@ -0,0 +1 @@
+/* Customize this stylesheet. */
@@ -0,0 +1,11 @@
+<ul class="menu">
+ <li>
+ <a class="selected" href="/">Blog</a>
+ </li>
+ <li>
+ <a href="http://clojure.org/">Clojure.org</a>
+ </li>
+ <li>
+ <a href="https://github.com/abscondment/clog">Clog Documentation</a>
+ </li>
+</ul>
@@ -0,0 +1 @@
+Edit post-footer.snippet to change this.
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head></head>
+ <body>
+ <div class="envelope">
+ <div class="lbar">
+ <div id='menu'></div>
+ </div>
+ <div class="banner"><h1 class="title"></h1></div>
+ <div class="content post">
+ <h1><a></a></h1>
+ <div style="clear:both;margin:2em 0 1em 0;">
+ <div style="float:left;" id="previousPost"></div>
+ <div style="float:right;" id="nextPost"></div>
+ <br style="clear:both;"/>
+ </div>
+ </div>
+ <div class="footer"></div>
+ </div>
+ </body>
+</html>
+
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+ <url>
+ <loc></loc>
+ <changefreq>weekly</changefreq>
+ <priority>1.0</priority>
+ </url>
+</urlset>
@@ -0,0 +1,87 @@
+;; This API provides:
+;; * an easy way to generate files and namespaces
+;; * a way to render files written with a flexible template language
+;; * a way to get those files off of the classpath transparently
+(ns leiningen.new.templates
+ (:require [clojure.java.io :as io]
+ [clojure.string :as string]
+ [stencil.core :as stencil]))
+
+;; It is really easy to get resources off of the classpath in Clojure
+;; these days.
+(defn slurp-resource
+ "Reads the contents of a file on the classpath."
+ [resource-name]
+ (-> resource-name .getPath io/resource io/reader slurp))
+
+;; This is so common that it really is necessary to provide a way to do it
+;; easily.
+(defn sanitize
+ "Replace hyphens with underscores."
+ [s]
+ (string/replace s #"-" "_"))
+
+;; It'd be silly to expect people to pull in stencil just to render
+;; a mustache string. We can just provide this function instead. In
+;; doing so, it is much more likely that a template author will have
+;; to pull in any external libraries. Though he is welcome to if he
+;; needs.
+(def render-text stencil/render-string)
+
+;; Templates are expected to store their mustache template files in
+;; `leiningen/new/<template>/`. We have our convention of where templates
+;; will be on the classpath but we still have to know what the template's
+;; name is in order to know where this directory is and thus where to look
+;; for mustache template files. Since we're likely to be rendering a number
+;; of templates, we don't want to have to pass the name of the template every
+;; single time. We've also avoided magic so far, so a dynamic var and accompanying
+;; macro to set it is not in our game plan. Instead, our function for rendering
+;; templates on the classpath will be a function returned from this higher-order
+;; function. This way, we can say the name of our template just once and our
+;; render function will always know.
+(defn renderer
+ "Create a renderer function that looks for mustache templates in the
+ right place given the name of your template. If no data is passed, the
+ file is simply slurped and the content returned unchanged."
+ [name]
+ (fn [template & [data]]
+ (let [text (slurp-resource (io/file "clog" "generate" name template))]
+ (if data
+ (render-text text data)
+ text))))
+
+;; Our file-generating function, `->files` is very simple. We'd like
+;; to keep it that way. Sometimes you need your file paths to be
+;; templates as well. This function just renders a string that is the
+;; path to where a file is supposed to be placed by a template.
+;; It is private because you shouldn't have to call it yourself, since
+;; `->files` does it for you.
+(defn- template-path [name path data]
+ (io/file name (render-text path data)))
+
+;; A template, at its core, is meant to generate files and directories that
+;; represent a project. This is our way of doing that. `->files` is basically
+;; a mini-DSL for generating files. It takes your mustache template data and
+;; any number of vectors or strings. It iterates through those arguments and
+;; when it sees a vector, it treats the first element as the path to spit to
+;; and the second element as the contents to put there. If it encounters a
+;; string, it treats it as an empty directory that should be created. Any parent
+;; directories for any of our generated files and directories are created
+;; automatically. All paths are considered mustache templates and are rendered
+;; with our data. Of course, this doesn't effect paths that don't have templates
+;; in them, so it is all transparent unless you need it.
+(defn ->files
+ "Generate a file with content. path can be a java.io.File or string.
+ It will be turned into a File regardless. Any parent directories will
+ be created automatically. Data should include a key for :name so that
+ the project is created in the correct directory"
+ [{:keys [name] :as data} & paths]
+ (if (.mkdir (io/file name))
+ (doseq [path paths]
+ (if (string? path)
+ (.mkdirs (template-path name path data))
+ (let [[path content] path
+ path (template-path name path data)]
+ (.mkdirs (.getParentFile path))
+ (spit path content))))
+ (println "Directory" name "already exists!")))

0 comments on commit 64748b9

Please sign in to comment.