1 | fs = require 'fs'
|
2 | util = require 'util'
|
3 | path = require 'path'
|
4 | walkdir = require 'walkdir'
|
5 | Async = require 'async'
|
6 | _ = require 'underscore'
|
7 | CoffeeScript = require 'coffee-script'
|
8 |
|
9 | Parser = require './parser'
|
10 | Metadata = require './metadata'
|
11 | {exec} = require 'child_process'
|
12 |
|
13 | SRC_DIRS = ['src', 'lib', 'app']
|
14 | BLACKLIST_FILES = ['Gruntfile.coffee']
|
15 |
|
16 | main = ->
|
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 |
|
49 | generateMetadata = (inputs) ->
|
50 | metadataSlugs = []
|
51 |
|
52 | for input in inputs
|
53 | continue unless (fs.existsSync || path.existsSync)(input)
|
54 | parser = new Parser()
|
55 |
|
56 |
|
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 |
|
79 | logError = (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 |
|
85 | isAcceptableFile = (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 |
|
94 | isInAcceptableDir = (inputPath, filePath) ->
|
95 |
|
96 | return true if path.join(inputPath, path.basename(filePath)) is filePath
|
97 |
|
98 |
|
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 |
|
105 | writeMetadata = (metadataSlugs, output) ->
|
106 | fs.writeFileSync path.join(output, 'metadata.json'), JSON.stringify(metadataSlugs, null, " ")
|
107 |
|
108 |
|
109 | generateMetadataSlug = (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 |
|
127 | populateSlug = (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 |
|
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 |
|
148 | findMainFile = (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 |
|
168 | module.exports = {Parser, Metadata, main, generateMetadata, generateMetadataSlug, populateSlug}
|