wintersmith
Version:
A flexible static site generator.
195 lines (155 loc) • 5.55 kB
text/coffeescript
async = require 'async'
chalk = require 'chalk'
fs = require 'fs'
path = require 'path'
npm = require 'npm'
mkdirp = require 'mkdirp'
https = require 'https'
{NpmAdapter, loadEnv, commonOptions, extendOptions} = require './common'
{fileExists, readJSON} = require './../core/utils'
{logger} = require './../core/logger'
usage = """
usage: wintersmith plugin [options] <command>
commands:
#{ chalk.bold 'list' } - list available plugins
#{ chalk.bold 'install' } <plugin> - install plugin
options:
-C, --chdir [path] change the working directory
-c, --config [path] path to config
"""
options = {}
extendOptions options, commonOptions
max = (array, get) ->
get ?= (item) -> item
rv = null
for item in array
v = get(item)
rv = v if v > rv
return rv
lpad = (string, amount, char=' ') ->
p = ''
p += char for i in [0...amount-string.length]
return p + string
clip = (string, maxlen) ->
return string if string.length <= maxlen
return string[0...maxlen-2].trim() + ".."
fetchListing = (callback) ->
request = https.get 'https://api.npms.io/v2/search?q=keywords:wintersmith-plugin&size=200', (response) ->
if response.statusCode isnt 200
error = new Error "Unexpected response when searching registry, HTTP #{ response.statusCode }"
if not /^application\/json/.test response.headers['content-type']
error = new Error "Invalid content-type: #{ response.headers['content-type'] }"
if error?
response.resume()
callback error
return
data = []
response.on 'data', (chunk) -> data.push chunk
response.on 'end', ->
try
parsed = JSON.parse Buffer.concat data
catch error
callback error
return
listing = parsed.results.map (result) -> result.package
listing.sort (a, b) ->
return 1 if a.name > b.name
return -1 if a.name < b.name
return 0
callback null, listing
displayListing = (list, callback) ->
display = list.map (plugin) ->
name = normalizePluginName plugin.name
description = plugin.description
maintainers = plugin.maintainers.map((v) -> v.username).join(' ')
homepage = plugin.links?.homepage ? plugin.links?.npm
return {name, description, maintainers, homepage}
pad = max(display, (item) -> item.name.length)
maxw = process.stdout.getWindowSize()[0] - 2
margin = ([0...pad].map -> ' ').join ''
for plugin in display
line = "#{ lpad(plugin.name, pad) } #{ clip(plugin.description, maxw - pad - 2) }"
left = maxw - line.length
if left > plugin.maintainers.length
line += chalk.grey lpad(plugin.maintainers, left)
logger.info line.replace /^\s*(\S+) /, (m) -> chalk.bold m
if plugin.homepage? and plugin.homepage.length < maxw - pad - 2
logger.info "#{ margin } #{ chalk.gray plugin.homepage }"
logger.info ''
callback null, list
waterfall = (flow, callback) ->
### async.waterfall that allows for parallel tasks ###
resolved = []
for item in flow
switch typeof item
when 'function'
resolved.push item
when 'object', 'array'
resolved.push async.apply async.parallel, item
else
return callback new Error "Invalid item '#{ item }' in flow"
async.waterfall resolved, callback
normalizePluginName = (name) ->
name.replace /^wintersmith\-/, ''
main = (argv) ->
action = argv._[3]
if not action?
console.log usage
process.exit 0
loadCurrentEnv = (callback) ->
loadEnv argv, callback
installPlugin = (res, callback) ->
[env, list] = res
name = argv._[4]
plugin = null
for p in list
if normalizePluginName(p.name) is normalizePluginName(name)
plugin = p
break
if not plugin
return callback new Error "Unknown plugin: #{ name }"
configFile = env.config.__filename
packageFile = env.resolvePath 'package.json'
createPackageJson = (callback) ->
fileExists packageFile, (exists) ->
if exists
callback()
else
logger.warn "package.json missing, creating minimal package"
fs.writeFile packageFile, '{\n "dependencies": {},\n "private": true\n}\n', callback
readConfig = (callback) ->
readJSON configFile, callback
updateConfig = (config, callback) ->
config.plugins ?= []
if plugin.name not in config.plugins
config.plugins.push plugin.name
callback null, config
saveConfig = (config, callback) ->
logger.verbose "saving config file: #{ configFile }"
json = JSON.stringify config, null, 2
fs.writeFile configFile, json + '\n', callback
install = (callback) ->
logger.verbose "installing #{ plugin.name }"
process.chdir env.workDir
async.series [
createPackageJson
(callback) -> npm.load {logstream: new NpmAdapter(logger), save: true}, callback
(callback) -> npm.install plugin.name, callback
], (error) -> callback error
async.waterfall [install, readConfig, updateConfig, saveConfig], callback
switch action
when 'list'
cmd = [fetchListing, displayListing]
when 'install'
cmd = [[loadCurrentEnv, fetchListing], installPlugin]
else
cmd = [(callback) -> callback new Error "Unknown plugin action: #{ action }"]
waterfall cmd, (error) ->
if error?
logger.error error.message, error
process.exit 1
else
process.exit 0
module.exports = main
module.exports.usage = usage
module.exports.options = options