docpad
Version:
DocPad is a dynamic static site generator. Write your content as files, or import your content from other external sources. Render the content with plugins. And deploy your static or dynamic website to your favourite hosting provider.
371 lines (322 loc) • 8.25 kB
text/coffeescript
# =====================================
# Requires
# Standard Library
pathUtil = require('path')
util = require('util')
# External
Errlop = require('errlop').default
{uniq, compact} = require('underscore')
extractOptsAndCallback = require('extract-opts')
inquirer = require('inquirer')
extendr = require('extendr')
# =====================================
# Export
###*
# The DocPad Util Class.
# Collection of DocPad utility methods
# @class docpadUtil
# @constructor
# @static
###
module.exports = docpadUtil =
###*
# Create a new Buffer, with support for Node <4
# @private
# @method newBuffer
# @param {*} data
# @return {Buffer}
###
newBuffer: (data) ->
if Buffer.from?
return Buffer.from(data)
else
return new Buffer(data)
###*
# Write to stderr
# @private
# @method writeStderr
# @param {String} data
###
writeStderr: (data) ->
try
process.stderr.write(data)
catch err
# ignore the error, try stdout instead
process.stdout.write(data)
###*
# Get Default Log Level
# @private
# @method getDefaultLogLevel
# @return {Number} default log level
###
getTestingLogLevel: ->
if docpadUtil.isTravis() or ('-d' in process.argv)
return 7
else
return 5
###*
# Are we executing on Travis
# @private
# @method isTravis
# @return {String} The travis node version
###
isTravis: ->
return process.env.TRAVIS_NODE_VERSION?
###*
# Is this TTY
# @private
# @method isTTY
# @return {Boolean}
###
isTTY: ->
return process.stdout?.isTTY is true and process.stderr?.isTTY is true
###*
# Is Standadlone
# @private
# @method isStandalone
# @return {Object}
###
isStandalone: ->
return /docpad$/.test(process.argv[1] or '')
###*
# Is user
# @private
# @method isUser
# @return {Boolean}
###
isUser: ->
return docpadUtil.isStandalone() and docpadUtil.isTTY() and docpadUtil.isTravis() is false
###*
# Are we using standard encoding?
# @private
# @method isStandardEncoding
# @param {String} encoding
# @return {Boolean}
###
isStandardEncoding: (encoding) ->
return encoding.toLowerCase() in ['ascii', 'utf8', 'utf-8']
###*
# Get Local DocPad Installation Executable - ie
# not the global installation
# @private
# @method getLocalDocPadExecutable
# @return {String} the path to the local DocPad executable
###
getLocalDocPadExecutable: ->
return pathUtil.join(process.cwd(), 'node_modules', 'docpad', 'bin', 'docpad')
###*
# Is Local DocPad Installation
# @private
# @method isLocalDocPadExecutable
# @return {Boolean}
###
isLocalDocPadExecutable: ->
return docpadUtil.getLocalDocPadExecutable() in process.argv
###*
# Does the local DocPad Installation Exist?
# @private
# @method getLocalDocPadExecutableExistance
# @return {Boolean}
###
getLocalDocPadExecutableExistance: ->
return require('safefs').existsSync(docpadUtil.getLocalDocPadExecutable()) is true
###*
# Spawn Local DocPad Executable
# @private
# @method startLocalDocPadExecutable
# @param {Function} next
# @return {Object} don't know what
###
startLocalDocPadExecutable: (next) ->
args = process.argv.slice(2)
command = ['node', docpadUtil.getLocalDocPadExecutable()].concat(args)
return require('safeps').spawn command, {stdio:'inherit'}, (err) ->
if err
if next
next(err)
else
message = 'An error occured within the child DocPad instance: '+err.message+'\n'
docpadUtil.writeStderr(message)
else
next?()
###*
# get a filename without the extension
# @method getBasename
# @param {String} filename
# @return {String} base name
###
getBasename: (filename) ->
if filename[0] is '.'
basename = filename.replace(/^(\.[^\.]+)\..*$/, '$1')
else
basename = filename.replace(/\..*$/, '')
return basename
###*
# Get the extensions of a filename
# @method getExtensions
# @param {String} filename
# @return {Array} array of string
###
getExtensions: (filename) ->
extensions = filename.split(/\./g).slice(1)
return extensions
###*
# Get the extension from a bunch of extensions
# @method getExtension
# @param {Array} extensions
# @return {String} the extension
###
getExtension: (extensions) ->
unless require('typechecker').isArray(extensions)
extensions = docpadUtil.getExtensions(extensions)
if extensions.length isnt 0
extension = extensions.slice(-1)[0] or null
else
extension = null
return extension
###*
# Get the directory path.
# Wrapper around the node.js path.dirname method
# @method getDirPath
# @param {String} path
# @return {String}
###
getDirPath: (path) ->
return pathUtil.dirname(path) or ''
###*
# Get the file name.
# Wrapper around the node.js path.basename method
# @method getFilename
# @param {String} path
# @return {String}
###
getFilename: (path) ->
return pathUtil.basename(path)
###*
# Get the DocPad out file name
# @method getOutFilename
# @param {String} basename
# @param {String} extension
# @return {String}
###
getOutFilename: (basename, extension) ->
if basename is '.'+extension # prevent: .htaccess.htaccess
return basename
else
return basename+(if extension then '.'+extension else '')
###*
# Get the URL
# @method getUrl
# @param {String} relativePath
# @return {String}
###
getUrl: (relativePath) ->
return '/'+relativePath.replace(/[\\]/g, '/')
###*
# Get the post slug from the URL
# @method getSlug
# @param {String} relativeBase
# @return {String} the slug
###
getSlug: (relativeBase) ->
return require('bal-util').generateSlugSync(relativeBase)
###*
# Get the user to select one of the choices
# @method choose
# @param {string} message
# @param {Array<string>} choices
# @param {Object} [opts={}]
# @param {Function} next
###
choose: (message, choices, opts={}, next) ->
# Question
question = extendr.extend({
name: 'question',
type: 'list',
message,
choices
}, opts)
# Ask
inquirer.prompt([question])
.catch(next)
.then((answers) -> next(null, answers.question))
# Chain
@
###*
# Perform an action
# next(err,...), ... = any special arguments from the action
# this should be it's own npm module
# as we also use the concept of actions in a few other packages.
# Important concept in DocPad.
# @method action
# @param {Object} action
# @param {Object} opts
# @param {Function} next
###
action: (action,opts,next) ->
# Prepare
[opts,next] = extractOptsAndCallback(opts,next)
me = @
locale = me.getLocale()
run = opts.run ? true
runner = opts.runner ? me.getActionRunner()
# Array?
if Array.isArray(action)
actions = action
else
actions = action.split(/[,\s]+/g)
# Clean actions
actions = uniq compact actions
# Exit if we have no actions
if actions.length is 0
err = new Errlop(locale.actionEmpty)
return next(err); me
# We have multiple actions
if actions.length > 1
actionTaskOrGroup = runner.createTaskGroup 'actions bundle: '+actions.join(' ')
for action in actions
# Fetch
try
actionMethod = me[action].bind(me)
catch missingError
err = new Errlop(
util.format(locale.actionNonexistant, action),
missingError
)
return next(err); me
# Task
task = actionTaskOrGroup.createTask(action, actionMethod, {args: [opts]})
actionTaskOrGroup.addTask(task)
# We have single actions
else
# Fetch the action
action = actions[0]
# Fetch
try
actionMethod = me[action].bind(me)
catch missingError
err = new Errlop(
util.format(locale.actionNonexistant, action),
missingError
)
return next(err); me
# Task
actionTaskOrGroup = runner.createTask(action, actionMethod, {args: [opts]})
# Create our runner task
runnerTask = runner.createTask "runner task for action: #{action}", (continueWithRunner) ->
# Add our listener for our action
actionTaskOrGroup.done (args...) ->
# If we have a completion callback, let it handle the error
if next
next(args...)
args = args.slice()
args[0] = null
# Continue with our runner
continueWithRunner(args...)
# Run our action
actionTaskOrGroup.run()
# Add it and run it
runner.addTask(runnerTask)
runner.run() if run is true
# Chain
return me