UNPKG

5.11 kBtext/coffeescriptView Raw
1fs = require 'fs'
2util = require 'util'
3path = require 'path'
4walkdir = require 'walkdir'
5Async = require 'async'
6_ = require 'underscore'
7CoffeeScript = require 'coffee-script'
8
9Parser = require './parser'
10Metadata = require './metadata'
11{exec} = require 'child_process'
12
13SRC_DIRS = ['src', 'lib', 'app']
14BLACKLIST_FILES = ['Gruntfile.coffee']
15
16main = ->
17 optimist = require('optimist')
18 .usage("""
19 Usage: $0 [options] [source_files]
20 """)
21 .options('o',
22 alias: 'output-dir'
23 describe: 'The output directory'
24 default: './doc'
25 )
26 .options('d',
27 alias: 'debug'
28 describe: 'Show stacktraces and converted CoffeeScript source'
29 boolean: true
30 default: false
31 )
32 .options('h',
33 alias: 'help'
34 describe: 'Show the help'
35 )
36
37 argv = optimist.argv
38
39 if argv.h
40 console.log optimist.help()
41 return
42
43 options =
44 inputs: argv._
45 output: argv.o
46
47 writeMetadata(generateMetadata(options.inputs), options.output)
48
49generateMetadata = (inputs) ->
50 metadataSlugs = []
51
52 for input in inputs
53 continue unless (fs.existsSync || path.existsSync)(input)
54 parser = new Parser()
55
56 # collect probable package.json path
57 packageJsonPath = path.join(input, 'package.json')
58 stats = fs.lstatSync input
59 absoluteInput = path.resolve(process.cwd(), input)
60
61 if stats.isDirectory()
62 for filename in walkdir.sync input
63 if isAcceptableFile(filename) and isInAcceptableDir(absoluteInput, filename)
64 try
65 parser.parseFile(filename, absoluteInput)
66 catch error
67 logError(filename, error)
68 else
69 if isAcceptableFile(input)
70 try
71 parser.parseFile(input, path.dirname(input))
72 catch error
73 logError(filename, error)
74
75 metadataSlugs.push generateMetadataSlug(packageJsonPath, parser)
76
77 metadataSlugs
78
79logError = (filename, error) ->
80 if error.location?
81 console.warn "Cannot parse file #{ filename }@#{error.location.first_line}: #{ error.message }"
82 else
83 console.warn "Cannot parse file #{ filename }: #{ error.message }"
84
85isAcceptableFile = (filePath) ->
86 try
87 return false if fs.statSync(filePath).isDirectory()
88
89 for file in BLACKLIST_FILES
90 return false if new RegExp(file+'$').test(filePath)
91
92 filePath.match(/\._?coffee$/)
93
94isInAcceptableDir = (inputPath, filePath) ->
95 # is in the root of the input?
96 return true if path.join(inputPath, path.basename(filePath)) is filePath
97
98 # is under src, lib, or app?
99 acceptableDirs = (path.join(inputPath, dir) for dir in SRC_DIRS)
100 for dir in acceptableDirs
101 return true if filePath.indexOf(dir) == 0
102
103 false
104
105writeMetadata = (metadataSlugs, output) ->
106 fs.writeFileSync path.join(output, 'metadata.json'), JSON.stringify(metadataSlugs, null, " ")
107
108# Public: Builds and writes to metadata.json
109generateMetadataSlug = (packageJsonPath, parser) ->
110 if fs.existsSync(packageJsonPath)
111 packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
112
113 metadata = new Metadata(packageJson?.dependencies ? {}, parser)
114 slug =
115 main: findMainFile(packageJsonPath, packageJson?.main)
116 repository: packageJson?.repository?.url ? packageJson?.repository
117 version: packageJson?.version
118 files: {}
119
120 for filename, content of parser.iteratedFiles
121 metadata.generate(CoffeeScript.nodes(content))
122 populateSlug(slug, filename, metadata)
123
124 slug
125
126# Public: Parse and collect metadata slugs
127populateSlug = (slug, filename, {defs:unindexedObjects, exports:exports}) ->
128 objects = {}
129 for key, value of unindexedObjects
130 startLineNumber = value.range[0][0]
131 startColNumber = value.range[0][1]
132 objects[startLineNumber] = {} unless objects[startLineNumber]?
133 objects[startLineNumber][startColNumber] = value
134 # Update the classProperties/prototypeProperties to be line numbers
135 if value.type is 'class'
136 value.classProperties = ( [prop.range[0][0], prop.range[0][1]] for prop in _.clone(value.classProperties))
137 value.prototypeProperties = ([prop.range[0][0], prop.range[0][1]] for prop in _.clone(value.prototypeProperties))
138
139 if exports._default?
140 exports = exports._default.range[0][0] if exports._default.range?
141 else
142 for key, value of exports
143 exports[key] = value.startLineNumber
144
145 slug["files"][filename] = {objects, exports}
146 slug
147
148findMainFile = (packageJsonPath, main_file) ->
149 return unless main_file?
150
151 if main_file.match(/\.js$/)
152 main_file = main_file.replace(/\.js$/, ".coffee")
153 else
154 main_file += ".coffee"
155
156 filename = path.basename(main_file)
157 filepath = path.dirname(packageJsonPath)
158
159 for dir in SRC_DIRS
160 composite_main = path.normalize path.join(filepath, dir, filename)
161
162 if fs.existsSync composite_main
163 file = path.relative(packageJsonPath, composite_main)
164 file = file.substring(1, file.length) if file.match /^\.\./
165 return file
166
167# TODO: lessen the suck enough to remove generateMetadataSlug and populateSlug. They really shouldnt be necessary.
168module.exports = {Parser, Metadata, main, generateMetadata, generateMetadataSlug, populateSlug}