coffeemill
Version:
CoffeeScript project manager
326 lines (284 loc) • 9.94 kB
text/coffeescript
sys = require 'sys'
path = require 'path'
fs = require 'fs'
{ spawn } = require 'child_process'
{ Deferred } = require 'jsdeferred'
commander = require 'commander'
uglify = require 'uglify-js'
colors = require 'colors'
ejs = require 'ejs'
jade = require 'jade'
coffee = require 'coffee-script'
class CoffeeMill
EXT_NAMES = [ '.coffee' ]
@rTagVersion : /^v?([0-9\.]+)$/
@rDocComment : /\/\*\*([\s\S]+?)\*\/\s*(.*)/g
@rParam : /@param\s+{?(\S+?)}?\s+(\S+)\s+(.*)/g
@rReturn : /@return\s+{?(\S+?)}?\s+(.*)/g
@rCompletelyBlank: /^\s*$/
@rLineEndSpace : /[ \t]+$/g
@rBreak : /[\r\n]{3,}/g
constructor: (@cwd) ->
list = (val) ->
val.split ','
commander
.version(JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'))).version)
.usage('[options]')
.option('-n, --name <basename>', 'output directory (defualt is \'\')', '')
.option('-i, --input <dirnames>', 'output directory (defualt is \'src\')', list,
[ 'src' ])
.option('-o, --output <dirnames>', 'output directory (defualt is \'lib\')', list,
[ 'lib' ])
.option('-u, --uglify', 'minify with uglifyJS (.min.js)')
.option('-m, --map', 'generate source maps (.map)')
.option('-w, --watch', 'watch the change of input directory recursively')
.option('-v, --ver <version>', 'file version: supports version string, \'gitTag\' or \'none\' (default is \'none\')', 'none')
.parse(process.argv)
console.log commander
@scanInput()
@compile()
scanInput: ->
watcher.close() for watcher in @watchers?
@watchers = []
@files = @findFiles commander.input, if commander.watch then @changed else null
# fs.watch @makefile.jsdoc.template, @changed if @makefile.jsdoc?.template?
findFiles: (dirs, change, basedir, files = []) ->
isBasedir = basedir?
for dir in dirs
if isBasedir
dirPath = dir
else
dirPath = basedir = dir
stats = fs.statSync dirPath
if stats.isFile()
# when extname is relevant, push filepath into result
filePath = dirPath
if EXT_NAMES.indexOf(path.extname filePath) isnt -1
# 1. parse package name
# 2. read code
# 3. parse class name and dependent class name
packages = path.relative(basedir, filePath).split path.sep
packages.pop()
extname = path.extname filePath
name = path.basename filePath, extname
code = fs.readFileSync filePath, 'utf8'
r = code.match /class\s+(\w+)(?:\s+extends\s+(\w+))?/m
if r?
[ {},
className,
extendsName ] = r
files.push
filePath : filePath
extname : extname
packages : packages
name : name
extendsName: extendsName or ''
code : code
else if stats.isDirectory()
# watch dir
if change?
@watchers.push fs.watch dirPath, change
# recursively
childs = fs.readdirSync dirPath
for file, i in childs
childs[i] = path.join dirPath, file
@findFiles childs, change, basedir, files
files
changed: =>
clearTimeout @timeoutId
@timeoutId = setTimeout =>
@scanInput()
@compile()
, 100
compile: ->
sys.puts new Date().toString().underline
Deferred
.next =>
switch commander.ver
when 'none'
''
when 'gitTag'
@gitTag()
else
commander.ver
.error (err) =>
''
.next (version) =>
if version isnt ''
sys.puts 'version: ' + version
postfix = "-#{version}"
else
postfix = ''
# if @makefile.jsdoc
# @jsdoc()
# find source files
# filePathes = @findFiles path.join @cwd, commander.input
# sort on dependency
@files.sort (a, b) ->
if a.extendsName is ''
-1
else if b.extendsName is ''
1
else if b.extendsName is a.name
-1
else if a.extendsName is b.name
1
else
0
# 1. find package
# 2. concat codes
# 3. add exports
codes = []
exports = {}
for file in @files
codes.push file.code
exp = exports
for pkg in file.packages
exp[pkg] = {} unless exp[pkg]?
exp = exp[pkg]
exp[file.name] = file.name
codes.push 'window[k] = v for k, v of ' + JSON.stringify(exports, null, 2).replace(/(:\s+)"(\w+)"/g, '$1$2')
code = codes.join '\n\n'
for outputDir in commander.output
output = path.join @cwd, outputDir
# Make output directory
fs.mkdirSync output unless fs.existsSync output
filename = "#{commander.name}#{postfix}.coffee"
output = path.join @cwd, outputDir, filename
fs.writeFileSync output, code, 'utf8'
sys.puts 'concat : '.cyan + output
@copy code, filename
{ js: code, v3SourceMap } = @coffee code,
sourceMap : true
generatedFile: "#{commander.name}#{postfix}.js"
sourceRoot : ''
sourceFiles : [ "#{commander.name}#{postfix}.coffee" ]
if commander.map
code += "\n/*\n//@ sourceMappingURL=#{commander.name}#{postfix}.map\n*/"
filename = "#{commander.name}#{postfix}.js"
output = path.join @cwd, outputDir, filename
fs.writeFileSync output, code, 'utf8'
sys.puts 'compile : '.cyan + output
@copy code, filename
if commander.map
filename = "#{commander.name}#{postfix}.map"
output = path.join @cwd, outputDir, filename
fs.writeFileSync output, v3SourceMap, 'utf8'
sys.puts 'source map: '.cyan + output
@copy code, filename
if commander.uglify
{ code } = uglify.minify code, { fromString: true }
filename = "#{commander.name}#{postfix}.min.js"
output = path.join @cwd, outputDir, filename
fs.writeFileSync output, code, 'utf8'
sys.puts 'minify : '.cyan + output
@copy code, filename
sys.puts 'complete!!'.green
.error (err) ->
sys.error err.stack
copy: (code, filename) ->
return unless commander.copy
output = path.join commander.copy, filename
fs.writeFileSync output, code, 'utf8'
sys.puts 'copy : '.cyan + output
gitTag: ->
d = new Deferred()
gitTag = spawn 'git', [ 'tag' ]
out = ''
gitTag.stdout.setEncoding 'utf8'
gitTag.stdout.on 'data', (data) ->
out += data
err = ''
gitTag.stderr.setEncoding 'utf8'
gitTag.stderr.on 'data', (data) ->
err += data.red
gitTag.on 'close', ->
return d.fail err if err isnt ''
tags = out.split '\n'
i = tags.length
while i--
tag = tags[i]
r = tag.match CoffeeMill.rTagVersion
continue unless r?[1]?
versions = r[1].split '.'
minor = parseInt versions[versions.length - 1], 10
versions[versions.length - 1] = minor + 1
return d.call versions.join '.'
d.fail 'no tag as version'
d
jsdoc: (wholeCode) ->
if @makefile.jsdoc.src.files?
files = @makefile.jsdoc.src.files
for file, i in files
files[i] = path.join @cwd, commander.input, file
code = @concatFiles files
else
code = wholeCode
properties = []
while r = CoffeeMill.rDocComment.exec code
comment = r[1]
name = r[2]
params = []
returns = []
comment = comment
.replace(/^[ \t]*\/\/.*$/g, '')
.replace(/^[ \t]*\* ?/g, '')
comment = comment.replace CoffeeMill.rParam, (matched, type, name, description) ->
optional = false
if r = name.match(/^\[(.*)\]$/)
optional = true
name = r[1]
r = name.split('=')
params.push
types : type.split('|')
optional : optional
name : r[0]
defaultValue: r[1]
description : description
''
comment = comment.replace CoffeeMill.rReturn, (matched, type, description) ->
returns.push
types : type.split('|')
description: description
''
continue if CoffeeMill.rCompletelyBlank.test comment
r2 = name.match /(\S+)\s*[:=]/
name = r2[1] if r2? [ 1 ]?
properties.push
name : name
comment: comment
params : params
returns: returns
switch @makefile.jsdoc.engine
when 'ejs'
generateDoc = ejs.compile fs.readFileSync(@makefile.jsdoc.template, 'utf8'),
compileDebug: true
doc = generateDoc(
title : rawFilename
properties: properties
)
.replace(CoffeeMill.rLineEndSpace, '')
.replace(CoffeeMill.rBreak, '\n\n')
fs.writeFileSync @makefile.jsdoc.filename, doc, 'utf8'
when 'jade'
generateDoc = jade.compile fs.readFileSync(@makefile.jsdoc.template, 'utf8'),
compileDebug: true
doc = generateDoc(
title : rawFilename
properties: properties
)
.replace(CoffeeMill.rLineEndSpace, '')
.replace(CoffeeMill.rBreak, '\n\n')
fs.writeFileSync @makefile.jsdoc.filename, doc, 'utf8'
concatFiles: (files) ->
codes = []
for file in files
codes.push fs.readFileSync file, 'utf8'
codes.join '\n\n'
coffee: (code, options) ->
try
coffee.compile code, options
catch err
sys.puts "Compile Error: #{err.toString()}".red
exports.run = ->
new CoffeeMill process.cwd()