path = require 'path' Handlebars = require 'handlebars' marked = require 'marked' cjsxtransform = require 'coffee-react-transform' rimraf = require 'rimraf' fs = require 'fs-plus' _ = require 'underscore-plus' donna = require 'donna' tello = require 'tello' moduleBlacklist = [ 'space-pen' ] marked.setOptions highlight: (code) -> require('highlight.js').highlightAuto(code).value standardClassURLRoot = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/' standardClasses = [ 'string', 'object', 'array', 'function', 'number', 'date', 'error', 'boolean', 'null', 'undefined', 'json', 'set', 'map', 'typeerror', 'syntaxerror', 'referenceerror', 'rangeerror', ] thirdPartyClasses = { 'react.component': 'https://facebook.github.io/react/docs/component-api.html', 'promise': 'https://github.com/petkaantonov/bluebird/blob/master/API.md' } module.exports = (grunt) -> getClassesToInclude = -> modulesPath = path.resolve(__dirname, '..', '..', 'internal_packages') classes = {} fs.traverseTreeSync modulesPath, (modulePath) -> # Don't traverse inside dependencies return false if modulePath.match(/node_modules/g) # Don't traverse blacklisted packages (that have docs, but we don't want to include) return false if path.basename(modulePath) in moduleBlacklist return true unless path.basename(modulePath) is 'package.json' return true unless fs.isFileSync(modulePath) apiPath = path.join(path.dirname(modulePath), 'api.json') if fs.isFileSync(apiPath) _.extend(classes, grunt.file.readJSON(apiPath).classes) true classes sortClasses = (classes) -> sortedClasses = {} for className in Object.keys(classes).sort() sortedClasses[className] = classes[className] sortedClasses processFields = (json, fields = [], tasks = []) -> if json instanceof Array for val in json processFields(val, fields, tasks) else for key, val of json if key in fields for task in tasks val = task(val) json[key] = val if _.isObject(val) processFields(val, fields, tasks) grunt.registerTask 'build-docs', 'Builds the API docs in src', -> done = @async() # Convert CJSX into coffeescript that can be read by Donna docsOutputDir = grunt.config.get('docsOutputDir') cjsxOutputDir = path.join(docsOutputDir, 'temp-cjsx') rimraf cjsxOutputDir, -> fs.mkdir(cjsxOutputDir) srcPath = path.resolve(__dirname, '..', '..', 'src') fs.traverseTreeSync srcPath, (file) -> if path.extname(file) is '.cjsx' transformed = cjsxtransform(grunt.file.read(file)) # Only attempt to parse this file as documentation if it contains # real Coffeescript classes. if transformed.indexOf('\nclass ') > 0 grunt.file.write(path.join(cjsxOutputDir, path.basename(file)[0..-5]+'coffee'), transformed) true # Process coffeescript source metadata = donna.generateMetadata(['.', cjsxOutputDir]) api = tello.digest(metadata) _.extend(api.classes, getClassesToInclude()) api.classes = sortClasses(api.classes) apiJson = JSON.stringify(api, null, 2) apiJsonPath = path.join(docsOutputDir, 'api.json') grunt.file.write(apiJsonPath, apiJson) done() grunt.registerTask 'render-docs', 'Builds html from the API docs', -> docsOutputDir = grunt.config.get('docsOutputDir') apiJsonPath = path.join(docsOutputDir, 'api.json') templatesPath = path.resolve(__dirname, '..', '..', 'docs-templates') grunt.file.recurse templatesPath, (abspath, root, subdir, filename) -> if filename[0] is '_' and path.extname(filename) is '.html' Handlebars.registerPartial(filename[0..-6], grunt.file.read(abspath)) templatePath = path.join(templatesPath, 'class.html') template = Handlebars.compile(grunt.file.read(templatePath)) api = JSON.parse(grunt.file.read(apiJsonPath)) classnames = _.map Object.keys(api.classes), (s) -> s.toLowerCase() console.log("Generating HTML for #{classnames.length} classes") expandTypeReferences = (val) -> refRegex = /{([\w.]*)}/g while (match = refRegex.exec(val)) isnt null classname = match[1].toLowerCase() url = false if classname in standardClasses url = standardClassURLRoot+classname else if thirdPartyClasses[classname] url = thirdPartyClasses[classname] else if classname in classnames url = "./#{classname}.html" else console.warn("Cannot find class named #{classname}") if url val = val.replace(match[0], "#{match[1]}") val expandFuncReferences = (val) -> refRegex = /{([\w]*)?::([\w]*)}/g while (match = refRegex.exec(val)) isnt null [text, a, b] = match url = false if a and b url = "#{a}.html##{b}" label = "#{a}::#{b}" else url = "##{b}" label = "#{b}" if url val = val.replace(text, "#{label}") val for classname, contents of api.classes processFields(contents, ['description'], [marked, expandTypeReferences, expandFuncReferences]) processFields(contents, ['type'], [expandTypeReferences]) result = template(contents) resultPath = path.join(docsOutputDir, "#{classname}.html") grunt.file.write(resultPath, result)