#!/usr/bin/env coffee #============================================================================ # CoffeeScriptのソースファイルと保存先を指定し、コンパイル&minify化を行う # 「-c」オプションでソースファイルだけを指定した場合は、生成されるファイル # はソースファイルと同じ場所に保存される。 # 例)terffee -c hoge.coffee # # 「-o」オプションで保存先を指定する。 # 保存場所の最後をスラッシュにするか、すでに存在するディレクトリ名を指定し # た場合は、ディレクトリとみなしその中に生成したファイルが「.min.js」の拡 # 張子で保存される。 # 例)terffee -c hoge.coffee -o ./apps/js/ → ./apps/js/hoge.min.jsが生成される # # 最後がスラッシュではない場合は、指定したソースファイルのコンパイル&minify # されたものがひとつのファイルとして保存される。 # 例)terffee -c hoge.coffee -c foo.coffee -o hogefoo.min.js # # ソースファイルと保存場所の対応は、記述した順番になる。 # 保存先の数がソースファイルの数よりも少ない場合、足りない分は最後の保存場所 # がそのまま使われる。 # 例)terffee -c hoge.coffee -o hoge.min.js -c foo.coffee -o foo.min.js -c bar.coffee # (上記の例では、「bar.coffee」は「foo.min.js」に結合される) # # type: -1 指定したpathが存在しない # 0 (未使用) # 1 ディレクトリ # 2 ファイル  #============================================================================ TERSER = require("terser") COFFEE = require("coffee-compiler2") CHOKIDAR = require("chokidar") ARGV = require("argv") MINIMIST = require("minimist") ASYNC = require("async") FS = require("fs-extra") PATH = require("path") PROMISE = require("bluebird") FORM = require("ndlog").form #============================================================================ # CoffeeScriptをコンパイルし、minify化する #============================================================================ compile = (fpath) -> return new PROMISE (resolve, reject) -> COFFEE_OPTS = sourceMap: true bare: true try COFFEE.fromFile fpath, COFFEE_OPTS, (err, jsstr) -> if (err) reject err: -1 message: "compile error." result: err.message else code = (TERSER.minify(jsstr)).code resolve err: 0 message: "compiled." result: code catch e reject err: -2 message: "compile error." result: err.message #============================================================================ # 保存先として指定された場所/ファイルをチェックする #============================================================================ path_check = (path) -> if (path.match(/\/$/)) # pathがスラッシュで終わっている type = 1 # ディレクトリ fname = undefined else # pathがスラッシュで終わっていない try # pathが既存ディレクトリ if (FS.statSync(path).isDirectory()) type = 1 # ディレクトリ fname = undefined else type = 2 # ファイル fname = PATH.basename("./#{path}") catch err # pathが存在しない type = -1 # 存在しない fname = undefined return type: type fname: fname #============================================================================ # メイン処理 #============================================================================ # 引数チェック ARGV.option name: "watch" short: "w" type: "string" description: "watch source file change." example: "terffee -wc [source file path]" ARGV.option name: "compile" short: "c" type: "path" description: "compile source file." example: "terffee -c [source file path]" ARGV.option name: "output" short: "o" type: "path" description: "compiled file output directory." example: "terffee -o [output directroy]" argopt = ARGV.run() target = process.argv target.splice(0, 2) argm = MINIMIST(target) #============================================================================ # コンパイルするソースファイル一覧を取得する #============================================================================ sourcepath_tmp = [] directotypath = [] sourcepath_tmp = argm._ c_opt = argm.c || argm.compile if (c_opt?) if (typeof c_opt == 'string') c_opt = [c_opt] #c_opt.push.apply(c_opt, argm._) sourcepath_tmp.push.apply(sourcepath_tmp, c_opt) #============================================================================ # コンパイル/minify化したファイルを保存する一覧を取得する  #============================================================================ outputlist_tmp = argm.o || argm.output if (typeof outputlist_tmp == "object") outputlist = outputlist_tmp else outputlist = [outputlist_tmp] #============================================================================ # 引数で指定されたソース一覧と保存先一覧を整理する #============================================================================ sourcepath = [] sourcepath_tmp.map (fpath, cnt) -> #=========================================================================== # ソースの種類(ファイルかディレクトリか)と存在するかチェック #=========================================================================== # 処理するファイル src = fpath stype = path_check(src).type # ソースに指定されたファイル/ディレクトリが存在する場合は処理する if (stype > 0) # 保存先リストからひとつ取り出す output = outputlist[cnt] || outputlist[outputlist.length-1] # 保存先がundefined if (!output?) # 保存先が無い場合は、保存先をsrcから生成する otype = 1 if (FS.statSync(src).isDirectory()) # srcがディレクトリだった output = src else # srcがファイルだった output = PATH.dirname(src) else # 保存先が存在する otype = path_check(output).type # outputが存在しなかったらファイル if (otype == -1) otype = 2 # srcの末尾に「/」があったら除去する src = src.replace(/\/*$/, "") # outputの末尾に「/」があったら除去する output = output.replace(/\/*$/, "") sourcepath.push src: src stype: stype output: output otype: otype #============================================================================ # ソースファイルが指定されていない #============================================================================ if (target.length == 0) ARGV.run(["-h"]) process.exit(1) #process.exit(0) if (argm.w || argm.watch) #========================================================================== # ソースファイル/ディレクトリ監視 #========================================================================== watchlist = [] for fn in sourcepath fn = fn.replace(/\/*$/, "") watchlist[watchlist.length] = CHOKIDAR.watch(fn) #persistent: true try # check compile target FS.accessSync(fn, FS.F_OK) watchlist[watchlist.length-1].on 'change', (fpath, stats) -> fname = PATH.basename(fpath) if (PATH.extname(fname) == ".coffee") console.log("compile and minify: [#{fname}]") .on 'add', (fpath, stats) -> fname = PATH.basename(fpath) if (PATH.extname(fname) == ".coffee") console.log("watching file: [#{fname}]") .on 'unlink', (fpath, stats) -> fname = PATH.basename(fpath) if (PATH.extname(fname) == ".coffee") console.log("delete file: [#{fname}]") catch err # target is not accessible else #process.exit(0) #========================================================================== # ソースファイルコンパイル #========================================================================== # ソース指定がディレクトリの場合を想定して展開する sourcepath_expand = [] for srcinfo in sourcepath src = srcinfo.src output = srcinfo.output stype = srcinfo.stype otype = srcinfo.otype if (stype == 1) # ディレクトリ files = FS.readdirSync(src) for srcfname in files srcfullpath = FORM("%@/%@", src, srcfname) if (!srcfname.match(/\.coffee$/) || FS.statSync(srcfullpath).isDirectory()) continue switch (otype) when 1 # 出力先がディレクトリ  ofile = "#{output}/"+PATH.basename(srcfname).replace(/\.coffee$/, ".min.js") when 2 # 出力先がファイル  ofile = output sourcepath_expand.push src: srcfullpath output: ofile else if (stype == 2) # ファイル if (!src.match(/\.coffee$/)) continue switch (otype) when 1 # 出力先がディレクトリ  ofile = "#{output}/"+PATH.basename(src).replace(/\.coffee$/, ".min.js") when 2 # 出力先がファイル  ofile = output sourcepath_expand.push src: src output: ofile createFileFlagTable = {} ASYNC.whilst -> # コンパイルするソースファイルがなくなったらループを抜ける if (sourcepath_expand.length > 0) return true else return false , (callback) -> # ファイルパスをひとつ取り出す srcinfo = sourcepath_expand.shift() src = srcinfo.src output = srcinfo.output compile(src).then (ret) -> code = ret.result if (createFileFlagTable[src.toString]?) return FS.appendFile(output, code, 'utf8') else return FS.outputFile(output, code, 'utf8') .then (err) -> if (!err?) createFileFlagTable[src.toString] = true callback(null, 1) .catch (ret) -> console.log ret.result if (ret.err) , (ret, result) -> err = ret.err message = ret.result if (err) switch (err) when -1, -2 console.log(ret.result) process.exit(0)