Skip to content

Commit

Permalink
Merge pull request #19 from carrot/km/clean
Browse files Browse the repository at this point in the history
API Clean Up + Inquirer.js
  • Loading branch information
Jeff Escalante committed Mar 26, 2014
2 parents 776b7a9 + f4c8bda commit 41461b3
Show file tree
Hide file tree
Showing 28 changed files with 445 additions and 123 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
node_modules
test/testproj
16 changes: 11 additions & 5 deletions bin/sprout
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ require('colors');
var argv = require('optimist').argv,
commands = require('../lib/api');

// get command
// add sprout.new alias
commands['new'] = function(){};

// get command
var name = argv._.splice(0,1)[0];
var cmd = commands[name];
if (!cmd) return commands.help()

// execute command

if (cmd.sync) {
res = cmd({ pretty: true });
if (res.error) return console.error("ERROR: ".red + res.error)
Expand All @@ -22,13 +23,18 @@ if (cmd.sync) {
var p;
switch (name) {
case 'add':
p = cmd({ name: argv._[0], url: argv._[1], options: opts_only(argv) }); break;
// sprout add <name> <template>
p = cmd({ name: argv._[0], template: argv._[1] }); break;
case 'remove':
// sprout remove <name>
p = cmd(argv._[0]); break;
case 'list':
p = cmd({ pretty: true }); break;
// sprout list <options>
p = cmd({ pretty: true, options: opts_only(argv) }); break;
case 'new':
case 'init':
p = cmd({ template: argv._[0], path: argv._[1], options: opts_only(argv) }); break;
// sprout init <name> <path> <options>
p = commands['init']({ name: argv._[0], path: argv._[1], options: opts_only(argv) }); break;
}

p.done(function(r){ console.log(r.green) }, function(err){
Expand Down
73 changes: 52 additions & 21 deletions lib/api/add.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,78 @@ W = require 'when'
nodefn = require 'when/node/function'
exec = require('child_process').exec
Base = require '../base'
fs = require 'fs'
path = require 'path'
rimraf = require 'rimraf'
url = require 'url'

class Add extends Base

constructor: -> super

execute: (opts) ->
configure_options.call(@, opts)
.then(link_project.bind(@))
.then(=> if @branch then nodefn.call(exec, "cd #{@path(@name)}; git checkout #{@branch}"))
configure_options.call(@, opts).with(@)
.then(determine_if_local)
.then(ensure_local_template_exists)
.then(set_branch)
.then(remove_existing_template)
.then(link_project)
.then(checkout_branch)
.yield("template '#{@name}' added")

# @api private

configure_options = (opts) ->
if not opts then return W.reject('your template needs a name!')
@name = opts.name
@url = opts.url
@options = opts.options || {}
if not opts or not opts.name
return W.reject('your template needs a name!')

@name = opts.name
@template = opts.template
@options = opts.options || {}
@local = false

if @name and not @template
@template = @name
@name = @template.split('/')[@template.split('/').length-1]

W.resolve()

determine_if_local = ->
# set @local to true if @template isn't an http or git url
url = url.parse(@template)
remote = url.pathname.split('.')[url.pathname.split('.').length-1] == 'git'
if not remote
@local = true
W.resolve()

if not @name then return W.reject('your template needs a name!')
if not which.sync('git') then return W.reject('you need to have git installed')
ensure_local_template_exists = ->
if not @local then return W.resolve()
if not which.sync('git')
return W.reject('you need to have git installed')

if @name and not @url
@url = @name
@name = @url.split('/')[@url.split('/').length-1]
if not fs.existsSync(path.normalize(@template))
return W.reject("there is no sprout template located at '#{@template}'")

set_branch = ->
if @local then return W.resolve()
@branch = null
branch_matcher = /#(.*)$/
if @url.match(branch_matcher)
@branch = "#{@url.match(branch_matcher)[1]}"
@url = @url.replace(branch_matcher, '')

if @template.match(branch_matcher)
@branch = "#{@template.match(branch_matcher)[1]}"
@template = @template.replace(branch_matcher, '')
W.resolve()

remove_existing_template = ->
nodefn.call(rimraf, @path(@name))

link_project = ->
if not @options.local
nodefn.call(exec, "git clone #{@url} #{@path(@name)}")
else
nodefn.call(exec, "rm -rf #{@path(@name)}; ln -s #{@url} #{@path(@name)}")
cmd = "git clone #{@template} #{@path(@name)}"
if @local then cmd = "rm -rf #{@path(@name)} && ln -s #{@template} #{@path(@name)}"
nodefn.call(exec, cmd)

checkout_branch = ->
if not @branch then return W.resolve()
nodefn.call(exec, "git checkout #{@branch}", {cwd: @path(@name)})

module.exports = (opts) ->
(new Add()).execute(opts)
93 changes: 51 additions & 42 deletions lib/api/init.coffee
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
path = require 'path'
fs = require 'fs'
W = require 'when'
nodefn = require 'when/node/function'
nodefn = require 'when/node'
nodecb = require 'when/callbacks'
readdirp = require 'readdirp'
ncp = require('ncp').ncp
exec = require('child_process').exec
ejs = require 'ejs'
prompt = require 'prompt'
inquirer = require 'inquirer'
Base = require '../base'
S = require 'string'
_ = require 'lodash'

class Init extends Base

constructor: -> super

execute: (opts) ->

configure_options.call(@, opts)
.then(get_user_config.bind(@))
.then(user_before_fn.bind(@))
.then(prompt_for_info.bind(@))
.then(update_template.bind(@))
.then(copy_template.bind(@))
.then(replace_ejs.bind(@))
.then(user_after_fn.bind(@))
.yield("project created at ./#{@target}!")
configure_options.call(@, opts).with(@)
.then(get_user_init_file)
.then(run_user_before_function)
.then(remove_overrides_from_prompt)
.then(prompt_user_for_answers)
.then(merge_config_values_with_overrides)
.then(ensure_template_is_updated)
.then(copy_template)
.then(replace_ejs)
.then(run_user_after_function)
.yield("project created at '#{@target}'!")

# intended for use in the after function, quick way to remove
# files/folders that users wanted to nix after the prompts.
Expand All @@ -35,60 +40,64 @@ class Init extends Base
#

configure_options = (opts) ->
if not opts then return W.reject('please provide a template name')
@template = opts.template
@target = opts.path
@options = opts.options
if not opts or not opts.name
return W.reject('your template needs a name!')

if not @template then return W.reject('please provide a template name')
if not @target then @target = path.join(process.cwd(), @template)
@name = opts.name
@target = opts.path
@options = opts.options
@sprout_path = @path(@name)
@answers = {}

@sprout_path = @path(@template)
if not fs.existsSync(@sprout_path) then return W.reject("template #{@template} does not exist")
if not fs.existsSync(@sprout_path)
return W.reject("template '#{@name}' does not exist")

if not @target then @target = path.join(process.cwd(), @name)

W.resolve()

get_user_config = ->
get_user_init_file = ->
init_file = path.join(@sprout_path, 'init.coffee')
if not fs.existsSync(init_file) then return @config = {}
@config = require(init_file)

user_before_fn = ->
run_user_before_function = ->
if not @config.before then return W.resolve()
nodefn.call(@config.before, @)

prompt_for_info = ->
if not @config.configure
@config_values = @options
return W.resolve()

prompt.override = @options
prompt.message = ''
prompt.delimiter = ''
remove_overrides_from_prompt = ->
keys = _.keys(@options)
@questions = _.reject(@config.configure, (v) -> _.contains(keys, v.name) )

if not prompt.override then console.log '\nplease enter the following information:'.yellow
prompt_user_for_answers = ->
if not @questions.length then return W.resolve()
nodecb.call(inquirer.prompt, @questions)
.then((o) => @answers = o)

prompt.start()
nodefn.call(prompt.get, @config.configure).tap (res) =>
@config_values = res
if not prompt.override then console.log('')

user_after_fn = ->
if not @config.after then return W.resolve()
nodefn.call(@config.after, @)
merge_config_values_with_overrides = ->
@config_values = _.assign(@answers, @options)

update_template = ->
ensure_template_is_updated = ->
nodefn.call(exec, "cd #{@sprout_path} && git pull")
.catch(-> return W.resolve())

copy_template = ->
nodefn.call(ncp, path.join(@sprout_path, 'root'), @target)
root_path = path.join(@sprout_path, 'root')
if not fs.existsSync(root_path)
return W.reject('template does not contain root directory')
nodefn.call(ncp, root_path, @target)

replace_ejs = ->
nodefn.call(readdirp, { root: @target })
.tap (res) =>
ejs_options = _.extend(@config_values, {S: S})
res.files.map (f) =>
processed = ejs.render(fs.readFileSync(f.fullPath, 'utf8'), @config_values)
fs.writeFileSync(f.fullPath, processed)
out = ejs.render(fs.readFileSync(f.fullPath, 'utf8'), ejs_options)
fs.writeFileSync(f.fullPath, out)

run_user_after_function = ->
if not @config.after then return W.resolve()
nodefn.call(@config.after, @)

module.exports = (opts) ->
(new Init()).execute(opts)
19 changes: 14 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@
"name": "sprout",
"version": "0.0.8",
"author": "Carrot Creative <[email protected]>",
"contibutors": ["Jeff Escalante <[email protected]>"],
"contibutors": [
"Jeff Escalante <[email protected]>"
],
"description": "Simple project templating",
"main": "index.js",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/carrot/sprout"
},
"keywords": ["new", "project", "templating", "configuration"],
"keywords": [
"new",
"project",
"templating",
"configuration"
],
"bugs": "https://github.com/carrot/sprout/issues",
"bin": {
"sprout": "bin/sprout"
Expand All @@ -25,11 +32,13 @@
"rimraf": "2.x",
"ncp": "0.5.x",
"ejs": "0.8.x",
"prompt": "0.2.x",
"readdirp": "0.3.x",
"when": "2.x",
"when": "3.x",
"mkdirp": "0.3.x",
"which": "1.0.x"
"which": "1.0.x",
"inquirer": "0.4.x",
"lodash": "2.4.x",
"string": "1.8.x"
},
"devDependencies": {
"mocha": "*",
Expand Down
Loading

0 comments on commit 41461b3

Please sign in to comment.