templito
Version:
Generates javascript files for underscore.js templates.
276 lines (239 loc) • 8.37 kB
text/coffeescript
events = require 'events'
fs = require 'fs'
path = require 'path'
utilities = require './utilities'
try
_ = require 'underscore'
catch e
console.warn 'Underscore could not be loaded. Precompiling templates will '+
'not work until this problem is resolved.', e
###
# OutFile
###
class OutFile
= {}
warning_message: """
/* WARNING: This file was automatically generated by templito.
* Do not manually edit this file if you plan to continue using templito.
*/\n\n
"""
constructor: (_path, ) ->
= _path
existing = OutFile.existing[]
return existing if existing
OutFile.existing[] = this
= []
= []
run_queued_appends = =>
fn = null
while not fn and .length
fn = .shift()
if not fn
# end recursion
fn = => = true
fn run_queued_appends
# Make all the needed directories for this file.
utilities.mkdirp path.dirname(), =>
, =>
run_queued_appends()
return undefined # to supress annoying vim warning
default_object_path: (object_paths..., cb) ->
defaults = []
for object_path in object_paths
parts = object_path.split '.'
_parts = [parts[0]]
# Don't initialize the namespace. If the namespace doesn't exist
# before the templates are included, that's an error.
for part in parts.slice 1
_parts.push part
part = _parts.join '.'
if part not in
_default = "#{part} || (#{part} = {});"
defaults.push _default
.push part
defaults = defaults.join '\n'
if defaults
defaults + '\n\n', cb
else
cb(null)
append_template: (name, fn, cb) ->
object_path = name.split('.').slice(0, -1).join('.')
object_path, (err) =>
throw err if err
"#{name} = #{fn};\n\n", cb
append: (text, cb) ->
do_append = (next) =>
fs.appendFile , text, , (err) ->
if err
utilities.error "Error appending to #{@path}"
throw err
cb and cb()
next and next()
if
do_append()
else
.push do_append
write: (text, cb) ->
fs.writeFile , text, , (err) ->
if err
utilities.error "Error writing to #{@path}"
throw err
cb and cb()
###
# Template
###
class Template
re_template_settings: /^\s*<!\-\-(\{[\s\S]+?\})\-\->/
node_version = utilities.version_parts(process.version)
file_options: if node_version[1] < 10 then 'utf8' else {encoding: 'utf8'}
###
# path The path to the file from the base source directory.
###
constructor: (_path, ) ->
= _path
= utilities.replace_extension(
path.basename
.extension
''
)
= utilities.to_case .function_case,
dirname = path.relative .source_dir, path.dirname()
dirname = path.join .source_dir_basename, dirname
= dirname.split path.sep
= for part in
utilities.to_case .path_case, part
= ()
get_out_file: ->
# Get rid of the source_dir from
path_parts = .slice 1
# Get the path of the output file that should be used.
compile_style = .compile_style
loop
# Find out the path to the file this template will be appended to
fpath = switch compile_style
when 'file' then path.join path_parts.concat()...
when 'directory' then path.join path_parts...
when 'combined' then [0]
# Paths directly in source_dir (no intermediate parent directories)
# Should be compiled with the style 'file' instead to avoid confusion.
if fpath is '.'
compile_style = 'file'
continue
break
ext = (if .keep_extension then .extension else '') + '.js'
fpath = utilities.replace_extension fpath, .extension, ext
new OutFile path.join(.out_dir, fpath),
compile: (cb) ->
fs.readFile , , (err, source) =>
if err
utilities.error "Error reading #{@path}"
throw err
# Get local file-level settings, if any
file_settings = source.match()
if file_settings
file_settings = file_settings[1]
file_settings = eval("(#{file_settings});")
# Remove this from the source so we don't get compile errors.
source = source.replace , ''
# Get the full template_settings object
template_settings = _.extend({}, _.templateSettings,
.template_settings, file_settings)
# Compile the template function
template_fn = _.template(source, null, template_settings)
if .template_wrapper
template_fn_source = .template_wrapper + '(' +
template_fn.source + ')'
else
template_fn_source = template_fn.source
# Get full javascript path to compiled template
template_path = [.namespace].concat(,
[]).join('.')
# Write to file
.append_template template_path, template_fn_source, =>
utilities.log "#{@path} -> #{@out_file.path}"
cb and cb()
###
# Cleans the out_dir specified by the user. By clean, we mean totally remove.
# The user will be prompted before we remove the compiled directory unless
# they have turned the unsafe_clean option on.
#
# argv The arguments object from optimist
# cb A callback for when the clean operation is done
###
clean_out_dir = (argv, cb) ->
utilities.with_prompter (prompter, close) ->
clean = (yn) ->
close()
if yn in [true, 'y', 'Y']
utilities.log "Removing #{argv.out_dir} prior to compiling..."
utilities.rmdirr(argv.out_dir, false, cb)
if argv.unsafe_clean
clean true
else
prompter.question(
"Really remove #{JSON.stringify argv.out_dir} and all its contents? (Y/n) ",
clean
)
###
# The main entry point from the cli _plate command. Does some basic sanity
# checking, performs the clean if requested and passes the rest on to _compile.
#
# argv the optimist argv object.
###
compiling = false
= (argv, cb) ->
return false if compiling
compiling = true
OutFile.existing = {}
stats_cb = utilities.group_cb ([err1], [err2]) ->
throw err if (err = err1 or err2)
compile_dir argv.source_dir, argv, ->
compiling = false
cb and cb()
cb1 = stats_cb()
srcstat = fs.stat argv.source_dir, (err, stat) ->
throw err if err
if not stat.isDirectory()
cb1(utilities.not_dir_error argv.source_dir)
else
cb1()
out_cb = stats_cb()
out_dirstat = fs.stat argv.out_dir, (err, stat) ->
if err
if err.code is 'ENOENT'
return out_cb()
else
throw err
if stat.isDirectory()
if argv.clean
clean_out_dir argv, out_cb
else
out_cb()
else
out_cb(utilities.not_dir_error argv.out_dir)
true
###
# This function recursively gathers information about the files and directory
# structure so we can properly compile the template files. Ensures we only
# compile files with the proper extension.
#
# source_dir The source dir we are compiling from.
# options The original argv object from optimist
# cb A callback
###
compile_dir = (source_dir, options, cb) ->
cb_group = utilities.group_cb cb
fs.readdir source_dir, (err, contents) ->
throw err if err
for item in contents then do (item) ->
itempath = path.join source_dir, item
fs.stat itempath, (err, stat) ->
throw err if err
if stat.isDirectory()
_cb = cb_group()
compile_dir itempath, options, _cb
else if path.extname(item) is options.extension
_cb = cb_group()
template = new Template itempath, options
template.compile _cb
# vim: et sw=2 sts=2