postal.js/node_modules/anvil.js/docs/anvil.html
2012-08-28 12:06:19 -04:00

1649 lines
No EOL
130 KiB
HTML

<!DOCTYPE html><head><style type="text/css">pre code{display:block;padding:.5em;color:black;background:#f8f8ff}pre .comment,pre .template_comment,pre .diff .header,pre .javadoc{color:#998;font-style:italic}pre .keyword,pre .css .rule .keyword,pre .winutils,pre .javascript .title,pre .lisp .title,pre .subst{color:black;font-weight:bold}pre .number,pre .hexcolor{color:#40a070}pre .string,pre .tag .value,pre .phpdoc,pre .tex .formula{color:#d14}pre .title,pre .id{color:#900;font-weight:bold}pre .javascript .title,pre .lisp .title,pre .subst{font-weight:normal}pre .class .title,pre .haskell .label,pre .tex .command{color:#458;font-weight:bold}pre .tag,pre .tag .title,pre .rules .property,pre .django .tag .keyword{color:navy;font-weight:normal}pre .attribute,pre .variable,pre .instancevar,pre .lisp .body{color:teal}pre .regexp{color:#009926}pre .class{color:#458;font-weight:bold}pre .symbol,pre .ruby .symbol .string,pre .ruby .symbol .keyword,pre .ruby .symbol .keymethods,pre .lisp .keyword,pre .tex .special,pre .input_number{color:#990073}pre .builtin,pre .built_in,pre .lisp .title{color:#0086b3}pre .preprocessor,pre .pi,pre .doctype,pre .shebang,pre .cdata{color:#999;font-weight:bold}pre .deletion{background:#fdd}pre .addition{background:#dfd}pre .diff .change{background:#0086b3}pre .chunk{color:#aaa}pre .tex .formula{opacity:.5}#docs {margin:auto}.block {clear:both}.comment {padding: 0 10px;width:50%;background:snow}.code {width:49%;background:#f8f8ff}td {vertical-align:top}table {width:90%; border-collapse:collapse}
</style></head><body><table id="docs"><tr class="block"><td class="comment"><p>Node's event emitter for all engines.</p></td><td class="code"><pre><code>events = require<span class="params">(<span class="string">"events"</span>)</span>
emitter = events.EventEmitter
</code></pre></td></tr><tr class="block"><td class="comment"><p>JavaScript's functional programming helper library -- <br />See <a href='http://documentcloud.github.com/underscore'>http://documentcloud.github.com/underscore</a> for more info</p></td><td class="code"><pre><code>_ = require <span class="string">"underscore"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>Console colors for Node -- <br />See <a href='https://github.com/Marak/colors.js'>https://github.com/Marak/colors.js</a> for more info</p></td><td class="code"><pre><code>colors = require <span class="string">"colors"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>Filesystem API</p></td><td class="code"><pre><code>fs = require <span class="string">"fs"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>Recursive mkdir for Node (think <em>mkdir -p</em>) -- <br />See ://github.com/substack/node-mkdirp for more info</p></td><td class="code"><pre><code>mkdir = require<span class="params">( <span class="string">"mkdirp"</span> )</span>.mkdirp
</code></pre></td></tr><tr class="block"><td class="comment"><p>Node's path helper library</p></td><td class="code"><pre><code>path = require <span class="string">"path"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>A Sinatra inspired web development framework for Node -- <br />See <a href='http://expressjs.com'>http://expressjs.com</a> for more info</p></td><td class="code"><pre><code>express = require <span class="string">"express"</span>
<span class="keyword">class</span> Log
</code></pre></td></tr><tr class="block"><td class="comment"><h2>onEvent</h2>
<p>Logs events in default console color</p>
<h3>Args:</h3>
<ul>
<li><em>x {String}</em>: message to log</li>
</ul></td><td class="code"><pre><code> onEvent: <span class="params">(x)</span> <span class="function">-></span>
<span class="keyword">unless</span> quiet
console.log <span class="string">" <span class="subst">#{x}</span>"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>onStep</h2>
<p>Logs steps in blue</p>
<h3>Args:</h3>
<ul>
<li><em>x {String}</em>: message to log</li>
</ul></td><td class="code"><pre><code> onStep: <span class="params">(x)</span> <span class="function">-></span>
<span class="keyword">unless</span> quiet
console.log <span class="string">"<span class="subst">#{x}</span>"</span>.blue
</code></pre></td></tr><tr class="block"><td class="comment"><h2>onComplete</h2>
<p>Logs successful process completions in green</p>
<h3>Args:</h3>
<ul>
<li><em>x {String}</em>: message to log</li>
</ul></td><td class="code"><pre><code> onComp<span class="reserved">let</span>e: <span class="params">(x)</span> <span class="function">-></span>
console.log <span class="string">"<span class="subst">#{x}</span>"</span>.green
</code></pre></td></tr><tr class="block"><td class="comment"><h2>onError</h2>
<p>Logs errors in red</p>
<h3>Args:</h3>
<ul>
<li><em>x {String}</em>: message to log</li>
</ul></td><td class="code"><pre><code> onError: <span class="params">(x)</span> <span class="function">-></span>
console.log <span class="string">"!!! <span class="subst">#{x}</span> !!!"</span>.red
log = <span class="keyword">new</span> Log<span class="params">()</span>
<span class="reserved">export</span>s.log = log
_ = require <span class="string">"underscore"</span>
path = require <span class="string">"path"</span>
Commander = require<span class="params">( <span class="string">"commander"</span> )</span>.Command
</code></pre></td></tr><tr class="block"><td class="comment"><p>Configuration container</p></td><td class="code"><pre><code>config = { }
</code></pre></td></tr><tr class="block"><td class="comment"><p>Configuration defaults</p></td><td class="code"><pre><code>siteConfig =
<span class="string">"source"</span>: <span class="string">"src"</span>
<span class="string">"style"</span>: <span class="string">"style"</span>
<span class="string">"markup"</span>: <span class="string">"markup"</span>
<span class="string">"output"</span>:
{
<span class="string">"source"</span>: [ <span class="string">"lib"</span>, <span class="string">"site/js"</span> ],
<span class="string">"style"</span>: [ <span class="string">"css"</span>, <span class="string">"site/css"</span> ],
<span class="string">"markup"</span>: <span class="string">"site/"</span>
}
<span class="string">"spec"</span>: <span class="string">"spec"</span>
<span class="string">"ext"</span>: <span class="string">"ext"</span>
<span class="string">"lint"</span>: {}
<span class="string">"uglify"</span>: {}
<span class="string">"cssmin"</span>: {}
<span class="string">"hosts"</span>: {
<span class="string">"/"</span>: <span class="string">"site"</span>
}
libConfig =
<span class="string">"source"</span>: <span class="string">"src"</span>
<span class="string">"output"</span>: <span class="string">"lib"</span>
<span class="string">"spec"</span>: <span class="string">"spec"</span>
<span class="string">"ext"</span>: <span class="string">"ext"</span>
<span class="string">"lint"</span>: {}
<span class="string">"uglify"</span>: {}
<span class="string">"hosts"</span>: {
<span class="string">"/"</span>: <span class="string">"spec"</span>
}
<span class="reserved">default</span>Mocha =
growl: <span class="literal">true</span>
ignoreLeaks: <span class="literal">true</span>
reporter: <span class="string">"spec"</span>
ui: <span class="string">"bdd"</span>
colors: <span class="literal">true</span>
<span class="reserved">default</span>Doc =
generator: <span class="string">"docco"</span>
output: <span class="string">"docs"</span>
continuous = test = inProcess = quiet = debug = <span class="literal">false</span>
ext =
gzip: <span class="string">"gz"</span>
uglify: <span class="string">"min"</span>
cssmin: <span class="string">"min"</span>
extensionLookup =
<span class="string">".css"</span>: <span class="string">"style"</span>
<span class="string">".scss"</span>: <span class="string">"style"</span>
<span class="string">".sass"</span>: <span class="string">"style"</span>
<span class="string">".less"</span>: <span class="string">"style"</span>
<span class="string">".stylus"</span>: <span class="string">"style"</span>
<span class="string">".js"</span>: <span class="string">"source"</span>
<span class="string">".coffee"</span>: <span class="string">"source"</span>
<span class="string">".markdown"</span>: <span class="string">"markup"</span>
<span class="string">".md"</span>: <span class="string">"markup"</span>
<span class="string">".html"</span>: <span class="string">"markup"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>Configuration</h2>
<p>Do all the things!<br />Calling anvil from the command line runs this.</p></td><td class="code"><pre><code><span class="keyword">class</span> Configuration
<span class="reserved">const</span>ructor: <span class="params">( @fp, @scheduler, @log )</span> <span class="function">-></span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>configure</h2>
<p>this call will return a configuration object that will<br />inform the rest of the process<br />* <em>onConfig {Function}</em>: the callback to invoke with a configuration object</p></td><td class="code"><pre><code> configure: <span class="params">( argList, onConfig )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
command = <span class="keyword">new</span> Commander<span class="params">()</span>
command
.version<span class="params">(<span class="string">"0.7.7"</span>)</span>
.option<span class="params">( <span class="string">"-b, --build [build file]"</span>, <span class="string">"Use a custom build file"</span>, <span class="string">"./build.json"</span> )</span>
.option<span class="params">( <span class="string">"--ci"</span>, <span class="string">"Run a continuous integration build"</span> )</span>
.option<span class="params">( <span class="string">"--host"</span>, <span class="string">"Setup a static HTTP host"</span> )</span>
.option<span class="params">( <span class="string">"--lib [project]"</span>, <span class="string">"Create a lib project at the folder [project]"</span> )</span>
.option<span class="params">( <span class="string">"--libfile [file name]"</span>, <span class="string">"Create a new lib build file named [file name]"</span> )</span>
.option<span class="params">( <span class="string">"--site [project]"</span>, <span class="string">"Create a site project at the folder [project]"</span> )</span>
.option<span class="params">( <span class="string">"--sitefile [file name]"</span>, <span class="string">"Create a new site build file named [file name]"</span> )</span>
.option<span class="params">( <span class="string">"--mocha"</span>, <span class="string">"Run specifications using Mocha"</span> )</span></code></pre></td></tr><tr class="block"><td class="comment"><p>.option( "--docco", "Create annotated source using docco" )</p></td><td class="code"><pre><code> .option<span class="params">( <span class="string">"--ape"</span>, <span class="string">"Create annotated source using ape"</span> )</span>
.option<span class="params">( <span class="string">"-q, --quiet"</span>, <span class="string">"Only print completion and error messages"</span> )</span>
command.parse<span class="params">( argList )</span>;
<span class="keyword">if</span> command.libfile <span class="keyword">or</span> command.sitefile</code></pre></td></tr><tr class="block"><td class="comment"><p>Generate all the directories and the config file</p></td><td class="code"><pre><code> name = command.libfile <span class="keyword">or</span>= command.sitefile
type = <span class="keyword">if</span> command.sitefile <span class="keyword">then</span> <span class="string">'site'</span> <span class="keyword">else</span> <span class="string">'lib'</span>
@writeConfig type, <span class="string">"<span class="subst">#{name}</span>.json"</span>, <span class="params">()</span> <span class="function">-></span>
self.log.onComp<span class="reserved">let</span>e <span class="string">"Created <span class="subst">#{ type }</span> build file - <span class="subst">#{ name }</span>"</span>
onConfig config, <span class="literal">true</span>
<span class="keyword">else</span> <span class="keyword">if</span> command.site <span class="keyword">or</span> command.lib</code></pre></td></tr><tr class="block"><td class="comment"><p>Generate all the directories and the config file</p></td><td class="code"><pre><code> type = <span class="keyword">if</span> command.site <span class="keyword">then</span> <span class="string">'site'</span> <span class="keyword">else</span> <span class="string">'lib'</span>
scaffold = command.site <span class="keyword">or</span>= command.lib
config = <span class="keyword">if</span> type == <span class="string">'site'</span> <span class="keyword">then</span> siteConfig <span class="keyword">else</span> libConfig
@log.onStep <span class="string">"Creating scaffolding for new <span class="subst">#{ type }</span> project"</span></code></pre></td></tr><tr class="block"><td class="comment"><p>Create all the directories</p></td><td class="code"><pre><code> self.ensurePaths<span class="params">( ()</span> <span class="function">-></span>
self.writeConfig<span class="params">( type, scaffold + <span class="string">"/build.json"</span>, ()</span> <span class="function">-></span>
self.log.onComp<span class="reserved">let</span>e <span class="string">"Scaffold ( <span class="subst">#{ scaffold }</span> ) created."</span>
onConfig config, <span class="literal">true</span>
)
, scaffold )
<span class="keyword">else</span>
buildFile = command.build
@log.onStep <span class="string">"Checking for <span class="subst">#{ buildFile }</span>"</span>
exists = @fp.pathExists buildFile
@prepConfig exists, buildFile, <span class="params">()</span> <span class="function">-></span>
<span class="keyword">if</span> command.host
config.host = <span class="literal">true</span>
<span class="keyword">if</span> command.ci
config.continuous = <span class="literal">true</span>
<span class="keyword">if</span> command.mocha
config.mocha = <span class="reserved">default</span>Mocha
<span class="keyword">if</span> command.ape
config.docs = <span class="reserved">default</span>Doc
config.docs.generator = <span class="string">"ape"</span>
<span class="keyword">if</span> command.docco
config.docs = <span class="reserved">default</span>Doc
</code></pre></td></tr><tr class="block"><td class="comment"><p>Run transforms and generate output</p></td><td class="code"><pre><code> self.ensurePaths <span class="params">()</span> <span class="function">-></span>
onConfig config
</code></pre></td></tr><tr class="block"><td class="comment"><h2>createLibBuild</h2>
<p>This creates a file containing the default lib build convention</p></td><td class="code"><pre><code> createLibBuild: <span class="params">()</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>build lib template?</p></td><td class="code"><pre><code> <span class="keyword">if</span> buildLibTemplate
output = <span class="keyword">if</span> buildLibTemplate == <span class="literal">true</span> <span class="keyword">then</span> <span class="string">"build.json"</span> <span class="keyword">else</span> buildLibTemplate
writeConfig <span class="string">"lib"</span>, output
global.process.exit<span class="params">(<span class="number">0</span>)</span>
config
</code></pre></td></tr><tr class="block"><td class="comment"><h2>createSiteBuild</h2>
<p>This creates a file containing the default site build convention</p></td><td class="code"><pre><code> createSiteBuild: <span class="params">()</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>build site template?</p></td><td class="code"><pre><code> <span class="keyword">if</span> buildSiteTemplate
output = <span class="keyword">if</span> buildSiteTemplate == <span class="literal">true</span> <span class="keyword">then</span> <span class="string">"build.json"</span> <span class="keyword">else</span> buildSiteTemplate
writeConfig <span class="string">"site"</span>, output
global.process.exit<span class="params">(<span class="number">0</span>)</span>
config
</code></pre></td></tr><tr class="block"><td class="comment"><h2>ensurePaths</h2>
<p>Make sure that all expected paths exist</p>
<h3>Args:</h3>
<ul>
<li><em>onComplete {Function}</em>: what to call when work is complete</li>
<li><em>prefix {String}</em>: the prefix to prepend to all paths</li>
</ul></td><td class="code"><pre><code> ensurePaths: <span class="params">( onComplete, prefix )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
prefix = prefix <span class="keyword">or</span>= <span class="string">""</span>
config.working = config.working || <span class="string">"./tmp"</span>
fp = @fp
paths = [
config[ <span class="string">"source"</span> ]
config[ <span class="string">"style"</span> ]
config[ <span class="string">"markup"</span> ]
config[ <span class="string">"spec"</span> ]
config[ <span class="string">"ext"</span> ]
config[ <span class="string">"working"</span> ]
]
</code></pre></td></tr><tr class="block"><td class="comment"><p>if documenting</p></td><td class="code"><pre><code> <span class="keyword">if</span> config.docs
paths.push config.docs.output
outputList = []</code></pre></td></tr><tr class="block"><td class="comment"><p>if the output is an object</p></td><td class="code"><pre><code> <span class="keyword">if</span> _.isObject config.output
outputList = _.flatten config.output
<span class="keyword">else</span></code></pre></td></tr><tr class="block"><td class="comment"><p>if output is a single path</p></td><td class="code"><pre><code> outputList = [ config.output ]
paths = paths.concat outputList
</code></pre></td></tr><tr class="block"><td class="comment"><p>if names</p></td><td class="code"><pre><code> name = config.name
<span class="keyword">if</span> name
<span class="keyword">for</span> output <span class="keyword">in</span> outputList
<span class="keyword">if</span> _.isString name
nestedPath = path.dirname name
<span class="keyword">if</span> nestedPath
paths.push path.join output, nestedPath
<span class="keyword">else</span>
nestedPaths = _.map _.flatten<span class="params">( name )</span>, <span class="params">( x )</span> <span class="function">-></span> path.join output, path.dirname<span class="params">( x )</span>
paths = paths.concat nestedPaths
worker = <span class="params">( p, done )</span> <span class="function">-></span>
<span class="keyword">try</span>
fp.ensurePath [ prefix, p ], <span class="params">()</span> <span class="function">-></span>
done<span class="params">()</span>
<span class="keyword">catch</span> err
done<span class="params">()</span>
@log.onStep <span class="string">"Ensuring project directory structure"</span>
@scheduler.parallel paths, worker, onComp<span class="reserved">let</span>e
</code></pre></td></tr><tr class="block"><td class="comment"><h2>prepConfig</h2>
<p>Fallback to default config, if specified config doesn't exist</p>
<h3>Args:</h3>
<ul>
<li><em>exists {Boolean}</em>: does the specified config file exist?</li>
<li><em>file {String}</em>: config file name</li>
<li><em>onComplete {Function}</em>: what to do after config is prepped</li>
</ul></td><td class="code"><pre><code> prepConfig: <span class="params">( exists, file, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
onDone = <span class="params">()</span> <span class="function">-></span> self.normalizeConfig onComp<span class="reserved">let</span>e
<span class="keyword">unless</span> exists
@loadConvention<span class="params">( onDone )</span>
<span class="keyword">else</span>
@loadConfig<span class="params">( file, onDone )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>loadConfig</h2>
<p>Setup full configuration using specified config file <br />For example, anvil -b custom.json</p>
<h3>Args:</h3>
<ul>
<li><em>file {String}</em>: config file name</li>
<li><em>onComplete {Function}</em>: what to do after config is loaded</li>
</ul></td><td class="code"><pre><code> loadConfig: <span class="params">( file, onComplete )</span> <span class="function">-></span>
@log.onStep <span class="string">"Loading config..."</span>
fp = @fp
fp.read file, <span class="params">( content )</span> <span class="function">-></span>
config = JSON.parse<span class="params">( content )</span>
<span class="keyword">if</span> config.extensions
ext.gzip = config.extensions.gzip || ext.gzip
ext.uglify = config.extensions.uglify || ext.uglify
</code></pre></td></tr><tr class="block"><td class="comment"><p>Carry on!</p></td><td class="code"><pre><code> onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>loadConvention</h2>
<p>Sets up default config if no config file is found</p>
<h3>Args:</h3>
<ul>
<li><em>onComplete {Function}</em>: what to do after config is setup</li>
</ul></td><td class="code"><pre><code> loadConvention: <span class="params">( onComplete )</span> <span class="function">-></span>
isSite = @fp.pathExists <span class="string">"./site"</span>
conventionConfig = <span class="keyword">if</span> isSite <span class="keyword">then</span> siteConfig <span class="keyword">else</span> libConfig
@log.onStep <span class="string">"No build file found, using <span class="subst">#{ <span class="keyword">if</span> isSite <span class="keyword">then</span> 'site' <span class="keyword">else</span> 'lib' }</span> conventions"</span>
config = conventionConfig
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>normalizeConfig</h2>
<p>Tries to normalize differences in configuration formats<br />between options and site vs. lib configurations</p>
<h4>Args:</h4>
<ul>
<li><em>onComplete {Function}</em>: what to call when work is complete</li>
</ul></td><td class="code"><pre><code> normalizeConfig: <span class="params">( onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
fp = @fp
config.output = config.output || <span class="string">"lib"</span>
<span class="keyword">if</span> _.isString config.output
outputPath = config.output
config.output =
style: outputPath
source: outputPath
markup: outputPath
calls = []
</code></pre></td></tr><tr class="block"><td class="comment"><p>finalization?</p></td><td class="code"><pre><code> finalize = config.finalize
<span class="keyword">if</span> finalize
calls.push <span class="params">( done )</span> <span class="function">-></span>
self.getFinalization finalize, <span class="params">( result )</span> <span class="function">-></span>
config.finalize = result
done<span class="params">()</span></code></pre></td></tr><tr class="block"><td class="comment"><p>wrapping?</p></td><td class="code"><pre><code> wrap = config.wrap
<span class="keyword">if</span> wrap
calls.push <span class="params">( done )</span> <span class="function">-></span>
self.getWrap wrap, <span class="params">( result )</span> <span class="function">-></span>
config.wrap = result
done<span class="params">()</span>
<span class="keyword">if</span> config.mocha
config.mocha = _.extend <span class="reserved">default</span>Mocha, config.mocha
<span class="keyword">if</span> config.docs
config.docs = _.extend <span class="reserved">default</span>Doc, config.docs
</code></pre></td></tr><tr class="block"><td class="comment"><p>any calls?</p></td><td class="code"><pre><code> <span class="keyword">if</span> calls.length > <span class="number">0</span>
@scheduler.parallel calls,
<span class="params">( call, done )</span> <span class="function">-></span>
call<span class="params">( done )</span>
, <span class="params">()</span> <span class="function">-></span> onComp<span class="reserved">let</span>e<span class="params">()</span>
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>getFinalization</h2>
<p>Build up a custom state machine to address how<br />finalization should happen for this project</p>
<h3>Args:</h3>
<ul>
<li><em>original {Object}</em>: the existing finalization block</li>
<li><em>onComplete {Function}</em>: what to call when work is complete</li>
</ul></td><td class="code"><pre><code> getFinalization: <span class="params">( original, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
finalization = {}
result = {}
aggregation = {}
aggregate = @scheduler.aggregate
</code></pre></td></tr><tr class="block"><td class="comment"><p>if there's no finalization</p></td><td class="code"><pre><code> <span class="keyword">if</span> <span class="keyword">not</span> original <span class="keyword">or</span> _.isEqual original, {}
onComp<span class="reserved">let</span>e finalization</code></pre></td></tr><tr class="block"><td class="comment"><p>if there's only one section</p></td><td class="code"><pre><code> <span class="keyword">else</span> <span class="keyword">if</span> original.header <span class="keyword">or</span>
original[<span class="string">"header-file"</span>] <span class="keyword">or</span>
original.footer <span class="keyword">or</span>
original[<span class="string">"footer-file"</span>]</code></pre></td></tr><tr class="block"><td class="comment"><p>build out aggregation for resolving header and footer</p></td><td class="code"><pre><code> @getContentBlock original, <span class="string">"header"</span>, aggregation
@getContentBlock original, <span class="string">"footer"</span>, aggregation</code></pre></td></tr><tr class="block"><td class="comment"><p>make sure we don't try to aggregate on empty</p></td><td class="code"><pre><code> <span class="keyword">if</span> _.isEqual aggregation, {}
onComp<span class="reserved">let</span>e finalization
<span class="keyword">else</span>
aggregate aggregation, <span class="params">( constructed )</span> <span class="function">-></span>
finalization.source = <span class="reserved">const</span>ructed
onComp<span class="reserved">let</span>e finalization</code></pre></td></tr><tr class="block"><td class="comment"><p>there are multiple sections</p></td><td class="code"><pre><code> <span class="keyword">else</span>
sources = {}
blocks = {
<span class="string">"source"</span>: original[ <span class="string">"source"</span> ],
<span class="string">"style"</span>: original[ <span class="string">"style"</span> ],
<span class="string">"markup"</span>: original[ <span class="string">"markup"</span> ]
}
_.each<span class="params">( blocks, ( block, name )</span> <span class="function">-></span>
subAggregate = {}
self.getContentBlock block, <span class="string">"header"</span>, subAggregate
self.getContentBlock block, <span class="string">"footer"</span>, subAggregate
sources[ name ] = <span class="params">( done )</span> <span class="function">-></span>
aggregate subAggregate, done
)
aggregate sources, onComp<span class="reserved">let</span>e
</code></pre></td></tr><tr class="block"><td class="comment"><h2>getWrap</h2>
<p>Build up a custom state machine to address how<br />wrapping should happen for this project</p>
<h3>Args:</h3>
<ul>
<li><em>original {Object}</em>: the existing wrap block</li>
<li><em>onComplete {Function}</em>: what to call when work is complete</li>
</ul></td><td class="code"><pre><code> getWrap: <span class="params">( original, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
wrap = {}
result = {}
aggregation = {}
aggregate = @scheduler.aggregate</code></pre></td></tr><tr class="block"><td class="comment"><p>if there's no wrap</p></td><td class="code"><pre><code> <span class="keyword">if</span> <span class="keyword">not</span> original <span class="keyword">or</span> _.isEqual original, {}
onComp<span class="reserved">let</span>e wrap</code></pre></td></tr><tr class="block"><td class="comment"><p>if there's only one section</p></td><td class="code"><pre><code> <span class="keyword">else</span> <span class="keyword">if</span> original.prefix <span class="keyword">or</span>
original[<span class="string">"prefix-file"</span>] <span class="keyword">or</span>
original.suffix <span class="keyword">or</span>
original[<span class="string">"suffix-file"</span>]</code></pre></td></tr><tr class="block"><td class="comment"><p>build out aggregation for resolving prefix and suffix</p></td><td class="code"><pre><code> @getContentBlock original, <span class="string">"prefix"</span>, aggregation
@getContentBlock original, <span class="string">"suffix"</span>, aggregation</code></pre></td></tr><tr class="block"><td class="comment"><p>make sure we don't try to aggregate on empty</p></td><td class="code"><pre><code> <span class="keyword">if</span> _.isEqual aggregation, {}
onComp<span class="reserved">let</span>e wrap
<span class="keyword">else</span>
aggregate aggregation, <span class="params">( constructed )</span> <span class="function">-></span>
wrap.source = <span class="reserved">const</span>ructed
onComp<span class="reserved">let</span>e wrap</code></pre></td></tr><tr class="block"><td class="comment"><p>there are multiple sections</p></td><td class="code"><pre><code> <span class="keyword">else</span>
sources = {}
blocks = {
<span class="string">"source"</span>: original[ <span class="string">"source"</span> ],
<span class="string">"style"</span>: original[ <span class="string">"style"</span> ],
<span class="string">"markup"</span>: original[ <span class="string">"markup"</span> ]
}
_.each<span class="params">( blocks, ( block, name )</span> <span class="function">-></span>
subAggregate = {}
self.getContentBlock block, <span class="string">"prefix"</span>, subAggregate
self.getContentBlock block, <span class="string">"suffix"</span>, subAggregate
sources[ name ] = <span class="params">( done )</span> <span class="function">-></span> aggregate subAggregate, done
)
aggregate sources, onComp<span class="reserved">let</span>e
</code></pre></td></tr><tr class="block"><td class="comment"><h2>getContentBlock</h2>
<p>Normalizes a wrapper or finalizer segment</p>
<h3>Args:</h3>
<ul>
<li>_property {string}: the property name to check for</li>
<li><em>source {Object}</em>: the configuration block</li>
<li><em>onComplete {Function}</em>: what to call when work is complete</li>
</ul></td><td class="code"><pre><code> getContentBlock: <span class="params">( source, property, aggregation )</span> <span class="function">-></span>
aggregation[ property ] = <span class="params">( done )</span> <span class="function">-></span> done <span class="string">""</span>
fp = @fp
<span class="keyword">if</span> source
propertyPath = source[<span class="string">"<span class="subst">#{ property }</span>-file"</span>]
propertyValue = source[ property ]
<span class="keyword">if</span> propertyPath <span class="keyword">and</span> @fp.pathExists propertyPath
aggregation[ property ] = <span class="params">( done )</span> <span class="function">-></span>
fp.read propertyPath, <span class="params">( content )</span> <span class="function">-></span>
done content
<span class="keyword">else</span> <span class="keyword">if</span> propertyValue
aggregation[ property ] = <span class="params">( done )</span> <span class="function">-></span> done propertyValue
</code></pre></td></tr><tr class="block"><td class="comment"><h2>writeConfig</h2>
<p>Creates new default config file</p>
<h3>Args:</h3>
<ul>
<li><em>name {String}</em>: the config file name</li>
<li><em>onComplete {Function}</em>: what to call when work is complete</li>
</ul></td><td class="code"><pre><code> writeConfig: <span class="params">( type, name, onComplete )</span> <span class="function">-></span>
config = <span class="keyword">if</span> type == <span class="string">"lib"</span> <span class="keyword">then</span> libConfig <span class="keyword">else</span> siteConfig
log = @log
json = JSON.stringify<span class="params">( config, <span class="literal">null</span>, <span class="string">"\t"</span> )</span>
@fp.write name, json, <span class="params">()</span> <span class="function">-></span>
log.onComp<span class="reserved">let</span>e <span class="string">"<span class="subst">#{name}</span> created successfully!"</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
<span class="reserved">export</span>s.configuration = Configuration
_ = require <span class="string">"underscore"</span></code></pre></td></tr><tr class="block"><td class="comment"><h2>Scheduler</h2>
<p>Provides flow control abstractions<br />aggregate and parallel are essentially fork/join variations and<br />pipeline is an asynchronous way to pass an input through a series<br />of transforms.</p></td><td class="code"><pre><code><span class="keyword">class</span> Scheduler
<span class="reserved">const</span>ructor: <span class="params">()</span> <span class="function">-></span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>parallel</h2>
<p>This takes a list of items and a single asynchronous<br />function with the signature ( item, done ) and<br />calls the worker for each item only invoking onComplete<br />once all calls have completed.<br />* <em>items {Array}</em>: a list of items to process<br />* <em>worker {Function}</em>: the worker that processes all the items<br />* <em>onComplete {Function}</em>: the function to call once all workers have completed</p></td><td class="code"><pre><code> parallel: <span class="params">( items, worker, onComplete )</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>Fail fast if list is empty</p></td><td class="code"><pre><code> <span class="keyword">if</span> <span class="keyword">not</span> items <span class="keyword">or</span> items.length == <span class="number">0</span>
onComp<span class="reserved">let</span>e []
count = items.length
results = []</code></pre></td></tr><tr class="block"><td class="comment"><p>Pushes <em>result</em> (if truthy) onto the <em>results</em> list and, if there are no more<br />items, calls <em>onComplete</em> with <em>results</em></p></td><td class="code"><pre><code> done = <span class="params">( result )</span> <span class="function">-></span>
count = count - <span class="number">1</span></code></pre></td></tr><tr class="block"><td class="comment"><p>Is <em>result</em> truthy?</p></td><td class="code"><pre><code> <span class="keyword">if</span> result</code></pre></td></tr><tr class="block"><td class="comment"><p>Append to <em>results</em>!</p></td><td class="code"><pre><code> results.push result</code></pre></td></tr><tr class="block"><td class="comment"><p>Is iteration complete?</p></td><td class="code"><pre><code> <span class="keyword">if</span> count == <span class="number">0</span></code></pre></td></tr><tr class="block"><td class="comment"><p>Call <em>onComplete</em>!</p></td><td class="code"><pre><code> onComp<span class="reserved">let</span>e<span class="params">( results )</span></code></pre></td></tr><tr class="block"><td class="comment"><p>Iteration occurs here</p></td><td class="code"><pre><code> worker<span class="params">( item, done )</span> <span class="keyword">for</span> item <span class="keyword">in</span> items
</code></pre></td></tr><tr class="block"><td class="comment"><h2>pipeline</h2>
<p>This takes an item and mutates it by calling a series<br />of asynchronous workers with the signature ( item, done ) and<br />only invokes onComplete after the last function in the pipeline completes.<br />* <em>item {Object}</em>: the initial item to pass to the first call<br />* <em>workers {Array}</em>: the ordered list of functions that compose the pipeline<br />* <em>onComplete {Function}</em>: the function to call once the last function has completed</p></td><td class="code"><pre><code> pipeline: <span class="params">( item, workers, onComplete )</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>Fail fast if list is empty</p></td><td class="code"><pre><code> <span class="keyword">if</span> item == <span class="literal">undefined</span> <span class="keyword">or</span> <span class="keyword">not</span> workers <span class="keyword">or</span> workers.length == <span class="number">0</span>
onComp<span class="reserved">let</span>e item || {}
</code></pre></td></tr><tr class="block"><td class="comment"><p>take the next worker in the list<br />and pass item (in its current state) to it</p></td><td class="code"><pre><code> iterate = <span class="params">( done )</span> <span class="function">-></span>
worker = workers.shift<span class="params">()</span>
worker item, done
done = <span class="function">-></span>
done = <span class="params">( product )</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>store the mutated product of the worker</p></td><td class="code"><pre><code> item = product</code></pre></td></tr><tr class="block"><td class="comment"><p>Any workers remaining?</p></td><td class="code"><pre><code> <span class="keyword">if</span> workers.length == <span class="number">0</span></code></pre></td></tr><tr class="block"><td class="comment"><p>Call <em>onComplete</em>!</p></td><td class="code"><pre><code> onComp<span class="reserved">let</span>e<span class="params">( product )</span>
<span class="keyword">else</span>
iterate done
</code></pre></td></tr><tr class="block"><td class="comment"><p>kick off the pipeline</p></td><td class="code"><pre><code> iterate done
</code></pre></td></tr><tr class="block"><td class="comment"><h2>aggregate</h2>
<p>Takes a hash map of calls and returns a corresponding hash map of<br />the results once all calls have completed. It's a weird fork/join<br />with named results vs. a randomly ordered list of results<br />* <em>calls {Object}</em>: the hash map of named asynchronous functions to call<br />* <em>onComplete {Function}</em>: the resulting hash map of corresponding values</p></td><td class="code"><pre><code> aggregate: <span class="params">( calls, onComplete )</span> <span class="function">-></span>
results = {}</code></pre></td></tr><tr class="block"><td class="comment"><p>checks to see if all results have been collected</p></td><td class="code"><pre><code> isDone = <span class="params">()</span> <span class="function">-></span>
_.chain<span class="params">( calls )</span>.keys<span class="params">()</span>.all<span class="params">( ( x )</span> <span class="function">-></span> results[ x ] != <span class="literal">undefined</span> ).value<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>build a callback for the specific named function</p></td><td class="code"><pre><code> getCallback = <span class="params">( name )</span> <span class="function">-></span>
<span class="params">( result )</span> <span class="function">-></span>
results[ name ] = result</code></pre></td></tr><tr class="block"><td class="comment"><p>have all the other calls completed?</p></td><td class="code"><pre><code> <span class="keyword">if</span> isDone<span class="params">()</span>
onComp<span class="reserved">let</span>e results
</code></pre></td></tr><tr class="block"><td class="comment"><p>iterate through the call list and invoke each one</p></td><td class="code"><pre><code> _.each<span class="params">( calls, ( call, name )</span> <span class="function">-></span>
callback = getCallback name
call callback
)
<span class="reserved">export</span>s.scheduler = Scheduler
fs = require <span class="string">"fs"</span>
path = require <span class="string">"path"</span>
_ = require <span class="string">"underscore"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>FSCrawler</h2>
<p>Wrote a custom 'dive' replacement after<br />the API changed significantly. The needs of Anvil are<br />pretty unique - always crawl the whole directory structure<br />from the start point and don't start work until we know all the files.<br />This 'crawls' a directory and returns all the files in the<br />structure recursive.</p></td><td class="code"><pre><code><span class="keyword">class</span> FSCrawler
<span class="reserved">const</span>ructor: <span class="params">( @scheduler )</span> <span class="function">-></span>
_.bindAll<span class="params">( <span class="keyword">this</span> )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>crawl</h2>
<p>Crawls the whole directory structure starting with <em>directory</em><br />and returns the full file listing.<br />* <em>directory {String/Array}</em>: a string or path spec for the directory to start crawling at<br />* <em>onComplete {Function}</em>: the function to call with a complete list of all the files</p></td><td class="code"><pre><code> crawl: <span class="params">( directory, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
fileList = []
forAll = @scheduler.parallel
<span class="keyword">if</span> directory <span class="keyword">and</span> directory != <span class="string">""</span></code></pre></td></tr><tr class="block"><td class="comment"><p>get the fully qualified path</p></td><td class="code"><pre><code> directory = path.resolve directory</code></pre></td></tr><tr class="block"><td class="comment"><p>read directory contents</p></td><td class="code"><pre><code> fs.readdir directory, <span class="params">( err, contents )</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>if we didn't get an error and we have contents</p></td><td class="code"><pre><code> <span class="keyword">if</span> <span class="keyword">not</span> err <span class="keyword">and</span> contents.length > <span class="number">0</span>
qualified = []</code></pre></td></tr><tr class="block"><td class="comment"><p>resolve and push qualified paths into the array</p></td><td class="code"><pre><code> <span class="keyword">for</span> item <span class="keyword">in</span> contents
qualified.push path.resolve directory, item
</code></pre></td></tr><tr class="block"><td class="comment"><p>find out if we have a directory or a file handle for<br />all the results from fs.readdir</p></td><td class="code"><pre><code> self.classifyHandles qualified, <span class="params">( files, directories )</span> <span class="function">-></span>
fileList = fileList.concat files</code></pre></td></tr><tr class="block"><td class="comment"><p>if we found any directories, continue crawling those</p></td><td class="code"><pre><code> <span class="keyword">if</span> directories.length > <span class="number">0</span>
forAll directories, self.crawl, <span class="params">( files )</span> <span class="function">-></span>
fileList = fileList.concat _.flatten files
onComp<span class="reserved">let</span>e fileList</code></pre></td></tr><tr class="block"><td class="comment"><p>no more directories at this level, return the file list</p></td><td class="code"><pre><code> <span class="keyword">else</span>
onComp<span class="reserved">let</span>e fileList</code></pre></td></tr><tr class="block"><td class="comment"><p>there was a problem or no files, return the list, we're done here</p></td><td class="code"><pre><code> <span class="keyword">else</span>
onComp<span class="reserved">let</span>e fileList</code></pre></td></tr><tr class="block"><td class="comment"><p>no more to do, return the list</p></td><td class="code"><pre><code> <span class="keyword">else</span>
onComp<span class="reserved">let</span>e fileList
</code></pre></td></tr><tr class="block"><td class="comment"><h2>classifyHandles</h2>
<p>Provides a fork/join wrapper around getting the fs stat objects for the list<br />of paths.<br />* <em>list {Array}</em>: the list of paths to check<br />* <em>onComplete {Function}</em>: the function to call with the lists of files and directories</p></td><td class="code"><pre><code> classifyHandles: <span class="params">( list, onComplete )</span> <span class="function">-></span>
<span class="keyword">if</span> list <span class="keyword">and</span> list.length > <span class="number">0</span>
@scheduler.parallel list, @classifyHandle, <span class="params">( classified )</span> <span class="function">-></span>
files = []
directories = []
<span class="keyword">for</span> item <span class="keyword">in</span> classified
<span class="keyword">if</span> item.isDirectory
directories.push item.file
<span class="keyword">else</span> <span class="keyword">if</span> <span class="keyword">not</span> item.error
files.push item.file
onComp<span class="reserved">let</span>e files, directories
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e [], []
</code></pre></td></tr><tr class="block"><td class="comment"><h2>classifyHandle</h2>
<p>Get the fs stat and determine if the path is to a file or a directory<br />* <em>file {String}</em>: the path to check<br />* <em>onComplete {Function}</em>: the function to call with the result of the check</p></td><td class="code"><pre><code> classifyHandle: <span class="params">( file, onComplete )</span> <span class="function">-></span>
fs.stat file, <span class="params">( err, stat )</span> <span class="function">-></span>
<span class="keyword">if</span> err
onComp<span class="reserved">let</span>e { file: file, err: err }
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e { file: file, isDirectory: stat.isDirectory<span class="params">()</span> }
<span class="reserved">export</span>s.crawler = FSCrawler
fs = require <span class="string">"fs"</span>
_ = require <span class="string">"underscore"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>FSProvider</h2>
<p>An abstraction around file interaction.<br />This is necessary to test any of Anvil's file level<br />interactions.</p></td><td class="code"><pre><code><span class="keyword">class</span> FSProvider
<span class="reserved">const</span>ructor: <span class="params">( @crawler, @log )</span> <span class="function">-></span>
_.bindAll <span class="keyword">this</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>buildPath</h2>
<p>Given an array or string pathspec, return a string pathspec</p>
<h3>Args:</h3>
<ul>
<li><em>pathSpec {Array, String}</em>: pathspec of either an array of strings or a single string</li>
</ul></td><td class="code"><pre><code> buildPath: <span class="params">( pathSpec )</span> <span class="function">-></span>
<span class="keyword">if</span> <span class="keyword">not</span> pathSpec
<span class="string">""</span>
<span class="keyword">else</span>
fullPath = pathSpec
<span class="keyword">if</span> _.isArray<span class="params">( pathSpec )</span>
fullPath = path.join.apply {}, pathSpec
fullPath
</code></pre></td></tr><tr class="block"><td class="comment"><h2>delete</h2>
<p>Deletes a file, given the file name (<em>file</em>) and its parent (<em>dir</em>)</p>
<h3>Args:</h3>
<ul>
<li><em>dir {String}</em>: pathspec of parent dir</li>
<li><em>filePath {String}</em>: file name or path spec array</li>
<li><em>onDeleted {Function}</em>: callback called if the file delete is successful</li>
</ul></td><td class="code"><pre><code> de<span class="reserved">let</span>e: <span class="params">( filePath, onDeleted )</span> <span class="function">-></span>
filePath = @buildPath filePath
<span class="keyword">if</span> @pathExists filePath
fs.unlink filePath, <span class="params">( err )</span> <span class="function">-></span>
onDe<span class="reserved">let</span>ed<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>ensurePath</h2>
<p>Makes sure <em>pathSpec</em> path exists before calling <em>onComplete</em> by<br />calling <em>mkdir pathSpec...</em> if <em>pathSpec</em> does not initially exist</p>
<h3>Args:</h3>
<ul>
<li><em>pathSpec {String}</em>: path string or array</li>
<li><em>onComplete {Function}</em>: called if path exists or is successfully created</li>
</ul></td><td class="code"><pre><code> ensurePath: <span class="params">( pathSpec, onComplete )</span> <span class="function">-></span>
pathSpec = @buildPath pathSpec
path.exists pathSpec, <span class="params">( exists )</span> <span class="function">-></span>
<span class="keyword">unless</span> exists</code></pre></td></tr><tr class="block"><td class="comment"><p>No <em>target</em> yet. Let's make it!</p></td><td class="code"><pre><code> mkdir pathSpec, <span class="string">"0755"</span>, <span class="params">( err )</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>Couldn't make the path. Report and abort!</p></td><td class="code"><pre><code> <span class="keyword">if</span> err
log.onError <span class="string">"Could not create <span class="subst">#{pathSpec}</span>. <span class="subst">#{err}</span>"</span>
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>getFiles</h2>
<p>Get all files in a specific path specification<br />* <em>filePath {String/Array}</em>: a string or array specifying the path to get files for<br />* <em>onFiles {Function}</em>: the function to call with the list of full file paths</p></td><td class="code"><pre><code> getFiles: <span class="params">( filePath, onFiles )</span> <span class="function">-></span>
<span class="keyword">if</span> <span class="keyword">not</span> filePath
onFiles []
<span class="keyword">else</span>
filePath = @buildPath filePath
files = []
@crawler.crawl filePath, onFiles
</code></pre></td></tr><tr class="block"><td class="comment"><h2>copy ##</h2>
<p>Copy a file<br />* <em>from {String/Array}</em>: the path spec for the file to copy<br />* <em>to {String/Array}</em>: the path spec for the destination<br />* <em>onComplete {Function}</em>: the function to call when the copy has completed</p></td><td class="code"><pre><code> copy: <span class="params">( from, to, onComplete )</span> <span class="function">-></span>
from = <span class="keyword">this</span>.buildPath from
to = <span class="keyword">this</span>.buildPath to
readStream = <span class="literal">undefined</span>
writeStream = fs.createWriteStream<span class="params">( to )</span>
<span class="params">( readStream = fs.createReadStream( from )</span> ).pipe<span class="params">( writeStream )</span>
readStream.<span class="literal">on</span> <span class="string">'end'</span>, <span class="params">()</span> <span class="function">-></span>
<span class="keyword">if</span> writeStream
writeStream.destroySoon<span class="params">()</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>pathExists</h2>
<p>Sychronously (GASP) check for the existence of a file or directory<br />* <em>pathSpec {String/Array}</em>: the string or path spec of the file or directory to check for</p></td><td class="code"><pre><code> pathExists: <span class="params">( pathSpec )</span> <span class="function">-></span>
pathSpec = <span class="keyword">this</span>.buildPath pathSpec
path.existsSync pathSpec
</code></pre></td></tr><tr class="block"><td class="comment"><h2>read</h2>
<p>Reads a file from <em>filePath</em> and calls <em>onFile</em> callback with contents (Asynchronously)</p>
<h3>Args:</h3>
<ul>
<li><em>filePath {String}</em>: pathspec of file to read and pass contents from</li>
<li><em>onContent {Function}</em>: callback to pass file's contents to</li>
</ul></td><td class="code"><pre><code> read: <span class="params">( filePath, onContent )</span> <span class="function">-></span>
filePath = @buildPath filePath
fs.readFile filePath, <span class="string">"utf8"</span>, <span class="params">( err, content )</span> <span class="function">-></span>
<span class="keyword">if</span> err
log.onError <span class="string">"Could not read <span class="subst">#{ filePath }</span> : <span class="subst">#{ err }</span>"</span>
onContent <span class="string">""</span>, err
<span class="keyword">else</span>
onContent content
</code></pre></td></tr><tr class="block"><td class="comment"><h2>readSync</h2>
<p>Reads a file from <em>filePath</em> ... synchronously ... SHAME! SHAAAAAAME! (ok, not really)<br />This function only exists for a specific use case in config, where there's literally<br />no advantage to reading files asynchronously but writing the code that way would<br />be a huge pain. Rationalization FTW</p>
<h3>Args:</h3>
<ul>
<li><em>filePath {String}</em>: pathspec of file to read and pass contents from</li>
</ul></td><td class="code"><pre><code> readSync: <span class="params">( filePath )</span> <span class="function">-></span>
filePath = @buildPath filePath
<span class="keyword">try</span>
fs.readFileSync filePath, <span class="string">"utf8"</span>
<span class="keyword">catch</span> err
log.onError <span class="string">"Could not read <span class="subst">#{ filePath }</span> : <span class="subst">#{ err }</span>"</span>
err
</code></pre></td></tr><tr class="block"><td class="comment"><h2>transformFile</h2>
<p>Given input file <em>filePath</em>, perform <em>transform</em> upon it then write the transformed content<br />to <em>outputPath</em> and call <em>onComplete</em>. (All operations performed asynchronously.)</p>
<h3>Args:</h3>
<ul>
<li><em>filePath {String}</em>: pathspec of file to transform</li>
<li><em>transform {Function}</em>: transform to perform on the file</li>
<li><em>outputPath {String}</em>: pathspec of output file</li>
<li><em>onComplete {Function}</em>: called when all operations are complete</li>
</ul></td><td class="code"><pre><code> transform: <span class="params">( filePath, transform, outputPath, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
filePath = @buildPath filePath
outputPath = @buildPath outputPath
<span class="keyword">this</span>.read<span class="params">(
filePath,
( content )</span> <span class="function">-></span>
transform content, <span class="params">( newContent, error )</span> <span class="function">-></span>
<span class="keyword">if</span> <span class="keyword">not</span> error
self.write outputPath, newContent, onComp<span class="reserved">let</span>e
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e error
)
</code></pre></td></tr><tr class="block"><td class="comment"><h2>write</h2>
<p>Writes <em>content</em> to file at <em>filePath</em> calling <em>done</em> after writing is complete (Asynchronously)</p>
<h3>Args:</h3>
<ul>
<li><em>filePath {String}</em>: pathspec of file to write</li>
<li><em>content {String}</em>: content to write to the file</li>
<li><em>onComplete {Function}</em>: called when all operations are complete</li>
</ul></td><td class="code"><pre><code> write: <span class="params">( filePath, content, onComplete )</span> <span class="function">-></span>
filePath = @buildPath filePath
fs.writeFile filePath, content, <span class="string">"utf8"</span>, <span class="params">( err )</span> <span class="function">-></span>
<span class="keyword">if</span> err
log.onError <span class="string">"Could not write <span class="subst">#{ filePath }</span> : <span class="subst">#{ err }</span>"</span>
onComp<span class="reserved">let</span>e err
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
<span class="reserved">export</span>s.fsProvider = FSProvider
</code></pre></td></tr><tr class="block"><td class="comment"><p>Unfancy JavaScript -- <br />See <a href='http://coffeescript.org/'>http://coffeescript.org/</a> for more info</p></td><td class="code"><pre><code>coffeeScript = require <span class="string">"coffee-script"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>LESS Compiler --<br />See <a href='http://lesscss.org'>http://lesscss.org</a></p></td><td class="code"><pre><code>less = require<span class="params">( <span class="string">"less"</span> )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>STYLUS Compiler --<br />See <a href='http://learnboost.github.com/stylus/'>http://learnboost.github.com/stylus/</a></p></td><td class="code"><pre><code>stylus = require<span class="params">( <span class="string">"stylus"</span> )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>HAML Compiler --<br />See <a href='http://haml-lang.com/'>http://haml-lang.com/</a></p></td><td class="code"><pre><code>haml = require<span class="params">( <span class="string">"haml"</span> )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>Markdown Compiler --<br />See <a href='https://github.com/chjj/marked'>http://github.com/chjj/marked</a></p></td><td class="code"><pre><code>marked = require<span class="params">( <span class="string">"marked"</span> )</span>
marked.setOptions { sanitize: <span class="literal">false</span> }
</code></pre></td></tr><tr class="block"><td class="comment"><p>HAML Compiler --<br />See <a href='http://haml-lang.com/'>http://haml-lang.com/</a></p></td><td class="code"><pre><code>coffeeKup = require<span class="params">( <span class="string">"coffeekup"</span> )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>underscore --<br />The most essential JS lib that ever was<br />See <a href='http://underscorejs.org/'>http://underscorejs.org/</a></p></td><td class="code"><pre><code>_ = require <span class="string">"underscore"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>Compiler</h2>
<p>'Compiles' files based on the extension to produce<br />browser friendly resources: JS, CSS, HTML</p></td><td class="code"><pre><code><span class="keyword">class</span> Compiler
<span class="reserved">const</span>ructor: <span class="params">(@fp, @log)</span> <span class="function">-></span>
_.bindAll<span class="params">( <span class="keyword">this</span> )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>compile</h2>
<p>Compiles a file with the correct compiler</p>
<h3>Args:</h3>
<ul>
<li><em>file {Object}</em>: file metadata for the file to compile</li>
<li><em>onComplete {Function}</em>: function to invoke when done</li>
</ul></td><td class="code"><pre><code> compile: <span class="params">( file, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
ext = file.ext<span class="params">()</span>
newExt = @extensionMap[ ext ]
newFile = file.name.replace ext, newExt
log = @log
log.onEvent <span class="string">"Compiling <span class="subst">#{ file.name }</span> to <span class="subst">#{ newFile }</span>"</span>
compiler = @compilers[ ext ]
<span class="keyword">if</span> compiler
@fp.transform<span class="params">(
[ file.workingPath, file.name ],
compiler,
[ file.workingPath, newFile ],
( err )</span> <span class="function">-></span>
<span class="keyword">unless</span> err
file.name = newFile
onComp<span class="reserved">let</span>e file
<span class="keyword">else</span>
log.onError <span class="string">"Error compiling <span class="subst">#{ file.name }</span>: \r\n <span class="subst">#{ err }</span>"</span>
onComp<span class="reserved">let</span>e err
)
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e file
</code></pre></td></tr><tr class="block"><td class="comment"><h2>extensionMap</h2>
<p>Provides a map of original to resulting extension</p></td><td class="code"><pre><code> extensionMap:
<span class="string">".js"</span>: <span class="string">".js"</span>
<span class="string">".css"</span>: <span class="string">".css"</span>
<span class="string">".html"</span>: <span class="string">".html"</span>
<span class="string">".coffee"</span> : <span class="string">".js"</span>
<span class="string">".kup"</span>: <span class="string">".html"</span>
<span class="string">".less"</span>: <span class="string">".css"</span>
<span class="string">".styl"</span>: <span class="string">".css"</span>
<span class="string">".sass"</span>: <span class="string">".css"</span>
<span class="string">".scss"</span>: <span class="string">".css"</span>
<span class="string">".haml"</span>: <span class="string">".html"</span>
<span class="string">".md"</span>: <span class="string">".html"</span>
<span class="string">".markdown"</span>: <span class="string">".html"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>compilers</h2>
<p>A simple hash map of file extension to a function that<br />invokes the corresponding compiler</p></td><td class="code"><pre><code> compilers:
<span class="string">".coffee"</span> : <span class="params">( content, onContent )</span> <span class="function">-></span>
<span class="keyword">try</span>
js = coffeeScript.compile content, { bare: <span class="literal">true</span> }
onContent js
<span class="keyword">catch</span> error
onContent <span class="string">""</span>, error
<span class="string">".less"</span> : <span class="params">( content, onContent )</span> <span class="function">-></span>
<span class="keyword">try</span>
less.render<span class="params">( content, {}, (e, css)</span> <span class="function">-></span> onContent<span class="params">(css)</span> )
<span class="keyword">catch</span> error
onContent <span class="string">""</span>, error
<span class="string">".sass"</span> : <span class="params">( content, onContent )</span> <span class="function">-></span>
<span class="keyword">try</span>
onContent content
<span class="keyword">catch</span> error
onContent <span class="string">""</span>, error
<span class="string">".scss"</span> : <span class="params">( content, onContent )</span> <span class="function">-></span>
<span class="keyword">try</span>
onContent content
<span class="keyword">catch</span> error
onContent <span class="string">""</span>, error
<span class="string">".styl"</span> : <span class="params">( content, onContent )</span> <span class="function">-></span>
<span class="keyword">try</span>
stylus.render<span class="params">( content, {}, (e, css)</span> <span class="function">-></span> onContent<span class="params">( css, e )</span> )
<span class="keyword">catch</span> error
onContent <span class="string">""</span>, error
<span class="string">".haml"</span> : <span class="params">( content, onContent )</span> <span class="function">-></span>
<span class="keyword">try</span>
html = haml.render content
onContent html
<span class="keyword">catch</span> error
onContent <span class="string">""</span>, error
<span class="string">".md"</span> : <span class="params">( content, onContent )</span> <span class="function">-></span>
<span class="keyword">try</span>
onContent<span class="params">( marked.parse( content )</span> )
<span class="keyword">catch</span> error
onContent <span class="string">""</span>, error
<span class="string">".markdown"</span> : <span class="params">( content, onContent )</span> <span class="function">-></span>
<span class="keyword">try</span>
onContent<span class="params">( marked.parse( content )</span> )
<span class="keyword">catch</span> error
onContent <span class="string">""</span>, error
<span class="string">".kup"</span> : <span class="params">( content, onContent )</span> <span class="function">-></span>
<span class="keyword">try</span>
html =<span class="params">( coffeeKup.compile content, {} )</span><span class="params">()</span>
onContent html
<span class="keyword">catch</span> error
onContent <span class="string">""</span>, error
<span class="reserved">export</span>s.compiler = Compiler
_ = require <span class="string">"underscore"</span>
path = require <span class="string">"path"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>Combiner</h2>
<p>Combines imports with the files importing them</p></td><td class="code"><pre><code><span class="keyword">class</span> Combiner
<span class="reserved">const</span>ructor: <span class="params">( @fp, @scheduler, @findPatterns, @replacePatterns )</span> <span class="function">-></span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>combineList</h2>
<p>combine all the files in the <em>list</em> and call onComplete when finished</p>
<h3>Args:</h3>
<ul>
<li><em>list {Array}</em>: collection of file metadata</li>
<li><em>onComplete {Function}</em>: callback to invoke on completion</li>
</ul></td><td class="code"><pre><code> combineList: <span class="params">( list, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
forAll = @scheduler.parallel</code></pre></td></tr><tr class="block"><td class="comment"><p>for all files in the list<br /> find all the imports for every file<br />then find all the files that depend on each file<br />then combine all the files in the list</p></td><td class="code"><pre><code> findImports = _.bind<span class="params">( ( file, done )</span> <span class="function">-></span>
self.findImports file, list, done
, <span class="keyword">this</span> )
</code></pre></td></tr><tr class="block"><td class="comment"><p>once the imports are known, we can determine how many<br />files import (or depend) a given file</p></td><td class="code"><pre><code> findDependents = _.bind<span class="params">( ( file, done )</span> <span class="function">-></span>
self.findDependents file, list, done
, <span class="keyword">this</span> )
</code></pre></td></tr><tr class="block"><td class="comment"><p>replace all of file's import statements with<br />the imported files' contents</p></td><td class="code"><pre><code> combineFile = _.bind<span class="params">( ( file, done )</span> <span class="function">-></span>
self.combineFile file, done
, <span class="keyword">this</span> )
</code></pre></td></tr><tr class="block"><td class="comment"><p>combine all the files</p></td><td class="code"><pre><code> forAll list, findImports, <span class="params">()</span> <span class="function">-></span>
<span class="keyword">for</span> f1 <span class="keyword">in</span> list
findDependents f1, list
forAll list, combineFile, onComp<span class="reserved">let</span>e
</code></pre></td></tr><tr class="block"><td class="comment"><h2>combineFile</h2>
<p>combine a specifc <em>file</em> after ensuring it's dependencies have been combined</p>
<h3>Args:</h3>
<ul>
<li><em>file {Object}</em>: the file metadata describing the file to combine</li>
<li><em>onComplete {Function}</em>: callback to invoke on completion</li>
</ul></td><td class="code"><pre><code> combineFile: <span class="params">( file, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
forAll = @scheduler.parallel</code></pre></td></tr><tr class="block"><td class="comment"><p>if we've already combined this file, just call complete</p></td><td class="code"><pre><code> <span class="keyword">if</span> file.combined
onComp<span class="reserved">let</span>e<span class="params">()</span></code></pre></td></tr><tr class="block"><td class="comment"><p>otherwise, combine all the file's dependencies first, then combine the file</p></td><td class="code"><pre><code> <span class="keyword">else</span>
combineFile = <span class="params">( file, done )</span> <span class="function">-></span>
self.combineFile file, done
dependencies = file.<span class="reserved">import</span>s
<span class="keyword">if</span> dependencies <span class="keyword">and</span> dependencies.length > <span class="number">0</span>
forAll dependencies, combineFile, <span class="params">()</span> <span class="function">-></span>
self.combine file, <span class="params">()</span> <span class="function">-></span>
file.combined = <span class="literal">true</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
<span class="keyword">else</span>
self.combine file, <span class="params">()</span> <span class="function">-></span>
file.combined = <span class="literal">true</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>fileImports</h2>
<p>search the <em>file</em> using regex patterns and store all referenced files</p>
<h3>Args:</h3>
<ul>
<li><em>file {Object}</em>: the file metadata describing the file to combine</li>
<li><em>list {Array}</em>: collection of file metadata</li>
<li><em>onComplete {Function}</em>: callback to invoke on completion</li>
</ul></td><td class="code"><pre><code> findImports: <span class="params">( file, list, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
<span class="reserved">import</span>s = []
@fp.read [ file.workingPath, file.name ], <span class="params">( content )</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>find the import statements in the file contents using @findPatterns</p></td><td class="code"><pre><code> <span class="keyword">for</span> pattern <span class="keyword">in</span> self.findPatterns
<span class="reserved">import</span>s = <span class="reserved">import</span>s.concat content.match pattern
<span class="reserved">import</span>s = _.filter <span class="reserved">import</span>s, <span class="params">( x )</span> <span class="function">-></span> x</code></pre></td></tr><tr class="block"><td class="comment"><p>strip out all the raw file names from the import statements<br />find the matching file metadata for the import</p></td><td class="code"><pre><code> for imported in imports
importName = ( imported.match ///['\"].*['\"]/// )[ 0 ].replace(///['\"]///g, "" )
importedFile = _.find( list, ( i ) ->
relativeImportPath = path.relative( path.dirname( file.fullPath ), path.dirname( i.fullPath ) )
relativeImport = self.fp.buildPath( [ relativeImportPath, i.name ] )
relativeImport == importName )
file.imports.push importedFile
onComplete()
</code></pre></td></tr><tr class="block"><td class="comment"><h2>fileDependents</h2>
<p>search the <em>list</em> to see if any files import <em>file</em></p>
<h3>Args:</h3>
<ul>
<li><em>file {Object}</em>: the file metadata describing the file to combine</li>
<li><em>list {Array}</em>: collection of file metadata</li>
<li><em>onComplete {Function}</em>: callback to invoke on completion</li>
</ul></td><td class="code"><pre><code> findDependents: <span class="params">( file, list )</span> <span class="function">-></span>
<span class="reserved">import</span>ed = <span class="params">( importFile )</span> <span class="function">-></span>
file.fullPath == <span class="reserved">import</span>File.fullPath
<span class="keyword">for</span> item <span class="keyword">in</span> list
<span class="keyword">if</span> _.any item.<span class="reserved">import</span>s, <span class="reserved">import</span>ed <span class="keyword">then</span> file.dependents++
</code></pre></td></tr><tr class="block"><td class="comment"><h2>combine</h2>
<p>combine all the <em>file</em>'s imports into its contents</p>
<h3>Args:</h3>
<ul>
<li><em>file {Object}</em>: the file metadata describing the file to combine</li>
<li><em>onComplete {Function}</em>: callback to invoke on completion</li>
</ul></td><td class="code"><pre><code> combine: <span class="params">( file, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
<span class="keyword">unless</span> file.combined
pipe = @scheduler.pipeline
fp = @fp
<span class="keyword">if</span> file.<span class="reserved">import</span>s.length > <span class="number">0</span></code></pre></td></tr><tr class="block"><td class="comment"><p>creates a closure around a specific import to prevent<br />access to a changing variable</p></td><td class="code"><pre><code> steps = <span class="keyword">for</span> <span class="reserved">import</span>ed <span class="keyword">in</span> file.<span class="reserved">import</span>s
self.getStep file, <span class="reserved">import</span>ed
fp.read [ file.workingPath, file.name ], <span class="params">( main )</span> <span class="function">-></span>
pipe main, steps, <span class="params">( result )</span> <span class="function">-></span>
fp.write [ file.workingPath, file.name ], result, <span class="params">()</span> <span class="function">-></span> onComp<span class="reserved">let</span>e<span class="params">()</span>
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>getStep</h2>
<p>This is insane but it works - creating a closure around<br />a specific import to prevent accessing a changing variable.<br />* <em>file {Object}</em> : the file we're importing into<br />* <em>import {Object}</em>: the imported file to create the closure around</p></td><td class="code"><pre><code> getStep: <span class="params">( file, imported )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
<span class="params">( text, onDone )</span> <span class="function">-></span> self.replace text, file, <span class="reserved">import</span>ed, onDone
</code></pre></td></tr><tr class="block"><td class="comment"><h2>replace</h2>
<p>create a replacement regex that will take the <em>imported</em> content and replace the<br />matched patterns within the main file's <em>content</em></p>
<h3>Args:</h3>
<ul>
<li><em>content {Object}</em>: the content of the main file</li>
<li><em>file {Object}</em> : the file we're importing into</li>
<li><em>imported {Object}</em>: file metadata for the imported</li>
<li><em>onComplete {Function}</em>: callback to invoke on completion</li>
</ul></td><td class="code"><pre><code> replace: <span class="params">( content, file, imported, onComplete )</span> <span class="function">-></span>
patterns = @replacePatterns
pipe = @scheduler.pipeline
source = <span class="reserved">import</span>ed.name
working = <span class="reserved">import</span>ed.workingPath
relativeImportPath = path.relative<span class="params">( path.dirname( file.fullPath )</span>, path.dirname<span class="params">( imported.fullPath )</span> )
relativeImport = @fp.buildPath<span class="params">( [ relativeImportPath, imported.name ] )</span>
@fp.read [ working, source ], <span class="params">( newContent )</span> <span class="function">-></span>
steps = <span class="keyword">for</span> pattern <span class="keyword">in</span> patterns</code></pre></td></tr><tr class="block"><td class="comment"><p>creates a function that will replace the import statement<br />with a specific file's contents</p></td><td class="code"><pre><code> <span class="params">( current, done )</span> <span class="function">-></span>
stringified = pattern.toString<span class="params">()</span>.replace ///replace///, relativeImport
stringified = stringified.substring<span class="params">( <span class="number">1</span>, stringified.length - <span class="number">2</span> )</span>
fullPattern = <span class="keyword">new</span> RegExp stringified, <span class="string">"g"</span>
capture = fullPattern.exec<span class="params">( content )</span>
<span class="keyword">if</span> capture <span class="keyword">and</span> capture.length > <span class="number">1</span></code></pre></td></tr><tr class="block"><td class="comment"><p>capture the indentation of the import</p></td><td class="code"><pre><code> whiteSpace = capture[<span class="number">1</span>]</code></pre></td></tr><tr class="block"><td class="comment"><p>apply indentation to all lines of the new content</p></td><td class="code"><pre><code> newContent = <span class="string">"<span class="subst">#{ whiteSpace }</span>"</span> + newContent.replace ///\n///g, <span class="string">"\n<span class="subst">#{ whiteSpace }</span>"</span>
sanitized = current.replace<span class="params">( fullPattern, newContent.replace( <span class="string">"\$"</span>, <span class="string">"$"</span> )</span> ).replace<span class="params">( <span class="string">"$"</span>, <span class="string">"$"</span> )</span>
done sanitized
pipe content, steps, <span class="params">( result )</span> <span class="function">-></span>
onComp<span class="reserved">let</span>e result
<span class="reserved">export</span>s.combiner = Combiner</code></pre></td></tr><tr class="block"><td class="comment"><p>Uglify: JavaScript parser and compressor/beautifier toolkit -- <br />See <a href='https://github.com/mishoo/UglifyJS'>https://github.com/mishoo/UglifyJS</a> for more info</p></td><td class="code"><pre><code>jsp = require<span class="params">( <span class="string">"uglify-js"</span> )</span>.parser
pro = require<span class="params">( <span class="string">"uglify-js"</span> )</span>.uglify
</code></pre></td></tr><tr class="block"><td class="comment"><p>A Node-compatible port of Douglas Crockford's JSLint -- </p></td><td class="code"><pre><code>jslint = require<span class="params">( <span class="string">"readyjslint"</span> )</span>.JSLINT
</code></pre></td></tr><tr class="block"><td class="comment"><p>CSS Minifier --<br />See <a href='https://github.com/jbleuzen/node-cssmin'>https://github.com/jbleuzen/node-cssmin</a></p></td><td class="code"><pre><code>cssminifier = require <span class="string">"cssmin"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>StylePipeline</h2>
<p>The set of post-processes that happen to completed style outputs.<br />These include minification, wrapping and<br />finalization depending on the build configuration.</p></td><td class="code"><pre><code><span class="keyword">class</span> StylePipeline
<span class="reserved">const</span>ructor: <span class="params">( @config, @fp, @minifier, @scheduler, @log )</span> <span class="function">-></span>
_.bindAll<span class="params">( <span class="keyword">this</span> )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>process</h2>
<p>Take the list of files and minify, wrap and finalize them<br />according to configuration. In the event that files are minified,<br />this function will create a seperate set of files to separate<br />processing between developer friendly and deployment friendly files.<br />* <em>files {Array}</em>: the list of files to process<br />* <em>onComplete {Array}</em>: the function to call with the list of files</p></td><td class="code"><pre><code> process: <span class="params">( files, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
forAll = @scheduler.parallel
forAll files, @wrap, <span class="params">()</span> <span class="function">-></span>
minified = []
<span class="keyword">if</span> self.config.cssmin
minified = _.map<span class="params">( files, ( x )</span> <span class="function">-></span> _.clone x )
forAll files, self.finalize, <span class="params">()</span> <span class="function">-></span>
self.log.onStep <span class="string">"Finalizing CSS"</span>
forAll minified, self.minify, <span class="params">()</span> <span class="function">-></span>
<span class="keyword">if</span> minified.length > <span class="number">0</span>
self.log.onStep <span class="string">"Minifying CSS"</span>
forAll minified, self.finalize, <span class="params">()</span> <span class="function">-></span>
onComp<span class="reserved">let</span>e<span class="params">( files.concat minified )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>minify</h2>
<p>Uses the cssmin lib to minify the output styles<br />* <em>file {String}</em>: the file to minify<br />* <em>onComplete {Function}</em>: the function to call after minification has completed</p></td><td class="code"><pre><code> minify: <span class="params">( file, onComplete )</span> <span class="function">-></span>
<span class="keyword">if</span> @config.cssmin
@log.onEvent <span class="string">"Minifying <span class="subst">#{ file.name }</span>"</span>
self = <span class="keyword">this</span>
ext = file.ext<span class="params">()</span>
newFile = file.name.replace ext, <span class="string">".min.css"</span>
self.fp.transform<span class="params">(
[ file.workingPath, file.name ],
( content, onTransform )</span> <span class="function">-></span>
onTransform<span class="params">( self.minifier.cssmin content )</span>
, [ file.workingPath, newFile ],
<span class="params">( )</span> <span class="function">-></span>
file.name = newFile
onComp<span class="reserved">let</span>e<span class="params">()</span>
)
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>finalize</h2>
<p>Finalize, for lack of a better term, puts header and footer content around the file's contents.<br />This step is different than wrapping because it happens AFTER minification and won't get<br />mangled as a result.<br />* <em>file {String}</em>: the file to finalize<br />* <em>onComplete {Function}</em>: the function to call after finalization has completed</p></td><td class="code"><pre><code> finalize: <span class="params">( file, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
<span class="keyword">if</span> @config.finalize <span class="keyword">and</span> @config.finalize.style
@log.onEvent <span class="string">"Finalizing <span class="subst">#{ file.name }</span>"</span>
header = @config.finalize.style.header
footer = @config.finalize.style.footer
@fp.transform<span class="params">(
[ file.workingPath, file.name ],
( content, onTransform )</span> <span class="function">-></span>
<span class="keyword">if</span> header
content = header + content
<span class="keyword">if</span> footer
content = content + footer
onTransform content
, [ file.workingPath, file.name ],
onComp<span class="reserved">let</span>e
)
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>finalize</h2>
<p>Wraps the contents of the file with a prefix and suffix before minification occurs.<br />* <em>file {String}</em>: the file to wrap<br />* <em>onComplete {Function}</em>: the function to call after wrapping has completed</p></td><td class="code"><pre><code> wrap: <span class="params">( file, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
<span class="keyword">if</span> @config.wrap <span class="keyword">and</span> @config.wrap.style
@log.onEvent <span class="string">"Wrapping <span class="subst">#{ file.name }</span>"</span>
prefix = @config.wrap.style.prefix
suffix = @config.wrap.style.suffix
@fp.transform<span class="params">(
[ file.workingPath, file.name ],
( content, onTransform )</span> <span class="function">-></span>
<span class="keyword">if</span> prefix
content = prefix + content
<span class="keyword">if</span> suffix
content = content + suffix
onTransform content
, [ file.workingPath, file.name ],
onComp<span class="reserved">let</span>e
)
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>StylePipeline</h2>
<p>The set of post-processes that happen to completed style outputs. <br />These include minification, wrapping and<br />finalization depending on the build configuration.</p></td><td class="code"><pre><code><span class="keyword">class</span> SourcePipeline
<span class="reserved">const</span>ructor: <span class="params">( @config, @fp, @minifier, @scheduler, @log )</span> <span class="function">-></span>
_.bindAll<span class="params">( <span class="keyword">this</span> )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>process</h2>
<p>Take the list of files and minify, wrap and finalize them<br />according to configuration. In the event that files are minified,<br />this function will create a seperate set of files to separate<br />processing between developer friendly and deployment friendly files.<br />* <em>files {Array}</em>: the list of files to process<br />* <em>onComplete {Array}</em>: the function to call with the list of files</p></td><td class="code"><pre><code> process: <span class="params">( files, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
forAll = @scheduler.parallel
forAll files, @wrap, <span class="params">()</span> <span class="function">-></span>
minify = []
<span class="keyword">if</span> self.config.uglify
minify = _.map<span class="params">( files, ( x )</span> <span class="function">-></span> _.clone x )
forAll files, self.finalize, <span class="params">()</span> <span class="function">-></span>
self.log.onStep <span class="string">"Finalizing source files"</span>
forAll minify, self.minify, <span class="params">()</span> <span class="function">-></span>
<span class="keyword">if</span> minify.length > <span class="number">0</span>
self.log.onStep <span class="string">"Minifying source files"</span>
forAll minify, self.finalize, <span class="params">()</span> <span class="function">-></span>
onComp<span class="reserved">let</span>e<span class="params">( files.concat minify )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>minify</h2>
<p>Uses the uglify lib to minify the output source<br />* <em>file {String}</em>: the file to minify<br />* <em>onComplete {Function}</em>: the function to call after minification has completed</p></td><td class="code"><pre><code> minify: <span class="params">( file, onComplete )</span> <span class="function">-></span>
exclusions = @config.uglify?.exclude || []
isExcluded = _.any exclusions, <span class="params">( x )</span> <span class="function">-></span> x == file.name
<span class="keyword">if</span> @config.uglify <span class="keyword">and</span> <span class="keyword">not</span> isExcluded
self = <span class="keyword">this</span>
ext = file.ext<span class="params">()</span>
newFile = file.name.replace ext, <span class="string">".min.js"</span>
@log.onEvent <span class="string">"Minifying <span class="subst">#{ newFile }</span>"</span>
@fp.transform<span class="params">(
[ file.workingPath, file.name ],
( content, onTransform )</span> <span class="function">-></span>
self.minifier content, <span class="params">( err, result )</span> <span class="function">-></span>
<span class="keyword">if</span> err
self.log.onError <span class="string">"Error minifying <span class="subst">#{ file.name }</span> : \r\n\t <span class="subst">#{ err }</span>"</span>
result = content
onTransform<span class="params">( result )</span>
, [ file.workingPath, newFile ],
<span class="params">()</span> <span class="function">-></span>
file.name = newFile
onComp<span class="reserved">let</span>e<span class="params">()</span>
)
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>finalize</h2>
<p>Finalize, for lack of a better term, puts header and footer content around the file's contents.<br />This step is different than wrapping because it happens AFTER minification and won't get<br />mangled as a result.<br />* <em>file {String}</em>: the file to finalize<br />* <em>onComplete {Function}</em>: the function to call after finalization has completed</p></td><td class="code"><pre><code> finalize: <span class="params">( file, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
<span class="keyword">if</span> @config.finalize <span class="keyword">and</span> @config.finalize.source
@log.onEvent <span class="string">"Finalizing <span class="subst">#{ file.name }</span>"</span>
header = @config.finalize.source.header
footer = @config.finalize.source.footer
@fp.transform<span class="params">(
[ file.workingPath, file.name ],
( content, onTransform )</span> <span class="function">-></span>
<span class="keyword">if</span> header
content = header + content
<span class="keyword">if</span> footer
content = content + footer
onTransform content
, [ file.workingPath, file.name ],
<span class="params">()</span> <span class="function">-></span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
)
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>finalize</h2>
<p>Wraps the contents of the file with a prefix and suffix before minification occurs.<br />* <em>file {String}</em>: the file to wrap<br />* <em>onComplete {Function}</em>: the function to call after wrapping has completed</p></td><td class="code"><pre><code> wrap: <span class="params">( file, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
<span class="keyword">if</span> @config.wrap <span class="keyword">and</span> @config.wrap.source
@log.onEvent <span class="string">"Wrapping <span class="subst">#{ file.name }</span>"</span>
prefix = @config.wrap.source.prefix
suffix = @config.wrap.source.suffix
@fp.transform<span class="params">(
[ file.workingPath, file.name ],
( content, onTransform )</span> <span class="function">-></span>
<span class="keyword">if</span> prefix
content = prefix + content
<span class="keyword">if</span> suffix
content = content + suffix
onTransform content
, [ file.workingPath, file.name ],
<span class="params">()</span> <span class="function">-></span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
)
<span class="keyword">else</span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>MarkupPipeline</h2>
<p>Provides is a placeholder as there are currently<br />no post-process steps for markup.</p></td><td class="code"><pre><code><span class="keyword">class</span> MarkupPipeline
<span class="reserved">const</span>ructor: <span class="params">()</span> <span class="function">-></span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>PostProcessor</h2>
<p>A provider abstraction around post-process steps for each resource<br />type that allows Anvil to have a 'branchless' pipeline for all <br />resource types</p></td><td class="code"><pre><code><span class="keyword">class</span> PostProcessor
<span class="reserved">const</span>ructor: <span class="params">( @config, @fp, @scheduler, @log )</span> <span class="function">-></span>
uglify = <span class="params">( source, callback )</span> <span class="function">-></span>
<span class="keyword">try</span>
ast = jsp.parse source
ast = pro.ast_mangle ast
ast = pro.ast_squeeze ast
callback <span class="literal">undefined</span>, pro.gen_code ast
<span class="keyword">catch</span> err
callback err, <span class="string">""</span>
@style = <span class="keyword">new</span> StylePipeline @config, @fp, cssminifier, @scheduler, @log
@source = <span class="keyword">new</span> SourcePipeline @config, @fp, uglify, @scheduler, @log
@markup = {
process: <span class="params">( files, onComplete )</span> <span class="function">-></span> onComp<span class="reserved">let</span>e files
}
<span class="reserved">export</span>s.postProcessor = PostProcessor</code></pre></td></tr><tr class="block"><td class="comment"><p>docco --<br />See <a href='http://jashkenas.github.com/docco/'>http://jashkenas.github.com/docco/</a><br />docco = require "docco"</p></td><td class="code"></td></tr><tr class="block"><td class="comment"><p>ape --<br />See </p></td><td class="code"><pre><code>ape = require <span class="string">"ape"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>Documents</h2>
<p>A minor adaptation of @aaronmccall's docco and ape support<br />that he contributed to the prior version of Anvil.</p></td><td class="code"><pre><code><span class="keyword">class</span> Documenter
<span class="reserved">const</span>ructor: <span class="params">( @config, @fp, @scheduler, @log )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
_.bindAll<span class="params">( <span class="keyword">this</span> )</span>
<span class="keyword">if</span> @config.docs</code></pre></td></tr><tr class="block"><td class="comment"><p>if @config.docs.generator == "docco"<br />@generator = @runDocco<br />else </p></td><td class="code"><pre><code> @generator = @runApe
<span class="keyword">else</span>
@generator = <span class="params">()</span> <span class="function">-></span>
callback = Array.prototype.slice.call arguments, <span class="number">4</span>
<span class="keyword">if</span> callback
callback<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>generate</h2>
<p>Generate documents for the list of files<br />* <em>files {Array}</em>: the array of file objects to create documents for</p></td><td class="code"><pre><code> generate: <span class="params">( files )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
<span class="keyword">if</span> files &amp;&amp; files.length > <span class="number">0</span>
@log.onEvent <span class="string">"Creating annotated source for: <span class="subst">#{ _.pluck( files, 'name' ).toString() }</span>"</span>
@scheduler.parallel files, @document, <span class="params">()</span> <span class="function">-></span>
self.log.onComp<span class="reserved">let</span>e <span class="string">"Code annotation completed"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>document</h2>
<p>Generate docco/ape annotated source for the combined file<br />Thanks much to @aaronmccall for contributing this code to Anvil!<br />* <em>file {String}</em>: the file object to create the document for<br />* <em>onComplete {Function}</em>: the function to call once the documentation is done</p></td><td class="code"><pre><code> document: <span class="params">( file, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
language = ape.get_language file.name
ext = file.ext<span class="params">()</span>
newFile = file.name.replace ext, <span class="string">".html"</span>
@log.onEvent <span class="string">"Annotation for <span class="subst">#{ file.name }</span>"</span>
@fp.read [ file.workingPath, file.name ], <span class="params">( content )</span> <span class="function">-></span>
self.generator language, ext, newFile, content, <span class="params">( doc )</span> <span class="function">-></span>
self.fp.write [ self.config.docs.output, newFile ], doc, onComp<span class="reserved">let</span>e
</code></pre></td></tr><tr class="block"><td class="comment"><h2>runDoco</h2>
<p>Wraps the document generation function in docco to a standard call format<br />runDocco: ( language, extension, newFile, code, onComplete ) -><br />docco.generate_doc_from_string newFile, code, extension, ( result ) -> onComplete result</p></td><td class="code"></td></tr><tr class="block"><td class="comment"><h2>runApe</h2>
<p>Wraps the document generation function in docco to a standard call format</p></td><td class="code"><pre><code> runApe: <span class="params">( language, extension, newFile, code, onComplete )</span> <span class="function">-></span>
ape.generate_doc code, language, <span class="string">'html'</span>, <span class="literal">null</span>, <span class="params">( err, result )</span> <span class="function">-></span> onComp<span class="reserved">let</span>e result
</code></pre></td></tr><tr class="block"><td class="comment"><h2>Anvil</h2>
<p>This provides the primary logic and flow control for build activities</p></td><td class="code"><pre><code><span class="keyword">class</span> Anvil
<span class="reserved">const</span>ructor: <span class="params">( @fp, @compiler, @combiner, @documenter, @scheduler, @postProcessor, @log, @callback )</span> <span class="function">-></span>
@buildNumber = <span class="number">0</span>
@inProcess = <span class="literal">false</span>
extensions: [ <span class="string">".js"</span>, <span class="string">".coffee"</span>, <span class="string">".html"</span>, <span class="string">".haml"</span>, <span class="string">".markdown"</span>, <span class="string">".md"</span>, <span class="string">".css"</span>, <span class="string">".styl"</span>, <span class="string">".less"</span>, <span class="string">".css"</span> ]
</code></pre></td></tr><tr class="block"><td class="comment"><h2>build</h2>
<p>Kicks off the build for the currently configured Anvil instance</p></td><td class="code"><pre><code> build: <span class="params">( config )</span> <span class="function">-></span>
<span class="keyword">if</span> <span class="keyword">not</span> @inProcess
@initialize<span class="params">( config )</span>
@log.onStep <span class="string">"Build <span class="subst">#{ @buildNumber }</span> initiated"</span>
@inProcess = <span class="literal">true</span>
@buildSource<span class="params">()</span>
@buildStyle<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>buildMarkup</h2>
<p>Builds all markup sources and provides the regex patterns used to<br />identify dependencies using regular expressions.</p></td><td class="code"><pre><code> buildMarkup: () ->
findPatterns = [ ///[\&lt;][!][-]{2}.?import[(]?.?['\"].*['\"].?[)]?.?[-]{2}[\>]///g ]
replacePatterns = [ ///([ \t]*)[\&lt;][!][-]{2}.?import[(]?.?['\"]replace['\"].?[)]?.?[-]{2}[\>]///g ]
@processType( "markup", findPatterns, replacePatterns )
</code></pre></td></tr><tr class="block"><td class="comment"><h2>buildSource</h2>
<p>Builds all JS and Coffee sources and provides the regex patterns used to<br />identify dependencies using regular expressions.</p></td><td class="code"><pre><code> buildSource: () ->
findPatterns = [ ///([/]{2}|[\#]{3}).?import.?[(]?.?[\"'].*[\"'].?[)]?[;]?.?([\#]{0,3})///g ]
replacePatterns = [ ///([ \t]*)([/]{2}|[\#]{3}).?import.?[(]?.?[\"']replace[\"'].?[)]?[;]?.?[\#]{0,3}///g ]
@processType( "source", findPatterns, replacePatterns )
</code></pre></td></tr><tr class="block"><td class="comment"><h2>buildSource</h2>
<p>Builds all CSS, LESS and Stylus sources and provides the regex patterns used to<br />identify dependencies using regular expressions.</p></td><td class="code"><pre><code> buildStyle: () ->
findPatterns = [ ///([/]{2}|[/][*]).?import[(]?.?[\"'].*[\"'].?[)]?([*][/])?///g ]
replacePatterns = [ ///([ \t]*)([/]{2}|[/][*]).?import[(]?.?[\"']replace[\"'].?[)]?([*][/])?///g ]
@processType( "style", findPatterns, replacePatterns )
</code></pre></td></tr><tr class="block"><td class="comment"><h2>initialize</h2>
<p>Initializes state for the build</p></td><td class="code"><pre><code> initialize: <span class="params">( config )</span> <span class="function">-></span>
@config = config
@filesBuilt = {}</code></pre></td></tr><tr class="block"><td class="comment"><p>mini FSM - basically we don't want to start building markup until<br />everything else is done since markup can import other built resources</p></td><td class="code"><pre><code> @steps =
source: <span class="literal">false</span>
style: <span class="literal">false</span>
markup: <span class="literal">false</span>
hasSource: config.source
hasStyle: config.style
hasMarkup: config.markup
markupReady: <span class="params">()</span> <span class="function">-></span> <span class="params">( <span class="keyword">this</span>.source <span class="keyword">or</span> <span class="keyword">not</span> <span class="keyword">this</span>.hasSource )</span> <span class="keyword">and</span> <span class="params">( <span class="keyword">this</span>.style <span class="keyword">or</span> <span class="keyword">not</span> <span class="keyword">this</span>.hasStyle )</span>
allDone: <span class="params">()</span> <span class="function">-></span>
status = <span class="params">( <span class="keyword">this</span>.source <span class="keyword">or</span> <span class="keyword">not</span> <span class="keyword">this</span>.hasSource )</span> <span class="keyword">and</span> <span class="params">( <span class="keyword">this</span>.style <span class="keyword">or</span> <span class="keyword">not</span> <span class="keyword">this</span>.hasStyle )</span> <span class="keyword">and</span> <span class="params">( <span class="keyword">this</span>.markup <span class="keyword">or</span> <span class="keyword">not</span> <span class="keyword">this</span>.hasMarkup )</span>
status
</code></pre></td></tr><tr class="block"><td class="comment"><h2>processType</h2>
<p>The steps that get followed for each resource type are the same.<br />This function provides the core behavior of identifying, combining,<br />compiling and post-processing for all the types.<br />* <em>type {String}</em>: ('source', 'style', 'markup') the type of resources to process<br />* <em>findPatterns {Regex}</em>: the list of regular expressions used to identify imports in this resource type<br />* <em>replacePatterns {Regex}</em>: the list of replacement regular expressions used to replace imports with file contents</p></td><td class="code"><pre><code> processType: <span class="params">( type, findPatterns, replacePatterns )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
forAll = @scheduler.parallel
compiler = @compiler
combiner = <span class="keyword">new</span> @combiner<span class="params">( @fp, @scheduler, findPatterns, replacePatterns )</span>
postProcessor = @postProcessor
@log.onStep <span class="string">"Starting <span class="subst">#{ type }</span> pipe-line"</span>
self.prepFiles type, <span class="params">( list )</span> <span class="function">-></span>
<span class="keyword">if</span> list <span class="keyword">and</span> list.length > <span class="number">0</span>
self.copyFiles list, <span class="params">()</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>combines imported files</p></td><td class="code"><pre><code> self.log.onStep <span class="string">"Combining <span class="subst">#{ type }</span> files"</span>
combiner.combineList list, <span class="params">()</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>filter out all files that were combined into another file</p></td><td class="code"><pre><code> final = _.filter<span class="params">( list, ( x )</span> <span class="function">-></span> x.dependents == <span class="number">0</span> )</code></pre></td></tr><tr class="block"><td class="comment"><p>if documentation should be generated, do that now</p></td><td class="code"><pre><code> <span class="keyword">if</span> self.config.docs
self.documenter.generate final</code></pre></td></tr><tr class="block"><td class="comment"><p>compiles the combined results</p></td><td class="code"><pre><code> self.log.onStep <span class="string">"Compiling <span class="subst">#{ type }</span> files"</span>
forAll final, compiler.compile, <span class="params">( compiled )</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>kick off post processors for compiled files</p></td><td class="code"><pre><code> self.log.onStep <span class="string">"Post-process <span class="subst">#{ type }</span> files"</span>
postProcessor[ type ].process compiled, <span class="params">( list )</span> <span class="function">-></span></code></pre></td></tr><tr class="block"><td class="comment"><p>copy complete files to the destination folders</p></td><td class="code"><pre><code> self.log.onStep <span class="string">"Moving <span class="subst">#{ type }</span> files to destinations"</span>
self.finalOutput list, <span class="params">()</span> <span class="function">-></span>
self.stepComp<span class="reserved">let</span>e type
<span class="keyword">else</span>
self.stepComp<span class="reserved">let</span>e type
</code></pre></td></tr><tr class="block"><td class="comment"><h2>finalOutput</h2>
<p>Copies the final list of files to their output folders<br />* <em>files {Array}</em>: the list of files to copy<br />* <em>onComplete {Function}</em>: the function to call once all files have been copied</p></td><td class="code"><pre><code> finalOutput: <span class="params">( files, onComplete )</span> <span class="function">-></span>
fp = @fp
names = @config.name
forAll = @scheduler.parallel
copy = <span class="params">( file, done )</span> <span class="function">-></span>
forAll<span class="params">( file.outputPaths, ( destination, moved )</span> <span class="function">-></span>
outputName = file.name
<span class="keyword">if</span> names
<span class="keyword">if</span> _.isString names
outputName = names
<span class="keyword">else</span>
custom = names[ file.name ]
outputName = custom <span class="keyword">or</span>= outputName
fp.copy [ file.workingPath, file.name ], [ destination, outputName ], moved
, done )
forAll files, copy, onComp<span class="reserved">let</span>e
</code></pre></td></tr><tr class="block"><td class="comment"><h2>copyFiles</h2>
<p>Copies the source files to the working path before beginning any processing<br />* <em>files {Array}</em>: the list of files to copy<br />* <em>onComplete {Function}</em>: the function to call once all files have been copied</p></td><td class="code"><pre><code> copyFiles: <span class="params">( files, onComplete )</span> <span class="function">-></span>
fp = @fp
copy = <span class="params">( file, done )</span> <span class="function">-></span>
fp.ensurePath file.workingPath, <span class="params">()</span> <span class="function">-></span>
fp.copy file.fullPath, [ file.workingPath, file.name ], done
@scheduler.parallel files, copy, onComp<span class="reserved">let</span>e
</code></pre></td></tr><tr class="block"><td class="comment"><h2>cleanWorking</h2>
<p>Clears all files from the working directory<br />* <em>onComplete {Function}</em>: the function to call after directory is cleaned</p></td><td class="code"><pre><code> cleanWorking: <span class="params">( onComplete )</span> <span class="function">-></span>
fp = @fp
forAll = @scheduler.parallel
fp.getFiles @config.working, <span class="params">( files )</span> <span class="function">-></span>
forAll files, fp.de<span class="reserved">let</span>e, <span class="params">()</span> <span class="function">-></span>
onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>prepFiles</h2>
<p>Determine the list of files that belong to this particular resource type<br />and create metadata objects that describe the file and provide necessary<br />metadata to the rest of the processes.<br />* <em>type {String}</em>: ('source', 'style', 'markup') <br />* <em>onComplete {Function}</em>: the function to invoke with a completed list of file metadata</p></td><td class="code"><pre><code> prepFiles: <span class="params">( type, onComplete )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
workingBase = @config.working
typePath = @config[ type ]
output = @config.output[ type ]
output = <span class="keyword">if</span> _.isArray<span class="params">( output )</span> <span class="keyword">then</span> output <span class="keyword">else</span> [ output ]
log = @log
@fp.getFiles typePath, <span class="params">( files )</span> <span class="function">-></span>
log.onEvent <span class="string">"Found <span class="subst">#{ files.length }</span> <span class="subst">#{ type }</span> files ..."</span>
list = <span class="keyword">for</span> file <span class="keyword">in</span> files
name = path.basename file
relative = path.dirname<span class="params">( file.replace( typePath, <span class="string">""</span>)</span> )
working = self.fp.buildPath<span class="params">( workingBase, relative )</span>
{
dependents: <span class="number">0</span>
ext: <span class="params">()</span> <span class="function">-></span> path.extname <span class="keyword">this</span>.name
fullPath: file
<span class="reserved">import</span>s: []
name: name
originalName: name
outputPaths: output
relativePath: relative
workingPath: working
}
filtered = _.filter list, <span class="params">( x )</span> <span class="function">-></span> _.any self.extensions, <span class="params">( y )</span> <span class="function">-></span> y == x.ext<span class="params">()</span>
onComp<span class="reserved">let</span>e filtered
</code></pre></td></tr><tr class="block"><td class="comment"><h2>stepComplete</h2>
<p>Called at the end of each type's pipe-line in order to control<br />when markup gets built. Markup must get built last since it can include<br />built targets from both style and source in it's files.<br />* <em>step {String}</em>: ('source','style','markup')</p></td><td class="code"><pre><code> stepComp<span class="reserved">let</span>e: <span class="params">( step )</span> <span class="function">-></span>
@steps[ step ] = <span class="literal">true</span>
<span class="keyword">if</span> step != <span class="string">"markup"</span> <span class="keyword">and</span> @steps.markupReady<span class="params">()</span>
@buildMarkup<span class="params">()</span>
<span class="keyword">if</span> step == <span class="string">"markup"</span> <span class="keyword">and</span> @steps.allDone<span class="params">()</span>
@inProcess = <span class="literal">false</span>
@cleanWorking @callback
</code></pre></td></tr><tr class="block"><td class="comment"><h2>Continuous</h2>
<p>Provides a way to trigger the build on file change</p></td><td class="code"><pre><code><span class="keyword">class</span> Continuous
<span class="reserved">const</span>ructor: <span class="params">( @fp, @config, @onChange )</span> <span class="function">-></span>
@style = @normalize @config.style
@source = @normalize @config.source
@markup = @normalize @config.markup
@spec = @normalize @config.spec
@watchers = []
@watching = <span class="literal">false</span>
_.bindAll<span class="params">( <span class="keyword">this</span> )</span>
<span class="keyword">this</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>normalize</h2>
<p>Takes an input and, if it is an array, returns the plain array<br />if the input is not an array, it turns it into a single element array<br />* <em>x {Object}</em>: anything</p></td><td class="code"><pre><code> normalize: <span class="params">( x )</span> <span class="function">-></span> <span class="keyword">if</span> _.isArray x <span class="keyword">then</span> x <span class="keyword">else</span> [ x ]
</code></pre></td></tr><tr class="block"><td class="comment"><h2>setup</h2>
<p>Determines which directories should cause a build to trigger<br />if any contents change</p></td><td class="code"><pre><code> setup: <span class="params">()</span> <span class="function">-></span>
<span class="keyword">if</span> <span class="keyword">not</span> @watching
@watching = <span class="literal">true</span>
<span class="keyword">if</span> @style <span class="keyword">then</span> @watchPath p <span class="keyword">for</span> p <span class="keyword">in</span> @style
<span class="keyword">if</span> @source <span class="keyword">then</span> @watchPath p <span class="keyword">for</span> p <span class="keyword">in</span> @source
<span class="keyword">if</span> @markup <span class="keyword">then</span> @watchPath p <span class="keyword">for</span> p <span class="keyword">in</span> @markup
<span class="keyword">if</span> @spec <span class="keyword">then</span> @watchPath p <span class="keyword">for</span> p <span class="keyword">in</span> @spec
</code></pre></td></tr><tr class="block"><td class="comment"><h2>watchpath</h2>
<p>Calls watchFiles for all files in the path<br />* <em>path {String/Array}</em>: the path specification to watch for changes in</p></td><td class="code"><pre><code> watchPath: <span class="params">( path )</span> <span class="function">-></span>
@fp.getFiles path, @watchFiles
</code></pre></td></tr><tr class="block"><td class="comment"><h2>watchFiles</h2>
<p>Creates a file watcher instance for all files in the list<br />* <em>files {Array}</em>: the list of files to watch for changes in</p></td><td class="code"><pre><code> watchFiles: <span class="params">( files )</span> <span class="function">-></span>
<span class="keyword">for</span> file <span class="keyword">in</span> files
@watchers.push fs.watch file, @onEvent
</code></pre></td></tr><tr class="block"><td class="comment"><h2>onEvent</h2>
<p>This handler triggers the build and closes all watchers in the event <br />of a change. This is necessary to prevent event storms that can trigger <br />during the build process.<br />* <em>event {Object}</em>: the event that fired on the file system<br />* <em>file {String}</em>: the file that triggered the change</p></td><td class="code"><pre><code> onEvent: <span class="params">( event, file )</span> <span class="function">-></span>
<span class="keyword">if</span> @watching
@watching = <span class="literal">false</span>
<span class="keyword">while</span> @watchers.length > <span class="number">0</span>
@watchers.pop<span class="params">()</span>.close<span class="params">()</span>
@onChange<span class="params">()</span>
Mocha = require <span class="string">"mocha"</span>
_ = require <span class="string">"underscore"</span>
reporters = Mocha.reporters
interfaces = Mocha.interfaces
Context = Mocha.Context
Runner = Mocha.Runner
Suite = Mocha.Suite
path = require <span class="string">"path"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><pre><code>This class is an adaptation of the code found in _mocha
from TJ Holowaychuk's Mocha repository:
<a href='https://github.com/visionmedia/mocha/blob/master/bin/_mocha'>https://github.com/visionmedia/mocha/blob/master/bin/_mocha</a>
</code></pre></td><td class="code"><pre><code><span class="keyword">class</span> MochaRunner
<span class="reserved">const</span>ructor: <span class="params">( @fp, @scheduler, @config, @onComplete )</span> <span class="function">-></span>
_.bindAll<span class="params">( <span class="keyword">this</span> )</span>
run: <span class="params">()</span> <span class="function">-></span>
self = <span class="keyword">this</span>
<span class="keyword">if</span> @config.spec
forAll = @scheduler.parallel
opts = @config.mocha <span class="keyword">or</span>=
growl: <span class="literal">true</span>
ignoreLeaks: <span class="literal">true</span>
reporter: <span class="string">"spec"</span>
ui: <span class="string">"bdd"</span>
colors: <span class="literal">true</span>
reporterName = opts.reporter.toLowerCase<span class="params">()</span>.replace<span class="params">( ///([a-z])</span>///, <span class="params">( x )</span> <span class="function">-></span> x.toUpperCase<span class="params">()</span> )
uiName = opts.ui.toLowerCase<span class="params">()</span>
mocha = <span class="keyword">new</span> Mocha<span class="params">( {
ui: uiName
ignoreLeaks: <span class="literal">true</span>
colors: opts.colors
growl: opts.growl
slow: opts.slow
timeout: opts.timeout
} )</span>
mocha.reporter<span class="params">(reporterName)</span>
specs = <span class="keyword">if</span> _.isString @config.spec <span class="keyword">then</span> [ @config.spec ] <span class="keyword">else</span> @config.spec
forAll specs, @fp.getFiles, <span class="params">( lists )</span> <span class="function">-></span>
files = _.flatten lists
<span class="keyword">for</span> file <span class="keyword">in</span> files
de<span class="reserved">let</span>e require.cache[ file ]
mocha.addFile file
mocha.run <span class="params">()</span> <span class="function">-></span>
self.onComp<span class="reserved">let</span>e<span class="params">()</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>SocketServer</h2>
<p>Class to manage client notifications via socket.io</p></td><td class="code"><pre><code><span class="keyword">class</span> SocketServer
<span class="reserved">const</span>ructor: <span class="params">( app )</span> <span class="function">-></span>
_.bindAll<span class="params">( <span class="keyword">this</span> )</span>
@clients = []
@io = require<span class="params">( <span class="string">"socket.io"</span> )</span>.listen<span class="params">(app)</span>
@io.set <span class="string">"log level"</span>, <span class="number">1</span></code></pre></td></tr><tr class="block"><td class="comment"><p>When a "connection" event occurs, call <em>@addClient</em></p></td><td class="code"><pre><code> @io.sockets.<span class="literal">on</span> <span class="string">"connection"</span>, @addClient
</code></pre></td></tr><tr class="block"><td class="comment"><h2>addClient</h2>
<p>Adds a new client to be notified upon change to watched files</p>
<h3>Args:</h3>
<ul>
<li><em>socket {Object}</em>: Socket object that is generated by a socket.io
connection event.</li>
</ul></td><td class="code"><pre><code> addClient: <span class="params">( socket )</span> <span class="function">-></span>
@clients.push socket
socket.<span class="literal">on</span> <span class="string">"end"</span>, @removeClient
socket.<span class="literal">on</span> <span class="string">"disconnect"</span>, @removeClient
log.onEvent <span class="string">"client connected"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>removeClient</h2>
<p>Removes the socket from the current list of connected sockets<br />* <em>socket {Object}</em>: the socket that has disconnected</p></td><td class="code"><pre><code> removeClient: <span class="params">( socket )</span> <span class="function">-></span>
index = @clients.indexOf socket
@clients.splice index, <span class="number">1</span>
log.onEvent <span class="string">"client disconnected"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>refreshClient</h2>
<p>Sends a 'refresh' message to all connected clients</p></td><td class="code"><pre><code> refreshClients: <span class="function">-></span>
log.onEvent <span class="string">"Refreshing hooked clients"</span>
@notifyClients <span class="string">"refresh"</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>notifyClients</h2>
<p>Send a message to all connected clients<br />* <em>msg {String}</em>: the message to send to connected clients</p></td><td class="code"><pre><code> notifyClients: <span class="params">( msg )</span> <span class="function">-></span>
<span class="keyword">for</span> client <span class="keyword">in</span> @clients
client.emit msg, {}
express = require <span class="string">'express'</span>
</code></pre></td></tr><tr class="block"><td class="comment"><h2>Host</h2>
<p>This class provides a simple static HTTP server<br />that can support all supported files types for Anvil<br />builds</p></td><td class="code"><pre><code><span class="keyword">class</span> Host
<span class="reserved">const</span>ructor: <span class="params">( @fp, @scheduler, @compiler, @config )</span> <span class="function">-></span>
self = <span class="keyword">this</span>
_.bindAll<span class="params">( <span class="keyword">this</span> )</span>
@app = express.createServer<span class="params">()</span>
app = @app
app.use express.bodyParser<span class="params">()</span>
app.use app.router
hosts = @config.hosts</code></pre></td></tr><tr class="block"><td class="comment"><p>if the user told us what to do, make no assumptions<br />only host exactly what they specify</p></td><td class="code"><pre><code> <span class="keyword">if</span> hosts
_.each<span class="params">( hosts, ( value, key )</span> <span class="function">-></span>
app.use key, express.static<span class="params">( path.resolve value )</span>
)</code></pre></td></tr><tr class="block"><td class="comment"><p>otherwise, let's have some fun...</p></td><td class="code"><pre><code> <span class="keyword">else</span>
output = @config.output
target = <span class="string">""</span>
<span class="keyword">if</span> @config.markup <span class="comment"># this is a site</span>
<span class="keyword">if</span> _.isString output
target = output
<span class="keyword">else</span> <span class="keyword">if</span> _.isArray output
target = output[ <span class="number">0</span> ]
<span class="keyword">else</span>
target = output.markup
<span class="keyword">else</span> <span class="comment"># this is a lib</span>
<span class="keyword">if</span> _.isString output
target = output
<span class="keyword">else</span> <span class="keyword">if</span> _.isArray output
target = output[ <span class="number">0</span> ]
<span class="keyword">else</span>
target = output.source
app.use <span class="string">"/"</span>, express.static<span class="params">( path.resolve target )</span>
<span class="keyword">if</span> @config.ext
app.use <span class="string">"/ext"</span>, express.static<span class="params">( path.resolve @config.ext )</span>
<span class="keyword">if</span> @config.spec
app.use <span class="string">"/spec"</span>, express.static<span class="params">( path.resolve @config.spec )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>host anvil prerequisites for supporting certain browser features out of<br />the box</p></td><td class="code"><pre><code> anvilPath = path.resolve<span class="params">( path.dirname( fs.realpathSync( __filename )</span> ), <span class="string">"../ext"</span> )
console.log <span class="string">"Hosting anvil prerequisites from <span class="subst">#{ anvilPath }</span>"</span>
app.use <span class="string">"/anvil"</span>, express.static<span class="params">( anvilPath )</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>if a static file type is requested that fits an extension we know how to<br />compile, use the compiler to translate it on-the-fly</p></td><td class="code"><pre><code> app.get ///.*[.]<span class="params">(coffee|kup|less|styl|md|markdown|haml)</span>///, <span class="params">( req, res )</span> <span class="function">-></span>
fileName = <span class="string">".<span class="subst">#{ req.url }</span>"</span>
ext = path.extname fileName
mimeType = self.contentTypes[ ext ]
res.header <span class="string">'Content-Type'</span>, mimeType
self.fp.read fileName, <span class="params">( content )</span> <span class="function">-></span>
self.compiler.compilers[ ext ] content, <span class="params">( compiled )</span> <span class="function">-></span>
res.send compiled
port = <span class="keyword">if</span> @config.port <span class="keyword">then</span> @config.port <span class="keyword">else</span> <span class="number">3080</span>
app.listen port
contentTypes:
<span class="string">".coffee"</span>: <span class="string">"application/javascript"</span>
<span class="string">".less"</span>: <span class="string">"text/css"</span>
<span class="string">".styl"</span>: <span class="string">"text/css"</span>
<span class="string">".md"</span>: <span class="string">"text/html"</span>
<span class="string">".markdown"</span>: <span class="string">"text/html"</span>
<span class="string">".haml"</span>: <span class="string">"text/html"</span>
<span class="string">".kup"</span>: <span class="string">"text/html"</span></code></pre></td></tr><tr class="block"><td class="comment"><h1>Cli</h1>
<p>Provides the command line interface for interacting with Anvil and related modules</p></td><td class="code"><pre><code><span class="keyword">class</span> Cli
<span class="reserved">const</span>ructor: <span class="params">()</span> <span class="function">-></span>
@anvil = {}
@ci = <span class="literal">undefined</span>
@documenter = <span class="literal">undefined</span>
@mochaRunner = <span class="literal">undefined</span>
@socketServer = {}
@postProcessor = {}
@log = log
@scheduler = <span class="keyword">new</span> Scheduler<span class="params">()</span>
@crawler = <span class="keyword">new</span> FSCrawler @scheduler
@fp = <span class="keyword">new</span> FSProvider @crawler, @log
@configuration = <span class="keyword">new</span> Configuration @fp, @scheduler, @log
@compiler = <span class="keyword">new</span> Compiler @fp, @log
_.bindAll <span class="keyword">this</span>
initCI: <span class="params">( config )</span> <span class="function">-></span>
@ci = <span class="keyword">new</span> Continuous @fp, config, @onFileChange
initHost: <span class="params">( config )</span> <span class="function">-></span>
@server = <span class="keyword">new</span> Host @fp, @scheduler, @compiler, config
@socketServer = <span class="keyword">new</span> SocketServer @server.app
@log.onStep <span class="string">"Static HTTP server listening on port <span class="subst">#{ config.port }</span>"</span>
initMocha: <span class="params">( config )</span> <span class="function">-></span>
@mochaRunner = <span class="keyword">new</span> MochaRunner @fp, @scheduler, config, @onTestsComp<span class="reserved">let</span>e
notifyHttpClients: <span class="params">()</span> <span class="function">-></span>
<span class="keyword">if</span> @socketServer.refreshClients
@log.onStep <span class="string">"Notifying clients of build completion"</span>
@socketServer.refreshClients<span class="params">()</span>
onBuildComp<span class="reserved">let</span>e: <span class="params">()</span> <span class="function">-></span>
self = <span class="keyword">this</span>
@log.onComp<span class="reserved">let</span>e <span class="string">"Build <span class="subst">#{ @anvil.buildNumber++ }</span> completed"</span>
<span class="keyword">if</span> self.mochaRunner</code></pre></td></tr><tr class="block"><td class="comment"><p>wrap the mocha runner invocation in a timeout call<br />to prevent odd timing issues.</p></td><td class="code"><pre><code> self.log.onStep <span class="string">"Running specifications with Mocha"</span>
self.mochaRunner.run<span class="params">()</span>
<span class="keyword">else</span>
self.startCI<span class="params">()</span>
self.notifyHttpClients<span class="params">()</span>
onConfig: <span class="params">( config, stop )</span> <span class="function">-></span>
@config = config</code></pre></td></tr><tr class="block"><td class="comment"><p>if stop comes back, then this is not a build and we're done</p></td><td class="code"><pre><code> <span class="keyword">if</span> stop <span class="keyword">then</span> process.exit <span class="number">0</span>
</code></pre></td></tr><tr class="block"><td class="comment"><p>if the user wants CI, setup the continuous module</p></td><td class="code"><pre><code> <span class="keyword">if</span> config.continuous <span class="keyword">then</span> @initCI config
</code></pre></td></tr><tr class="block"><td class="comment"><p>if the user wants mocha to run after the build, setup the mocha runner</p></td><td class="code"><pre><code> <span class="keyword">if</span> config.mocha <span class="keyword">then</span> @initMocha config
</code></pre></td></tr><tr class="block"><td class="comment"><p>if the user wants hosting then, spin up the Static HTTP host and socket server</p></td><td class="code"><pre><code> <span class="keyword">if</span> config.host <span class="keyword">then</span> @initHost config
</code></pre></td></tr><tr class="block"><td class="comment"><p>create the post processor instance</p></td><td class="code"><pre><code> @postProcessor = <span class="keyword">new</span> PostProcessor config, @fp, @scheduler, @log
@documenter = <span class="keyword">new</span> Documenter config, @fp, @scheduler, @log
@anvil = <span class="keyword">new</span> Anvil @fp, @compiler, Combiner, @documenter, @scheduler, @postProcessor, @log, @onBuildComp<span class="reserved">let</span>e
@anvil.build<span class="params">( config )</span></code></pre></td></tr><tr class="block"><td class="comment"><p>if we're using CI, kick it off the first time</p></td><td class="code"><pre><code> @startCI<span class="params">()</span>
onFileChange: <span class="params">()</span> <span class="function">-></span>
@log.onEvent <span class="string">"File change detected, starting build"</span>
@fileChange = <span class="function">-></span>
@anvil.build<span class="params">( @config )</span>
onTestsComp<span class="reserved">let</span>e: <span class="params">()</span> <span class="function">-></span>
@log.onComp<span class="reserved">let</span>e <span class="string">"Tests completed"</span>
@startCI<span class="params">()</span>
@notifyHttpClients<span class="params">()</span>
run: <span class="params">()</span> <span class="function">-></span>
@configuration.configure process.argv, @onConfig
startCI: <span class="params">()</span> <span class="function">-></span>
<span class="keyword">if</span> @ci
@log.onStep <span class="string">"Starting file watchers"</span>
@ci.setup<span class="params">()</span>
<span class="reserved">export</span>s.run = <span class="function">-></span>
cli = <span class="keyword">new</span> Cli<span class="params">()</span>
cli.run<span class="params">()</span>
</code></pre></td></tr></table></body>