Skip to content

Commit

Permalink
Merge pull request #24 from carrot/utils
Browse files Browse the repository at this point in the history
Template Utilities
  • Loading branch information
nporteschaikin committed Mar 3, 2015
2 parents 1cce8cd + 97952e1 commit 00f8f14
Show file tree
Hide file tree
Showing 14 changed files with 138 additions and 16 deletions.
31 changes: 15 additions & 16 deletions lib/api/init.coffee
Expand Up @@ -10,6 +10,7 @@ ejs = require 'ejs'
inquirer = require 'inquirer'
Base = require '../base'
S = require 'underscore.string'
Utils = require './utils'
_ = require 'lodash'
dns = require 'dns'

Expand All @@ -19,31 +20,28 @@ class Init extends Base

execute: (opts) ->
configure_options.call(@, opts).with(@)
.then(merge_config_values_with_overrides)
.then(install_template_dependencies)
.then(get_user_init_file)
.then(run_user_before_function)
.then(remove_overrides_from_prompt)
.then(add_defaults_to_questions)
.then(prompt_user_for_answers)
.then(merge_config_values_with_overrides)
.then(check_internet_connection)
.then(ensure_template_is_updated)
.then(checkout_version)
.then(copy_template)
.then(run_user_before_render_function)
.then(replace_ejs)
.then(@compile)
.then(run_user_after_function)
.then(-> "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.
# TODO: this should be refactored out into a separate utils module
remove: (f) ->
fs.unlinkSync(path.resolve(@target, f))

###*
* @private
###
compile: ->
copy_template.call(@)
.with(@)
.then(run_user_before_render_function)
.then(replace_ejs)
#
# @api private
#

configure_options = (opts) ->
if not opts or not opts.name
Expand All @@ -61,6 +59,7 @@ class Init extends Base
if @version.length then @name = @name.replace(@version, '').slice(0,-1)

@sprout_path = @path(@name)
@utils = new Utils(@)

# transform overrides paired array to object
if Array.isArray(@overrides)
Expand Down Expand Up @@ -110,6 +109,7 @@ class Init extends Base

merge_config_values_with_overrides = ->
@config_values = _.assign(@answers, @overrides)
@ejs_options = _.extend(@config_values, {S: S})

check_internet_connection = ->
nodefn.call(dns.resolve, 'google.com')
Expand Down Expand Up @@ -159,9 +159,8 @@ class Init extends Base
replace_ejs = ->
nodefn.call(readdirp, { root: @target })
.tap (res) =>
ejs_options = _.extend(@config_values, {S: S})
res.files.map (f) ->
out = ejs.render(fs.readFileSync(f.fullPath, 'utf8'), ejs_options)
res.files.map (f) =>
out = ejs.render(fs.readFileSync(f.fullPath, 'utf8'), @ejs_options)
fs.writeFileSync(f.fullPath, out)

run_user_after_function = ->
Expand Down
36 changes: 36 additions & 0 deletions lib/api/utils.coffee
@@ -0,0 +1,36 @@
fs = require 'fs'
path = require 'path'
Base = require '../base'
ejs = require 'ejs'
_ = require 'lodash'

class Utils extends Base

constructor: (@sprout) -> super

read: (base_path, encoding = 'utf8') ->
read_path = @_full_path(base_path)
fs.readFileSync(read_path, encoding) if fs.existsSync(read_path)

write: (base_path, src, opts) ->
ejs_opts = _.extend(@sprout.ejs_options, opts)
fs.writeFileSync @_full_path(base_path), ejs.render(src, ejs_opts)

rename: (source_path, destination_path) ->
fs.renameSync @_full_path(source_path), @_full_path(destination_path)

remove: (base_path) ->
for file in Array::concat(base_path)
remove_path = @_full_path(base_path)
fs.unlinkSync remove_path if fs.existsSync remove_path

configure: (config) ->
@sprout.config_values = _.extend(@sprout.config_values, config)


#private

_full_path: (file) ->
path.resolve(@sprout.target, file)

module.exports = Utils
16 changes: 16 additions & 0 deletions readme.md
Expand Up @@ -200,6 +200,22 @@ class <%= S.classify('user_model') %> // given 'user_model' is prompted by your

So between this config file and the root folder, you should be able to make anything happen fairly easily. If not, please open up and issue and we'll try to make it happening-er and/or easier for you : )

### Hooks
Sprout comes with the following events for you to write custom logic for. To utilize these events, export a function for the hook of your choosing in your `init.coffee`:

- `before` - run before prompting for user input
- `before_render` - run after use input but before your templates are rendered
- `after` - run after rendering has completed

#### Template Utilities
We've created a handful of useful utilities that you can utilize within your `init.coffee` file to make creating custom templates that much easier.

- `read(base_path, encoding = 'utf8')` - synchronously read a file
- `write(path, content, opts)` - write an EJS template. pass target path, template content, and EJS options. The options you pass here will be merged with the sprout options from your before functions and user inputs
- `rename(target, destination)` - rename a file
- `remove(file_or_array_of_files)` - remove files(s)
- `configure` - extend or override `@sprout.config_values`

### Versioning Templates

Sometimes changes happen and you might want to be able to specify different versions of a single template. Sprout handles this through [git tags](http://git-scm.com/book/en/Git-Basics-Tagging). You can specify a tag as a version when you initialize a template by adding a version number after an `@` sign, as such (examples provided via CLI here):
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/utils-configure/init.coffee
@@ -0,0 +1,4 @@
exports.after = (sprout, done) ->
sprout.utils.configure name: 'foo'
sprout.compile()
.done -> done()
1 change: 1 addition & 0 deletions test/fixtures/utils-configure/root/foo
@@ -0,0 +1 @@
<%= name %>
4 changes: 4 additions & 0 deletions test/fixtures/utils-read/init.coffee
@@ -0,0 +1,4 @@
exports.after = (sprout, done) ->
ejs = sprout.utils.read 'bin'
sprout.utils.write 'foo', ejs
done()
1 change: 1 addition & 0 deletions test/fixtures/utils-read/root/bin
@@ -0,0 +1 @@
<%= name %>
3 changes: 3 additions & 0 deletions test/fixtures/utils-remove/init.coffee
@@ -0,0 +1,3 @@
exports.after = (sprout, done) ->
sprout.utils.remove '.travis.yml'
done()
Empty file.
3 changes: 3 additions & 0 deletions test/fixtures/utils-rename/init.coffee
@@ -0,0 +1,3 @@
exports.after = (sprout, done) ->
sprout.utils.rename '.npc.yml', '.travis.yml'
done()
Empty file.
3 changes: 3 additions & 0 deletions test/fixtures/utils-write/init.coffee
@@ -0,0 +1,3 @@
exports.after = (sprout, done) ->
sprout.utils.write 'foo', 'bar <%= fizz %>', {fizz: 'buzz'}
done()
Empty file.
52 changes: 52 additions & 0 deletions test/test.coffee
Expand Up @@ -96,6 +96,58 @@ describe 'js api', ->
.then -> sprout.remove('foobar')
.should.be.fulfilled

describe 'utils', ->

it 'read file', (done) ->
utils_template = path.join(_path, 'utils-read')
sprout.add(name: 'utils-read', uri: utils_template)
.then(-> sprout.init(name: 'utils-read', path: test_path, overrides: { name: 'bar' }))
.tap(->
write_path = path.join(test_path, 'foo')
fs.readFileSync(write_path, 'utf8').should.match /bar/
).then(-> sprout.remove('utils-read'))
.done((-> done()), done)

it 'write file', (done) ->
utils_template = path.join(_path, 'utils-write')
sprout.add(name: 'utils-write', uri: utils_template)
.then(-> sprout.init(name: 'utils-write', path: test_path, overrides: { name: 'bar' }))
.tap(->
write_path = path.join(test_path, 'foo')
fs.readFileSync(write_path, 'utf8').should.match /bar buzz/
).then(-> sprout.remove('utils-write'))
.done((-> done()), done)

it 'rename file', (done) ->
utils_template = path.join(_path, 'utils-rename')
sprout.add(name: 'utils-rename', uri: utils_template)
.then(-> sprout.init(name: 'utils-rename', path: test_path) )
.tap(->
rename_path = path.join(test_path, '.travis.yml')
fs.existsSync(rename_path).should.be.true
).then(-> sprout.remove('utils-rename'))
.done((-> done()), done)

it 'remove file', (done) ->
utils_template = path.join(_path, 'utils-remove')
sprout.add(name: 'utils-remove', uri: utils_template)
.then(-> sprout.init(name: 'utils-remove', path: test_path, overrides: { name: 'bar' }))
.tap(->
remove_path = path.join(test_path, '.travis.yml')
fs.existsSync(remove_path).should.be.false
).then(-> sprout.remove('utils-remove'))
.done((-> done()), done)

it 'modifies configuration', (done) ->
utils_template = path.join(_path, 'utils-configure')
sprout.add(name: 'utils-configure', uri: utils_template)
.then(-> sprout.init(name: 'utils-configure', path: test_path, overrides: { name: 'bar' }) )
.tap(->
configure_path = path.join(test_path, 'foo')
fs.readFileSync(configure_path, 'utf8').should.match /foo/
).then(-> sprout.remove('utils-configure'))
.done((-> done()), done)

describe 'init', ->

before -> sprout = require '..'
Expand Down

0 comments on commit 00f8f14

Please sign in to comment.