UNPKG

11.6 kBtext/coffeescriptView Raw
1cp = require 'child_process'
2fs = require 'fs'
3_ = require 'underscore'
4_.str = require 'underscore.string'
5async = require 'async'
6
7CONTENT_TYPES = require './content'
8
9class Finder
10 constructor: ( @site, kind, cb ) ->
11 site = @site
12 kind = _.str.rtrim( kind, 's' )
13 klass = _.str.capitalize(kind)
14 @["_get#{klass}Paths"] ( err, filenames ) ->
15 # filter the paths to exclude . and special files, etc
16 filtered = _.reject( filenames || [], ( f ) -> f[0] == '.' || f[0] == '_' )
17 # generate Content instances for each path
18 contentGenerator = ( path, done ) ->
19 content = new CONTENT_TYPES[klass]( site, path )
20 content.process( done )
21 async.map( filtered, contentGenerator, cb )
22 _getStaticPaths: ( cb ) ->
23 fs.readdir "#{@site.root}/_static", ( err, statics ) ->
24 cb( err, statics || [] )
25 _getStylePaths: ( cb ) ->
26 fs.readdir "#{@site.root}/_styles", ( err, styles ) ->
27 cb( err, styles || [] )
28 _getScriptPaths: ( cb ) ->
29 fs.readdir "_scripts", ( err, scripts ) ->
30 cb( err, scripts || [] )
31 _getPostPaths: ( cb ) ->
32 fs.readdir "#{@site.root}/_posts", ( err, listings ) ->
33 # error is ok, no posts.
34 cb( null, listings || [] )
35 _getPagePaths: ( cb ) ->
36 site = @site
37 paths = []
38 processor = ( listing, done ) ->
39 if listing == null
40 done()
41 return
42 fs.stat "#{site.root}/_pages/#{listing}", ( err, stat ) ->
43 if err
44 Logger.error( "Could not stat file #{listing}" )
45 done()
46 else if stat.isFile()
47 paths.push listing
48 done()
49 else
50 fs.readdir "#{site.root}/_pages/#{listing}", ( err2, sublistings ) ->
51 sublistings.forEach ( sublisting ) ->
52 q.push( "#{listing}/#{sublisting}" )
53 done()
54
55 q = async.queue processor, 1
56
57 fs.readdir "#{@site.root}/_pages", ( err, listings ) ->
58 # again, error is ok
59 (listings || []).forEach ( listing ) -> q.push( listing )
60
61 q.push( null ) # kick off queue, even if there are no posts
62 q.drain = () -> cb( null, paths );
63
64module.exports = Finder
65
66###
67Builder = exports
68
69CWD = process.cwd()
70
71jade.filters.plain = ( b, c ) ->
72 return b.toString()
73
74globalInfo = {}
75
76Builder.reset = () ->
77 globalInfo.PAGE_INFO = {}
78 globalInfo.POST_INFO = {}
79
80sortedPosts = () ->
81 _(globalInfo.POST_INFO).chain()
82 .values()
83 .sortBy( (v) -> return v.timestamp.getTime() )
84 .reverse()
85 .value()
86
87Builder.addInfo = ( path, kind, info ) ->
88 globalInfo["#{kind.toUpperCase()}_INFO"][path] = info
89Builder.getInfo = ( path, kind ) ->
90 globalInfo["#{kind.toUpperCase()}_INFO"][path]
91
92
93renderers = {}
94innerContent = ( meta ) ->
95 type = meta.extension
96 switch type
97 when 'md'
98 meta.preprocessed = ejs.render( meta.src, locals: meta )
99 renderers[type] ?= require( 'node-markdown' ).Markdown
100 return renderers[type]( meta.preprocessed )
101 when 'jade'
102 renderers[type] ?= jade
103 return renderers[type].compile( meta.src )(meta)
104 when 'ejs'
105 return ejs.render( meta.src, locals: meta )
106
107Builder.render = ( path, kind, pathOverride=null ) ->
108 meta = _.clone(Builder.getInfo( path, kind ))
109 dir = "#{CWD}/build/#{meta.permalink}"
110 mkdir_p dir, 0777, ( err ) ->
111 if err
112 Logger.error "Error in mkdir_p - #{dir} - #{err}"
113 return
114 Logger.debug "Processing #{meta.permalink}"
115 meta.posts = sortedPosts()
116 meta.pages = _.values(globalInfo.PAGE_INFO)
117 meta.config = Config
118 meta._ = _
119 meta.h = _.reduce(
120 _.keys( Config.helpers||{} ),
121 (
122 (uh, key) ->
123 uh[key] = _.bind( Config.helpers[key], meta )
124 return uh
125 ),
126 {
127 moment: moment
128 innerContent: innerContent
129 }
130 )
131
132 try
133 meta.content = innerContent( meta )
134 catch e
135 Logger.error "Could not process file: #{meta.permalink} - #{e}, #{e.stack}"
136 return
137
138 writeFile = ( dest, html ) ->
139 fs.writeFile dest, html.toString(), ( err ) ->
140 Logger.error "Error writing final render #{err}" if err
141 Logger.debug "Wrote #{dest}"
142
143 dest = pathOverride || "#{meta.permalink}/index.html"
144 dest = "#{CWD}/build/#{dest}"
145
146 if meta.layout == false
147 writeFile( dest, meta.content )
148 else
149 fs.readFile "#{CWD}/_inc/#{meta.layout||'layout'}.jade", ( err, layout ) ->
150 Logger.error( meta.permalink, err, err.stack ) if err
151 tmpl = jade.compile layout.toString()
152 html = tmpl( meta, (err) -> Logger.error("OMG#{err}") if err )
153 writeFile( dest, html )
154
155contentList = ( filenames ) ->
156 _.reject( filenames || [], ( f ) -> f[0] == '.' || f[0] == '_' )
157
158# find all pages
159Builder.buildSite = () ->
160 findFunctions =
161 posts: ( cb ) ->
162 fs.readdir "#{CWD}/_posts", ( err, listings ) ->
163 # error is ok, no posts.
164 cb( null, listings || [] )
165 pages: ( cb ) ->
166 paths = []
167 processor = ( listing, done ) ->
168 if listing == null
169 done()
170 return
171 fs.stat "#{CWD}/_pages/#{listing}", ( err, stat ) ->
172 if err
173 Logger.error( "Could not stat file #{listing}" )
174 done()
175 else if stat.isFile()
176 paths.push listing
177 done()
178 else
179 fs.readdir "#{CWD}/_pages/#{listing}", ( err2, sublistings ) ->
180 sublistings.forEach ( sublisting ) ->
181 q.push( "#{listing}/#{sublisting}" )
182 done()
183
184 q = async.queue processor, 1
185
186 fs.readdir "#{CWD}/_pages", ( err, listings ) ->
187 # again, error is ok
188 (listings || []).forEach ( listing ) -> q.push( listing )
189
190 q.push( null ) # kick off queue, even if there are no posts
191 q.drain = () -> cb( null, paths );
192
193 async.parallel findFunctions, ( err, all ) ->
194 pages = contentList( all.pages )
195 posts = contentList( all.posts )
196 f = ( kind ) ->
197 return ( thing ) ->
198 meta = Builder.getInfo( thing, kind )
199 if Config.DEV || Config.drafts || !meta.draft
200 if meta.permalink == Config.index || meta.index
201 Logger.debug " index: #{meta.permalink}"
202 Builder.render( meta.thing, kind, 'index.html' )
203 Logger.debug( " #{kind}: #{meta.permalink}" )
204 Builder.render meta.thing, kind
205 else
206 Logger.debug( " skipped: #{thing}" )
207 process = ( kind ) ->
208 return ( thing ) ->
209 return ( cb ) ->
210 fs.readFile "#{CWD}/_#{kind}s/#{thing}", ( err, src ) ->
211 Logger.error "Error reading source of #{thing} - #{err}" if err
212 src = src.toString().split('---\n')
213 meta = eval coffeescript.compile( src[0], { bare: true } )
214 meta.scripts ?= []
215 meta.styles ?= []
216 meta.src = src.splice(1).join('---\n')
217
218 pathParts = thing.split('.')
219 meta.extension = pathParts.pop()
220 pathParts = pathParts.pop().split('/')
221
222 meta.filename = pathParts.pop()
223 meta.path = pathParts.join('/')
224 meta.path += '/' if meta.path
225 meta.permalink = "#{meta.path}#{meta.filename}"
226
227 meta.thing = "#{meta.permalink}.#{meta.extension}"
228
229 if kind == 'post'
230 dateFields = _.map(meta.date.split('.').reverse(), (f) -> parseInt(f, 10))
231 meta.timestamp = new Date( dateFields[0], dateFields[1] - 1, dateFields[2] )
232
233 meta.kind = kind
234
235 # get any old info
236 oldInfo = Builder.getInfo( meta.thing, kind )
237
238 # port over info we populated on first run through
239 meta.index = oldInfo.index if oldInfo
240
241 # insert new info
242 Builder.addInfo( meta.thing, kind, meta )
243 cb() if cb
244
245 pageProcess = process( 'page' )
246 postProcess = process( 'post' )
247
248 async.parallel(
249 _.flatten( [
250 _.map( pages, pageProcess ),
251 _.map( posts, postProcess )
252 ] ),
253 ( err ) ->
254 if not Config.index?
255 # determine newest/index post
256 newestThing = _.reject( sortedPosts(), (p) -> p.draft )[0].thing
257 newestMeta = Builder.getInfo( newestThing, 'post' )
258 newestMeta.index = true
259 Builder.addInfo( newestThing, 'post', newestMeta )
260 Logger.debug " index: #{newestMeta.permalink}"
261 pages.forEach f('page')
262 posts.forEach f('post')
263 )
264
265 if Config.DEV
266 pages.forEach ( thing ) ->
267 Logger.debug "Watching #{thing}"
268 Watcher.onChange "_pages/#{thing}", () ->
269 pageProcess( thing )( () -> f('page')( thing ) )
270 posts.forEach ( thing ) ->
271 Logger.debug "Watching #{thing}"
272 Watcher.onChange "_posts/#{thing}", () ->
273 postProcess( thing )( () -> f('post')( thing ) )
274
275
276Builder.compileStyles = () ->
277 parser = new(less.Parser)(
278 paths: [ process.cwd() + '/_styles' ]
279 )
280 f = ( style ) ->
281 fs.readdir "_styles", ( err, styles ) ->
282 fs.readFile "_styles/#{style}", ( err, css ) ->
283 return if err
284 fs.mkdir "build/css", 0777, ( err ) ->
285 done = ( err, src ) ->
286 if err
287 Logger.error "Could not process style: #{style}, #{JSON.stringify(err,undefined,2)}"
288 return
289 #log.info "SRC: #{src.toString()}"
290 fs.writeFile "build/css/#{style.replace(/less$/, 'css')}", src.toString(), ( err ) ->
291 Logger.error "Error in style done callback #{err}" if err
292 Logger.debug " style: #{style}"
293
294 if style.match /less$/
295 parser.parse( css.toString(), ( err, src ) ->
296 if err
297 done( err, src )
298 return
299 try
300 result = src.toCSS( compress: true )
301 done( null, result )
302 catch err2
303 done( err2, src )
304 )
305 else
306 done( null, css+"" )
307 fs.readdir "_styles", ( err, styles ) ->
308 buildAll = () -> contentList(styles).forEach f
309 buildAll()
310 styles.forEach ( style ) ->
311 Watcher.onChange "_styles/#{style}", buildAll
312
313Builder.compileScripts = () ->
314 fs.readdir "_scripts", ( err, scripts ) ->
315 contentList(scripts).forEach ( script ) ->
316 f = () ->
317 fs.readFile "_scripts/#{script}", ( err, coffee ) ->
318 fs.mkdir "build/js", 0777, ( err ) ->
319 src = if script.match( /coffee$/ ) then coffeescript.compile( coffee+"" ) else (coffee+"")
320 fs.writeFile "build/js/#{script.replace(/coffee$/, 'js')}", src.toString(), ( err ) ->
321 Logger.error "Error in script done callback #{err}" if err
322 Logger.debug " script: #{script}"
323 f()
324 Watcher.onChange "_scripts/#{script}", f
325
326Builder.copyStatics = () ->
327 # force delete anything that exists in data/static
328 # and then based on env:
329 # development: symlink anything in data/static to build
330 # production: fs copy anything in data/static to build
331 fs.readdir "_static", ( err, statics ) ->
332 contentList(statics).forEach ( static ) ->
333 cp.exec "rm -rf build/#{static}", ( err ) ->
334 Logger.error "Error in static copy #{err}" if err
335 if !Config.DEV
336 src = "_static/#{static}"
337 dest = "build/#{static}"
338 cp.exec "cp -R #{src} #{dest}", ( err ) ->
339 Logger.error " Could not write build/#{static} during generation." if err
340 Logger.error arguments if err
341 Logger.error err if err
342 else
343 fs.symlink "../_static/#{static}", "build/#{static}", ( err ) ->
344 Logger.error "Error symlinking statics #{err}" if err
345###