glug.coffee
base_path = process.cwd()
require('app-module-path').addPath("#{base_path}/node_modules")
Promise = require('bluebird')
Logdown = require('logdown')
global.l = new Logdown prefix: 'glug', alignOutput: true
l.debug = ->
if global.verbose
l.log arguments...
require_dependencies = ->
l.debug 'requiring dependencies'
new Promise (resolve, reject) ->
global.h = require('./helpers')
global.fs = require('fs')
global.path = require('path')
global.mkdirp = require('mkdirp')
global.chokidar = require('chokidar')
global.jstransformer = require('jstransformer')
global.recursive = require('fs-readdir-recursive')
global.anymatch = require('anymatch')
global.matter = require('gray-matter')
global.browser_sync = require('browser-sync')
resolve()
config = undefined
config_path = undefined
bs = undefined
transformer_names = undefined
transformers = undefined
input_dir = undefined
output_dir = undefined
paths = {}
files = {}
pipelines = {}
locals = {}
load_config = ->
new Promise (resolve, reject) ->
input_dir = config?.input_dir or 'app'
output_dir = config?.output_dir or 'public'
config_path = "#{base_path}/glug-config.coffee"
try
config = require(config_path)
catch error
throw error + "\nConfig file not found at #{config_path}"
resolve()
start_browser_sync = ->
bs = browser_sync.create()
bs.init h.merge({
server: output_dir
}, config.server)
load_transformer = (name) ->
new Promise (resolve, reject) ->
l.debug "Requiring JSTransformer: **#{name}**"
try
transformers[name] = jstransformer(require("jstransformer-#{name}"))
resolve()
catch error
reject l.error """
#{error}
Try running `npm install --save jstransformer-#{name}`
or `yarn add jstransformer-#{name}`
""".replace('Error: ', '')
handle = (err) ->
if err?
if err.stack?
err = err.stack
console.error(err)
load_all_transformers = ->
transformer_names = config.transformers
transformers = {}
Promise.map h.to_array(transformer_names), (transformer) ->
load_transformer(transformer.name)
generate_file_list = ->
new Promise (resolve, reject) ->
l.debug 'Making file list...'
all_files = recursive(input_dir)
for file in all_files
files[file] = {}
files[file].is_rendered = false
files[file].in_format = path.extname(file).replace('.', '')
for pipeline_name, pipeline of config.pipelines
for tier_name, tier of pipeline
for glob, transforms of tier
if anymatch(glob, file)
# Convert transforms to array if necessary
if typeof transforms is 'string'
transforms = [transforms]
if transforms.length is 0
transforms = ['copy']
files[file].out_format ||= files[file].in_format
else
last_transformer = transformers[h.last(transforms).data]
files[file].out_format = last_transformer.outputFormat
files[file].pipeline = pipeline_name
pipelines[pipeline_name] ||= {}
pipelines[pipeline_name][tier_name] ||= {}
pipelines[pipeline_name][tier_name][file] ||= {}
file_ref = pipelines[pipeline_name][tier_name][file]
file_ref.transforms ||= []
file_ref.transforms = file_ref.transforms.concat transforms
files[file].out_format ||= files[file].in_format
files[file].out_path = file.replace(
files[file].in_format,
files[file].out_format
)
l.debug "Files: #{h.json files}"
l.debug "Pipelines: #{h.json pipelines}"
l.debug 'File list is finished'
resolve()
prepare_output_dir = ->
new Promise (resolve, reject) ->
try
h.rm_dir output_dir
unless fs.existsSync(output_dir)
fs.mkdir(output_dir)
resolve()
catch err
reject err
resolve_locals = ->
promises = {}
for local_name, local of config.locals
# if it is a promise
if local.then?
promises[local_name] = local
console.log('waiting for locals to resolve...')
# after all local promises are resolved
Promise.props(promises)
.then (res) ->
locals = res
console.log('locals finished')
.catch handle
start_config_watcher = ->
chokidar.watch(config_path).on 'change', ->
load_config()
.then prepare_output_dir
.then load_all_transformers
.then generate_file_list
.then render_all
.then -> bs.reload('*')
.catch handle
start_watcher = ->
chokidar.watch(input_dir, {}).on 'all', (event, file) ->
if event is 'change' or event is 'remove' # or event is 'add'
file = file.replace input_dir + '/', ''
file = file.replace input_dir + '\\', ''
pipeline = files[file].pipeline
# out_format = files[file].out_format
out_format = pipeline.toLowerCase()
files[file].is_rendered = false
l.debug "`#{file}` #{event}d"
l.debug "Pipeline to reload is #{pipeline}"
render_pipeline(name: pipeline, data: pipelines[pipeline])
.then ->
console.log "hi"
bs.reload("*.#{out_format}")
.catch handle
render = (file, contents, transform, settings = {}) ->
l.debug "Rendering **#{file.name}** with `#{transform}`."
new Promise (resolve, reject) ->
bad_things = ['', undefined, null]
if bad_things.includes contents
l.warn("I want to render #{file.name}
with #{transform},
but I can't because #{file.name} is #{contents}")
return resolve('')
if typeof contents isnt 'string'
l.warn("The contents of #{file.name}
is of type #{typeof contents}, and is:
#{h.json contents}")
file_data = matter(contents)
contents = file_data.content
frontmatter = file_data.data
if transform is 'copy'
return resolve(contents)
renderer = transformers[transform]
output_format = renderer.outputFormat
renderer_config = h.merge(settings,
frontmatter,
config.transformers[renderer.name])
renderer.renderAsync(contents, renderer_config, locals)
.then (contents) ->
# l.debug "#{file.name}: finished rendering with #{renderer.name}"
return resolve contents.body
.catch handle
render_file_tier = (file, tier, first_tier = true) ->
# l.debug "#{file.name}: #{tier.name}"
new Promise (resolve, reject) ->
file_data = files[file.name]
# sleep.sleep(1)
# l.debug "First tier: #{first_tier}"
out_path = path.join(output_dir, file_data.out_path)
if first_tier
file_path = path.join(input_dir, file.name)
else
file_path = out_path
# l.debug "Looking for file at #{file_path}"
fs.readFile file_path, encoding: 'utf8', (err, text) ->
if err
throw l.error err
transforms = tier.data[file.name].transforms
Promise.reduce(transforms,
(contents, transform) ->
render(file, contents, transform,
filename: file_path, basedir: output_dir)
.catch handle
, text)
.then (contents) ->
h.write_file(out_path, contents)
file_data.is_rendered = true
resolve()
.catch handle
render_all_in_tier = (pipeline, tier, first_tier = true) ->
# l.debug "#{pipeline.name}: #{tier.name}"
Promise.map h.to_array(tier.data), (file) ->
rendered = files[file.name].is_rendered
l.debug "#{file.name} is rendered: #{rendered}"
# unless rendered
# render_file_tier(file, tier, first_tier)
render_file_tier(file, tier, first_tier)
render_pipeline = (pipeline) ->
l.debug "**#{pipeline.name} Pipeline**"
Promise.reduce h.to_array(pipeline.data),
(acc, tier, i) ->
first_tier = if i is 0 then true else false
# l.debug "Rendering tier #{i + 1}
# - **#{tier.name}** for
# `#{pipeline.name}`"
promise = render_all_in_tier pipeline, tier, first_tier
# promise.then ->
# l.debug "Finished tier: #{tier.name}"
return promise
, 0
render_all = ->
Promise.map h.to_array(pipelines), (pipeline) ->
render_pipeline pipeline
class Glug
init: (directory, verbose = false) ->
if not directory?
throw new Error 'Please specify a directory'
global.verbose = verbose
l.log 'init', ('`verbose`' if verbose)
l.info "directory is #{directory}"
require_dependencies()
.then ->
Sprout = require('sprout')
path = require('path')
os = require('os')
mkdirp = require('mkdirp')
template_dir = path.join(os.homedir(), '.config/glug')
mkdirp.sync template_dir
sprout = new Sprout(template_dir)
sprout.add('glug', 'https://github.com/glugjs/sprout-glug')
.then (sprout) ->
inquirer = require('inquirer')
sprout.init 'glug', directory,
questionnaire: inquirer.prompt.bind(inquirer)
.catch handle
watch: (verbose = false) ->
global.verbose = verbose
l.log 'watch', ('`verbose`' if verbose)
require_dependencies()
.then load_config
.then start_config_watcher
.then start_browser_sync
.then load_all_transformers
.then prepare_output_dir
.then resolve_locals
.then generate_file_list
.then start_watcher
.then render_all
.catch handle
build: (verbose = false) ->
global.verbose = verbose
l.log 'build', ('`verbose`' if verbose)
require_dependencies()
.then load_config
.then load_all_transformers
.then prepare_output_dir
.then resolve_locals
.then generate_file_list
.then render_all
.catch handle
glug = new Glug()
module.exports = glug