UNPKG

coffeescript-concat

Version:

A utility for combining coffeescript files and resolving their dependencies.

227 lines (187 loc) 7.72 kB
# coffeescript-concat.coffee # # Copyright (C) 2010-2011 Tom Fairfield # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages # arising from the use of this software. # # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it # freely, subject to the following restrictions: # # 1. The origin of this software must not be misrepresented; you must not # claim that you wrote the original software. If you use this software # in a product, an acknowledgment in the product documentation would be # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. # # Tom Fairfield <fairfield@cs.xu.edu> # util = require('util') fs = require('fs') path = require('path') _ = require('underscore') # Search through a file and find all class definitions, # ignoring those in comments # findClasses = (file) -> file = '\n' + file classRegex = /\n[^#\n]*class\s([A-Za-z_$-][A-Za-z0-9_$-]*)/g classNames = [] while (result = classRegex.exec(file)) != null classNames.push(result[1]) return classNames # Search through a file and find all dependencies, # which is be done by finding all 'exends' # statements. Ignore those in comments # also find the dependencies marked by #= require ClassName # findClassDependencies = (file) -> file = '\n' + file dependencyRegex = /\n[^#\n]*extends\s([A-Za-z_$-][A-Za-z0-9_$-]*)/g dependencies = [] while (result = dependencyRegex.exec(file)) != null dependencies.push(result[1]) file = file.replace(dependencyRegex, '') classDirectiveRegex = /#=\s*require\s+([A-Za-z_$-][A-Za-z0-9_$-]*)/g while (result = classDirectiveRegex.exec(file)) != null dependencies.push(result[1]) return dependencies # Search through a file, given as a string and find the dependencies marked by # #= require <FileName> # # findFileDependencies = (file) -> file = '\n' + file dependencies = [] fileDirectiveRegex = /#=\s*require\s+<([A-Za-z_$-][A-Za-z0-9_$-.]*)>/g while (result = fileDirectiveRegex.exec(file)) != null dependencies.push(result[1]) return dependencies # Given a path to a directory and, optionally, a list of search directories #, create a list of all files with the # classes they contain and the classes those classes depend on. # mapDependencies = (sourceFiles, searchDirectories) -> files = sourceFiles for dir in searchDirectories files = files.concat(path.join(dir, f) for f in fs.readdirSync(dir)) fileDefs = [] for file in files when /\.coffee$/.test(file) contents = fs.readFileSync(file).toString() classes = findClasses(contents) dependencies = findClassDependencies(contents) fileDependencies = findFileDependencies(contents) #filter out the dependencies in the same file. dependencies = _.select(dependencies, (d) -> _.indexOf(classes, d) == -1) fileDef = {name: file, classes: classes, dependencies: dependencies, fileDependencies: fileDependencies, contents: contents} fileDefs.push(fileDef) return fileDefs # Given a list of files and their class/dependency information, # traverse the list and put them in an order that satisfies dependencies. # Walk through the list, taking each file and examining it for dependencies. # If it doesn't have any it's fit to go on the list. If it does, find the file(s) # that contain the classes dependencies. These must go first in the hierarchy. # concatFiles = (sourceFiles, fileDefs) -> usedFiles = [] allFileDefs = fileDefs.slice(0) sourceFileDefs = (fd for fd in fileDefs when fd.name in sourceFiles) # Given a class name, find the file that contains that # class definition. If it doesn't exist or we don't know # about it, return null findFileDefByClass = (className) -> for fileDef in allFileDefs for c in fileDef.classes if c == className return fileDef return null # Given a filename, find the file definition that # corresponds to it. If the file isn't found, # return null findFileDefByName = (fileName) -> for fileDef in allFileDefs temp = fileDef.name.split('/') name = temp[temp.length-1].split('.')[0] if fileName == name return fileDef return null # recursively resolve the dependencies of a file. If it # has no dependencies, return that file in an array. Otherwise, # find the files with the needed classes and resolve their dependencies # resolveDependencies = (fileDef) -> dependenciesStack = [] if _.indexOf(usedFiles, fileDef.name) != -1 return null else if fileDef.dependencies.length == 0 and fileDef.fileDependencies.length == 0 dependenciesStack.push(fileDef) usedFiles.push(fileDef.name) else dependenciesStack = [] for dependency in fileDef.dependencies depFileDef = findFileDefByClass(dependency) if depFileDef == null console.error("Error: couldn't find needed class: " + dependency) else nextStack = resolveDependencies(depFileDef) dependenciesStack = dependenciesStack.concat(if nextStack != null then nextStack else []) for neededFile in fileDef.fileDependencies neededFileName = neededFile.split('.')[0] neededFileDef = findFileDefByName(neededFileName) if neededFileDef == null console.error("Error: couldn't find needed file: " + neededFileName) else nextStack = resolveDependencies(neededFileDef) dependenciesStack = dependenciesStack.concat(if nextStack != null then nextStack else []) if _.indexOf(usedFiles, fileDef.name) == -1 dependenciesStack.push(fileDef) usedFiles.push(fileDef.name) return dependenciesStack fileDefStack = [] while sourceFileDefs.length > 0 nextFileDef = sourceFileDefs.pop() resolvedDef = resolveDependencies(nextFileDef) if resolvedDef fileDefStack = fileDefStack.concat(resolvedDef) # for f in fileDefStack # console.error(f.name) output = '' for nextFileDef in fileDefStack output += nextFileDef.contents + '\n' return output # remove all #= require directives from the # source file. removeDirectives = (file) -> fileDirectiveRegex = /#=\s*require\s+<([A-Za-z_$-][A-Za-z0-9_$-.]*)>/g classDirectiveRegex = /#=\s*require\s+([A-Za-z_$-][A-Za-z0-9_$-]*)/g file = file.replace(fileDirectiveRegex, '') file = file.replace(classDirectiveRegex, '') return file # Given a source directory, a relative filename to output # to, and optionally a list of class names to ignore, # resolve the dependencies and put all classes in one file # concatenate = (sourceFiles, includeDirectories, outputFile) -> deps = mapDependencies(sourceFiles, includeDirectories) output = concatFiles(sourceFiles, deps) output = removeDirectives(output) if outputFile fs.writeFile(outputFile, output) else util.puts(output) argv = require('optimist'). usage("""Usage: coffee coffeescript-concat.coffee [-I .] [-o outputfile.coffee] a.coffee b.coffee If no output file is specified, the resulting source will sent to stdout """). describe('I', 'directory to search for files'). alias('I', 'include-dir'). describe('o', 'output file name'). alias('o', 'output-file'). demand(2).argv includeDirectories = argv.I sourceFiles = argv._ concatenate(sourceFiles, includeDirectories, argv.o)