From 60b1df24d4c09e79bca81f1864a400b26eeaf500 Mon Sep 17 00:00:00 2001 From: Martin Gagnon Date: Tue, 23 Aug 2016 11:59:30 -0700 Subject: [PATCH] DRYer code, default directory --- coffeescript-concat.coffee | 278 ++++++++++++++----------------------- 1 file changed, 104 insertions(+), 174 deletions(-) diff --git a/coffeescript-concat.coffee b/coffeescript-concat.coffee index 023796a..670bef7 100644 --- a/coffeescript-concat.coffee +++ b/coffeescript-concat.coffee @@ -27,129 +27,90 @@ path = require('path') _ = require('underscore') options = require('yargs') -# 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]) - classNames - -findExternClasses = (file) -> +classRegex = /\n[^#\n]*class\s@?([A-Za-z_$-][A-Za-z0-9_$-.]*)/g +externRegex = /#=\s*extern\s+([A-Za-z_$-][A-Za-z0-9_$-.]*)/g +dependencyRegex = /\n[^#\n]*extends\s([A-Za-z_$-][A-Za-z0-9_$-.]*)/g +classDirectiveRegex = /#=\s*require\s+([A-Za-z_$-][A-Za-z0-9_$-]*)/g +fileDirectiveRegex = /#=\s*require\s+<([A-Za-z0-9_$-][A-Za-z0-9_$-.]*)>/g + +# Search through file for files to be included based on given regex +findSomething = (file, rx, names=[]) -> file = '\n' + file - externRegex = /#=\s*extern\s+([A-Za-z_$-][A-Za-z0-9_$-.]*)/g - classNames = [] - while (result = externRegex.exec(file)) != null - classNames.push(result[1]) - return classNames + while (result = rx.exec file) isnt null + names.push result[1] if result[1] isnt "this" + names -# 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 -# +# Search through file for class definitions ignoring those in comments +findClasses = (file) -> findSomething file, classRegex +findExternClasses = (file) -> findSomething file, externRegex + +# Search through file for dependencies marked by 'extends' and #= require ClassName +# statements, ignoring those in comments findClassDependencies = (file) -> - file = '\n' + file + dependencies = findSomething file, dependencyRegex + file = file.replace dependencyRegex, '' + findSomething file, classDirectiveRegex, dependencies - dependencyRegex = /\n[^#\n]*extends\s([A-Za-z_$-][A-Za-z0-9_$-.]*)/g - - dependencies = [] - while (result = dependencyRegex.exec(file)) != null - if result[1] != "this" - 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 +# Search through a file given as a string and find dependencies marked by # #= require -# -# -findFileDependencies = (file) -> - file = '\n' + file - - dependencies = [] - fileDirectiveRegex = /#=\s*require\s+<([A-Za-z0-9_$-][A-Za-z0-9_$-.]*)>/g - - while (result = fileDirectiveRegex.exec(file)) != null - dependencies.push(result[1]) - - return dependencies +findFileDependencies = (file) -> findSomething file, fileDirectiveRegex +# Given a list of directories, find all files recursively. The callback gets +# one argument (filesFound) where filesFound is a list of all the files +# present in each directory and subdirectory (excluding '.' and '..'). +# getFileNamesInDirsR = (dirs, filesFound, callback) -> if dirs.length > 0 nextDir = dirs[dirs.length-1] fs.readdir nextDir, (err, files) -> + throw err if err directories = [] - if err - throw err - else - for file in files - filePath = nextDir.replace(/\/$/, '') + '/' + file - stats = fs.statSync filePath - if stats.isDirectory() - directories.push filePath - else if stats.isFile() - filesFound.push filePath - - dirs.splice dirs.length-1, 1 - dirs = dirs.concat directories - - getFileNamesInDirsR dirs, filesFound, (innerFilesFound) -> - callback innerFilesFound + for file in files + filePath = nextDir.replace(/\/$/, '') + '/' + file + stats = fs.statSync filePath + if stats.isDirectory() + directories.push filePath + else if stats.isFile() + filesFound.push filePath + dirs.splice dirs.length-1, 1 + dirs = dirs.concat directories + getFileNamesInDirsR dirs, filesFound, (innerFilesFound) -> + callback innerFilesFound else callback filesFound -# Given a list of directories, find all files recursively. The callback gets -# one argument (filesFound) where filesFound is a list of all the files -# present in each directory and subdirectory (excluding '.' and '..'). -# -getFileNamesInDirs = (dirs, callback) -> - getFileNamesInDirsR dirs, [], callback +getFileNamesInDirs = (dirs, callback) -> getFileNamesInDirsR dirs, [], callback # 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, searchDirectoriesRecursive, callback) -> - files = sourceFiles for dir in searchDirectories - files = files.concat(path.join(dir, f) for f in fs.readdirSync(dir)) - + files = files.concat(path.join(dir, f) for f in fs.readdirSync dir) + getFileNamesInDirs searchDirectoriesRecursive, (filesFound) -> files = files.concat filesFound - fileDefs = [] - for file in files when /\.coffee$/.test(file) - contents = fs.readFileSync(file).toString() - classes = findClasses(contents) - extern = findExternClasses(contents) - dependencies = findClassDependencies(contents) - fileDependencies = findFileDependencies(contents) + for file in files when /\.coffee$/.test file + contents = fs.readFileSync(file).toString() + classes = findClasses contents + extern = findExternClasses contents + dependencies = findClassDependencies contents + fileDependencies = findFileDependencies contents #filter out the dependencies in the same file. - dependencies = _.select(dependencies, (d) -> _.indexOf(classes, d) == -1) - dependencies = _.select(dependencies, (d) -> _.indexOf(extern, d) == -1) - - fileDef = { - name: file, - classes: classes, - extern: extern, - dependencies: dependencies, - fileDependencies: fileDependencies, - contents: contents - } - fileDefs.push(fileDef) - + dependencies = _.select dependencies, (d) -> _.indexOf(classes, d) is -1 + dependencies = _.select dependencies, (d) -> _.indexOf(extern, d) is -1 + + fileDef = + name: file + classes: classes + extern: extern + dependencies: dependencies + fileDependencies: fileDependencies + contents: contents + fileDefs.push fileDef callback fileDefs # Given a list of files and their class/dependency information, @@ -160,101 +121,76 @@ mapDependencies = (sourceFiles, searchDirectories, searchDirectoriesRecursive, c # concatFiles = (sourceFiles, fileDefs, listFilesOnly) -> usedFiles = [] - allFileDefs = fileDefs.slice(0) - + allFileDefs = fileDefs.slice 0 # if sourceFiles was not specified by user concat all files that we found in directory if sourceFiles.length > 0 sourceFileDefs = (fd for fd in fileDefs when fd.name in sourceFiles) else sourceFileDefs = fileDefs - # 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 searchInClasses = fileDef.classes.concat fileDef.extern - for c in searchInClasses - 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 + return fileDef if c is className for c in searchInClasses + null + # Given a filename, find corresponding file definition + # 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 - + return fileDef if fileName is fileDef.name.replace /^.+\/([^/.]+)(\.[^/]+)*$/, '$1' + 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 + if _.indexOf(usedFiles, fileDef.name) isnt -1 return null - else if fileDef.dependencies.length == 0 and fileDef.fileDependencies.length == 0 - dependenciesStack.push(fileDef) - usedFiles.push(fileDef.name) + else if fileDef.dependencies.length is 0 and fileDef.fileDependencies.length is 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) + depFileDef = findFileDefByClass dependency + if depFileDef is null + console.error "Error: couldn't find needed class: " + dependency else - nextStack = resolveDependencies(depFileDef) - dependenciesStack = dependenciesStack.concat(if nextStack != null then nextStack else []) - + nextStack = resolveDependencies depFileDef + dependenciesStack = dependenciesStack.concat(if nextStack isnt 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) + neededFileDef = findFileDefByName neededFileName + if neededFileDef is 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 - + dependenciesStack = dependenciesStack.concat(if nextStack isnt null then nextStack else []) + if _.indexOf(usedFiles, fileDef.name) is -1 + dependenciesStack.push fileDef + usedFiles.push fileDef.name + dependenciesStack fileDefStack = [] while sourceFileDefs.length > 0 - nextFileDef = sourceFileDefs.pop() - resolvedDef = resolveDependencies(nextFileDef) - if resolvedDef - fileDefStack = fileDefStack.concat(resolvedDef) + nextFileDef = sourceFileDefs.pop() + resolvedDef = resolveDependencies nextFileDef + fileDefStack = fileDefStack.concat resolvedDef if resolvedDef # for f in fileDefStack # console.error(f.name) output = '' fileProp = if listFilesOnly then 'name' else 'contents' - for nextFileDef in fileDefStack - output += nextFileDef[fileProp] + '\n' - - return output + output += nextFileDef[fileProp] + '\n' for nextFileDef in fileDefStack + output -# remove all #= require directives from the -# source file. +# remove all #= require directives from 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 + file = file.replace fileDirectiveRegex, '' + file = file.replace classDirectiveRegex, '' + file # Given a list of source files, # a list of directories to look into for source files, @@ -262,30 +198,24 @@ removeDirectives = (file) -> # and a relative filename to output, # resolve the dependencies and put all classes in one file concatenate = (sourceFiles, includeDirectories, includeDirectoriesRecursive, outputFile, listFilesOnly) -> + # Add sourceFile's folder for include directories + for sourceFile in sourceFiles + sourceDir = sourceFile.replace /[^/]+$/g, '' + includeDirectories.push sourceDir unless sourceDir in includeDirectories mapDependencies sourceFiles, includeDirectories, includeDirectoriesRecursive, (deps) -> - - output = concatFiles(sourceFiles, deps, listFilesOnly) - output = removeDirectives(output) + output = removeDirectives concatFiles sourceFiles, deps, listFilesOnly if outputFile - fs.writeFile(outputFile, output, (err) -> - console.error err if err - ) + fs.writeFile outputFile, output, (err) -> console.error err if err else - console.log(output) - + console.log output -options. -usage("""Usage: coffeescript-concat [options] a.coffee b.coffee ... +options.usage("""Usage: coffeescript-concat [options] a.coffee b.coffee ... If no output file is specified, the resulting source will sent to stdout """). -describe('h', 'display this help'). -alias('h','help'). -describe('I', 'directory to search for files'). -alias('I', 'include-dir'). -describe('R', 'directory to search for files recursively'). -alias('R', 'include-dir-recursive'). -describe('o', 'output file name'). -alias('o', 'output-file'). +describe('h', 'display this help').alias('h','help'). +describe('I', 'directory to search for files').alias('I', 'include-dir'). +describe('R', 'directory to search for files recursively').alias('R', 'include-dir-recursive'). +describe('o', 'output file name').alias('o', 'output-file'). describe('list-files', 'list file names instead of outputting file contents') argv = options.argv @@ -295,4 +225,4 @@ sourceFiles = if typeof argv._ is 'string' then [argv._] else argv._ if argv.help || (includeDirectories.length==0 && includeDirectoriesRecursive.length==0 && sourceFiles.length==0) options.showHelp() -concatenate(sourceFiles, includeDirectories, includeDirectoriesRecursive, argv.o, argv['list-files']) +concatenate sourceFiles, includeDirectories, includeDirectoriesRecursive, argv.o, argv['list-files']