Skip to content

Commit

Permalink
Merge pull request #3 from carrot/promise
Browse files Browse the repository at this point in the history
Convert to a promise-based interface
  • Loading branch information
Jeff Escalante committed Dec 17, 2013
2 parents 149379f + 94b7ba6 commit 90b0025
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 216 deletions.
29 changes: 18 additions & 11 deletions bin/sprout
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ require('coffee-script');
require('colors');

var argv = require('optimist').argv,
commands = require('../lib/commands');
commands = require('../lib/api');

// get command

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

// execute command
Expand All @@ -18,16 +19,22 @@ if (cmd.sync) {
if (res.error) return console.error("ERROR: ".red + res.error)
console.log(res)
} else {
var cmd = cmd.apply(null, argv._.concat([opts_only(argv), cb]));
}

function cb(err, res){
if (err){
console.error("ERROR: ".red)
console.error(new Error(err).stack)
process.exit(64) // command line usage error
var p;
switch (name) {
case 'add':
p = cmd({ name: argv._[0], url: argv._[1] }); break;
case 'remove':
p = cmd(argv._[0]); break;
case 'list':
p = cmd({ pretty: true }); break;
case 'init':
p = cmd({ template: argv._[0], path: argv._[1], options: opts_only(argv) }); break;
}
console.log(res)

p.done(console.log.bind(console), function(err){
console.error("ERROR: ".red + err);
process.exit(64); // command line usage error
});
}

function opts_only(args){
Expand Down
40 changes: 40 additions & 0 deletions lib/api/add.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
which = require 'which'
W = require 'when'
nodefn = require 'when/node/function'
exec = require('child_process').exec
Base = require '../base'

class Add extends Base

constructor: -> super

execute: (opts) ->
configure_options.call(@, opts)
.then(nodefn.lift(exec, "git clone #{@url} #{@path(@name)}"))
.then(=> if @branch then nodefn.call(exec, "git 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

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')

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

@branch = ''
branch_matcher = /#(.*)$/
if @url.match(branch_matcher)
@branch = "#{@url.match(branch_matcher)[1]}"
@url = @url.replace(branch_matcher, '')

W.resolve()

module.exports = (opts) ->
(new Add()).execute(opts)
File renamed without changes.
File renamed without changes.
43 changes: 22 additions & 21 deletions lib/commands/init.coffee → lib/api/init.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,40 @@ exec = require('child_process').exec
ejs = require 'ejs'
prompt = require 'prompt'
Base = require '../base'
accord = require '../utils/accord'

class Init extends Base

constructor: (name, target, opts, cb) ->
super
accord.call(@, { name: name, target: target, options: opts, cb: cb })
if not @name then return @cb('please provide a template name')
if not @target then @target = path.join(process.cwd(), @name)
@done = @cb
constructor: -> super

execute: ->
@error = null
@sprout_path = @path(@name)
if not fs.existsSync(@sprout_path) then return @cb("template #{@name} does not exist")
execute: (opts) ->

get_user_config.call(@)

user_before_fn.call(@)
configure_options.call(@, opts)
.then(get_user_config.bind(@))
.then(user_before_fn.bind(@))
.then(prompt_for_info.bind(@))
.then(user_after_fn.bind(@))
.then(update_template.bind(@))
.then(copy_template.bind(@))
.then(replace_ejs.bind(@))
.catch(@cb.bind(@))
.done => @cb(null, "project #{@name} created!")
.yield("project #{@template} created!")

#
# @api private
#

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 @template then return W.reject('please provide a template name')
if not @target then @target = path.join(process.cwd(), @template)

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

W.resolve()

get_user_config = ->
init_file = path.join(@sprout_path, 'init.coffee')
Expand Down Expand Up @@ -68,21 +72,18 @@ class Init extends Base
if not @config.before then return W.resolve()
nodefn.call(@config.before, @)

# pull from git to ensure most recent version
update_template = ->
nodefn.call(exec, "cd #{@sprout_path}; git pull")

copy_template = ->
nodefn.call(ncp, path.join(@sprout_path, 'root'), @target)

replace_ejs = ->
# grab all files in the template
nodefn.call(readdirp, { root: @target })
.tap (res) =>
res.files.map (f) =>
processed = ejs.render(fs.readFileSync(f.fullPath, 'utf8'), @config_values)
fs.writeFileSync(f.fullPath, processed)

module.exports = (name, p, opts, cb) ->
cmd = new Init(name, p, opts, cb)
if cmd.done then cmd.execute()
module.exports = (opts) ->
(new Init()).execute(opts)
File renamed without changes.
14 changes: 14 additions & 0 deletions lib/api/remove.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
rimraf = require 'rimraf'
Base = require '../base'
nodefn = require 'when/node/function'

class Remove extends Base

constructor: -> super

execute: (name) ->
nodefn.call(rimraf, @path(name))
.yield("template '#{name}' removed")

module.exports = (name) ->
(new Remove()).execute(name)
2 changes: 1 addition & 1 deletion lib/base.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class Base
# @api private
#

# h/t to configstore for this logic
generate_fake_user: ->
# h/t to configstore for this logic
uid = [process.pid, Date.now(), Math.floor(Math.random() * 1000000)].join('-')
crypto.createHash('md5').update(uid).digest('hex')

Expand Down
43 changes: 0 additions & 43 deletions lib/commands/add.coffee

This file was deleted.

19 changes: 0 additions & 19 deletions lib/commands/remove.coffee

This file was deleted.

2 changes: 1 addition & 1 deletion lib/index.coffee
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Base = require('./base')
base = new Base

module.exports = require('./commands')
module.exports = require('./api')
module.exports.path = base.path.bind(base)
45 changes: 0 additions & 45 deletions lib/utils/accord.coffee

This file was deleted.

23 changes: 13 additions & 10 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,23 @@ Sprout also comes with a [man page](man) and will display a help menu as a refre

### Javascript API

Sprout was made specifically to be easy to integrate into javascript applications and libraries that create project structures for you. It can be installed locally via npm and used directly in a node project. The API is similar to the CLI interface described above. Example code given in coffeescript:
Sprout was made specifically to be easy to integrate into javascript applications and libraries that create project structures for you. It can be installed locally via npm and used directly in a node project. The API is similar to the CLI interface described above. Each method returns a [A+ compliant](http://promises-aplus.github.io/promises-spec/) promise (with extra sugar from [when.js](https://github.com/cujojs/when)) Example code given in coffeescript:

```coffee
path = require 'path'
sprout = require 'sprout'

# Adding a template
# -----------------
sprout.add 'node', 'https://github.com/carrot/sprout-node', (err, res) ->
if err then return console.error(err)
console.log 'template added!'
sprout.add({ name: 'node', url: 'https://github.com/carrot/sprout-node' })
.catch(console.error.bind(console))
.done(-> console.log('template added!'))

# removing a template
# -------------------
sprout.remove 'node', (err, res) ->
if err then return console.error(err)
console.log 'template removed!'
sprout.remove('node')
.catch(console.error.bind(console))
.done(-> console.log('template removed!'))

# listing templates
# -----------------
Expand All @@ -89,9 +89,12 @@ console.log sprout.list(pretty: true)
# initializing a template
# -----------------------

sprout.init 'node', path.join(process.cwd(), 'new_project'), (err, res) ->
if err then return console.error(err)
console.log 'project structure initialized!'
sprout.init({
template: 'node',
path: path.join(process.cwd(), 'new_project'),
options: { foo: 'bar' } # optional, will prompt if not provided
}).catch(console.error.bind(console))
.done(-> console.log('project initialized!'))

# other things
# ------------
Expand Down
Loading

0 comments on commit 90b0025

Please sign in to comment.