├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── README.md ├── common ├── asyncfunc.js ├── config.js ├── func.js └── global.js ├── doc.json ├── example └── prepare-commit-msg.js ├── index.js ├── package.json ├── src ├── MarkdownBuild.js ├── createApiDOC.js ├── createDbDOC.js └── index.js ├── test.js └── test └── test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /example/prepare-commit-msg.js -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "threerocks", 3 | "globals": { 4 | "Promise":true, 5 | "mkdirp":true, 6 | "dox":true, 7 | "colors":true, 8 | "path":true, 9 | "markdownBuild":true, 10 | "func":true, 11 | "config":true, 12 | "co":true, 13 | "asyncFunc":true 14 | } 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .DS_store 3 | .idea 4 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 一款自动化生成文档工具 2 | 3 | ## 简介 4 | 5 | 工具可以自动化生成数据库和API接口的markdown文档,并通过修改git hooks,使项目的每次commit都会自动更新文档。 6 | 7 | ## 安装 8 | 9 | `npm i createDOC -g` 10 | 11 | ## 配置 12 | - 在项目根目录使用`createDOC init`命令初始化,该命令会在当前目录创建`doc.json`文件。 13 | - 生成`doc.json`文件后,需要详细配置数据库schemas存储路径(目前只支持关系型数据库),以及路由控制文件,以及子路由目录。 14 | - API注释规则,遵循TJ大神dox规范,简化版规则如下 15 | 16 | ```js 17 | /** 18 | * API description 19 | * 20 | * @param {type} name/name=default_value description 21 | * @return {String} description 22 | * @example 23 | * any example 24 | * 25 | * @other description 26 | */ 27 | ``` 28 | 29 | *ps: 此工具为内部使用工具,如个人使用可下载源码,做简单修改即可* 30 | ## 使用 31 | 32 | ```sh 33 | Usage: createDOC [options] [command] 34 | 35 | Commands: 36 | 37 | init 初始化当前目录doc.json文件 38 | show 显示配置文件状态 39 | run 启动程序 40 | modifyhook 修改项目下的hook文件 41 | * 42 | 43 | Options: 44 | 45 | -h, --help output usage information 46 | -V, --version output the version number 47 | 48 | Examples: 49 | 50 | $ createDOC --help 51 | $ createDOC -h 52 | $ createDOC show 53 | ``` 54 | 55 | ## 示例说明 56 | doc.json示例 57 | 58 | ```json 59 | { 60 | "db": { 61 | "schemas": "/Users/mac/Desktop/testssss/schemas", 62 | "markdown": { 63 | "path": "/Users/mac/Desktop/testssss/doc1/", 64 | "file": "db.md" 65 | } 66 | }, 67 | "api": { 68 | "controller": "/Users/mac/Desktop/testssss", 69 | "routes": "/Users/mac/Desktop/testssss", 70 | "markdown": { 71 | "path": "/Users/mac/Desktop/testssss/doc1", 72 | "file": "api.md" 73 | } 74 | } 75 | } 76 | ``` 77 | schema.js示例 78 | 79 | ```js 80 | module.exports = function(sequelize, DataTypes) { 81 | return sequelize.define('test_zk_absence', { 82 | //这是id 83 | id: { 84 | type: DataTypes.INTEGER, 85 | allowNull: false, 86 | primaryKey: true, 87 | autoIncrement: true 88 | }, 89 | //这是end_data 90 | end_date: { 91 | type: DataTypes.DATE, 92 | allowNull: true 93 | }, 94 | /* 95 | { 96 | a:1, 97 | b:2, 98 | c:{a:2}, 99 | d:'2222' 100 | } 101 | */ 102 | type_id: { 103 | type: DataTypes.INTEGER, 104 | allowNull: true 105 | }, 106 | updated_at: { 107 | type: DataTypes.DATE, 108 | allowNull: false 109 | } 110 | }, { 111 | tableName: 'test_zk_absence' 112 | }); 113 | }; 114 | ``` 115 | api注释示例 116 | 117 | ```js 118 | /** 119 | * 获取多个课程 120 | * @param {Number} examinationId 考试类型 121 | * @param {Number} subjectId 科目类型 122 | * @param {Number} statusId=3 状态类型 123 | * @param {String} startDate 更新开始时间 124 | * @param {String} endDate 更新结束时间 125 | * @param {String} keyword 关键词 126 | * @param {Number} page 页码 127 | * @return {Array} ok 128 | * @example [1,2,3,3,4] 129 | */ 130 | getCourses(req, params) { 131 | ... ... 132 | } 133 | ``` 134 | ## TODO 135 | 1. 代码逻辑优化,适应力更强。 136 | 2. 代码速度、质量优化。 137 | 3. 加入单元测试 138 | 139 | [github地址](https://github.com/threerocks/buildDOC) -------------------------------------------------------------------------------- /common/asyncfunc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const handlebars = require('handlebars'); 4 | const createApiDoc = require('./../src/createApiDOC'), 5 | craeteDbDoc = require('./../src/createDbDOC'); 6 | 7 | const fs = Promise.promisifyAll(require('fs')); 8 | 9 | //初始化新建doc.json,处理函数 10 | const newDoc = function* (inputInfos) { 11 | if (!Array.isArray(inputInfos) || inputInfos.length <= 0) return; 12 | for (const inputinfo of inputInfos) { 13 | const data = { 14 | schemas: '', 15 | dbPath: '', 16 | dbFile: '', 17 | controller: '', 18 | routes: '', 19 | apiPath: '', 20 | apiFile: '', 21 | }; 22 | switch (inputinfo.title) { 23 | case 'modifyConfirm' || 'initConfirm': 24 | break; 25 | case 'defaultConfirm': { 26 | const template = handlebars.compile(config.docjson); 27 | const result = template(data); 28 | if (inputinfo.value === 'n' || inputinfo.value === 'N') { 29 | yield fs.writeFileAsync(process.cwd() + '/doc.json', result); 30 | break; 31 | } 32 | data.schemas = './schemas/'; 33 | data.dbPath = './doc/'; 34 | data.dbFile = 'db.md'; 35 | data.controller = './controller.js'; 36 | data.routes = './routes'; 37 | data.apiPath = './doc'; 38 | data.apiFile = 'api.md'; 39 | yield fs.writeFileAsync(process.cwd() + '/doc.json', result); 40 | break; 41 | } 42 | case 'showConfig': { 43 | if (inputinfo.value === 'n' || inputinfo.value === 'N') break; 44 | const content = yield fs.readFileAsync(process.cwd() + '/doc.json', 'utf-8'); 45 | console.log(''); 46 | console.log(config.colors.rainbow('======= doc.json ========')); 47 | console.log(content); 48 | console.log(config.colors.rainbow('==== 请继续配置详细路径 ====')); 49 | console.log(''); 50 | break; 51 | } 52 | default: 53 | break; 54 | } 55 | } 56 | }; 57 | 58 | const mkdir = function (path) { 59 | return new Promise((resolve, reject) => { 60 | mkdirp(path, (err) => { 61 | if (err) reject(err); 62 | else resolve(); 63 | }); 64 | }); 65 | }; 66 | 67 | const mkdirs = function* (str, path) { // eslint-disable-line 68 | let pathname = config.colors.red(path); 69 | if (Array.isArray(path)) { 70 | if (path.length === 2) { 71 | pathname = config.colors.red(path[0]) + ' 和 ' + config.colors.red(path[1]); 72 | } else { 73 | return; 74 | } 75 | } 76 | process.stdout.write(config.colors.red(str) + '输出目录' + pathname + '不存在,是否创建 (y/n):'); 77 | process.stdin.resume(); 78 | process.stdin.setEncoding('utf-8'); 79 | process.stdin.on('data', (chunk) => { 80 | co(function* () { 81 | chunk = chunk.replace(/[\s\n]/, ''); 82 | if (chunk !== 'y' && chunk !== 'Y' && chunk !== 'n' && chunk !== 'N') { 83 | console.log(config.colors.red('您输入的命令是: ' + chunk)); 84 | console.warn(config.colors.red('请输入正确指令:y/n')); 85 | process.exit(); 86 | } 87 | process.stdin.pause(); 88 | if (chunk === 'y' || chunk === 'Y') { 89 | if (Array.isArray(path)) { 90 | yield mkdir(path[0]); 91 | console.log(config.colors.red(path[0]) + '创建成功'); 92 | yield mkdir(path[1]); 93 | console.log(config.colors.red(path[1]) + '创建成功'); 94 | process.exit(); 95 | } 96 | yield mkdir(path); 97 | console.log(config.colors.red(path) + '创建成功'); 98 | process.exit(); 99 | } else if (chunk === 'n' || chunk === 'N') { 100 | process.exit(); 101 | } 102 | }); 103 | }); 104 | }; 105 | 106 | const modifyHook = function* (file) { 107 | try { 108 | const inputFile = process.cwd() + '/.git/hooks/prepare-commit-msg', 109 | content = yield fs.readFileAsync(file); 110 | yield fs.writeFileAsync(inputFile, content); 111 | console.log('修改 ' + config.colors.red(inputFile) + ' 成功。'); 112 | } catch (err) { 113 | console.warn(err); 114 | } 115 | }; 116 | 117 | const exists = function (file) { 118 | return new Promise((resolve, reject) => { // eslint-disable-line 119 | fs.exists(file, (exists) => { 120 | if (!exists) resolve(exists); 121 | resolve(exists); 122 | }); 123 | }); 124 | }; 125 | 126 | exports.initAction = function* () { 127 | try { 128 | const docPath = yield exists(process.cwd() + '/doc.json'); 129 | if (docPath) { 130 | func.initRepl(config.coverInit, arr => { 131 | co(newDoc(arr)); 132 | }); 133 | } else { 134 | func.initRepl(config.newInit, arr => { 135 | co(newDoc(arr)); 136 | }); 137 | } 138 | } catch (err) { 139 | console.warn(err); 140 | } 141 | }; 142 | 143 | exports.showAction = function* () { 144 | try { 145 | const docPath = yield exists(process.cwd() + '/doc.json'); 146 | if (docPath) { 147 | const doc = require(process.cwd() + '/doc.json'); // eslint-disable-line 148 | 149 | doc.db.markdown.path = func.checkPath(doc.db.markdown.path); 150 | doc.db.schemas = func.checkPath(doc.db.schemas); 151 | doc.api.markdown.path = func.checkPath(doc.api.markdown.path); 152 | doc.api.routes = func.checkPath(doc.api.routes); 153 | 154 | const dbMarkdownPath = yield exists(doc.db.markdown.path), 155 | dbSchemas = yield exists(doc.db.schemas), 156 | apiController = yield exists(doc.api.controller), 157 | apiMarkdownPath = yield exists(doc.api.markdown.path), 158 | apiRouters = yield exists(doc.api.routes); 159 | 160 | console.log(config.colors.rainbow(config.showDescription)); 161 | console.log(config.colors.red(` 162 | ${docPath ? '√' : 'X'}`) + ` doc.json -> ${process.cwd()}/doc.json 163 | `); 164 | console.log(' db:'); 165 | console.log(config.colors.red(`${dbSchemas ? '√' : 'X'}`) + ` 输入 <- ${doc.db.schemas}`); 166 | console.log(config.colors.red(`${dbMarkdownPath ? '√' : 'X'}`) + ` 输出 -> ${doc.db.markdown.path}${doc.db.markdown.file}`); 167 | console.log(' api:'); 168 | console.log(config.colors.red(`${apiController ? '√' : 'X'}`) + ` 控制 <- ${doc.api.controller}`); 169 | console.log(config.colors.red(`${apiRouters ? '√' : 'X'}`) + ` 输入 <- ${doc.api.routes}`); 170 | console.log(config.colors.red(`${apiMarkdownPath ? '√' : 'X'}`) + ` 输出 -> ${doc.api.markdown.path}${doc.api.markdown.file}`); 171 | console.log(''); 172 | if (!dbMarkdownPath && apiMarkdownPath) { 173 | yield mkdirs('db ', doc.db.markdown.path); 174 | } 175 | if (!apiMarkdownPath && dbMarkdownPath) { 176 | yield mkdirs('api ', doc.api.markdown.path); 177 | } 178 | if (!apiMarkdownPath && !dbMarkdownPath) { 179 | yield mkdirs('db 和 api ', [ doc.db.markdown.path, doc.api.markdown.path ]); 180 | } 181 | return; 182 | } else { 183 | console.warn(config.nofile); 184 | return; 185 | } 186 | } catch (err) { 187 | console.warn(err); 188 | } 189 | }; 190 | 191 | exports.runAction = function* () { 192 | try { 193 | const docPath = yield exists(process.cwd() + '/doc.json'); 194 | if (docPath) { 195 | const doc = require(process.cwd() + '/doc.json'); // eslint-disable-line 196 | // 处理db文档 197 | doc.db.markdown.path = func.checkPath(doc.db.markdown.path); 198 | doc.db.schemas = func.checkPath(doc.db.schemas); 199 | yield craeteDbDoc.createDOC(doc.db.schemas, doc.db.markdown); 200 | // 处理api文档 201 | doc.api.markdown.path = func.checkPath(doc.api.markdown.path); 202 | doc.api.routes = func.checkPath(doc.api.routes); 203 | yield createApiDoc.createDOC(doc.api.controller, doc.api.routes, doc.api.markdown); 204 | } else { 205 | console.warn(config.nofile); 206 | return; 207 | } 208 | } catch (err) { 209 | console.log(err); 210 | } 211 | }; 212 | 213 | exports.modifyhookAction = function* () { 214 | try { 215 | console.log(config.startModifyhook); 216 | const commitSamplePath = yield exists(process.cwd() + '/.git/hooks/prepare-commit-msg.sample'), 217 | commitPath = yield exists(process.cwd() + '/.git/hooks/prepare-commit-msg'); 218 | if (!commitPath && !commitSamplePath) { 219 | console.log(config.colors.red(config.nohook)); 220 | } else if (commitSamplePath) { 221 | yield fs.renameAsync(process.cwd() + '/.git/hooks/prepare-commit-msg.sample', 222 | process.cwd() + '/.git/hooks/prepare-commit-msg'); 223 | yield modifyHook(path.resolve(__dirname, '..') + '/example/prepare-commit-msg.js'); 224 | } else if (commitPath) { 225 | yield modifyHook(path.resolve(__dirname, '..') + '/example/prepare-commit-msg.js'); 226 | } 227 | } catch (err) { 228 | console.warn(err); 229 | } 230 | }; 231 | 232 | //遍历目录文件,包括子目录 233 | const getFiles = function* (dir) { 234 | try { 235 | let filesArr = []; 236 | const files = yield fs.readdirAsync(dir); 237 | for (const file of files) { 238 | const pathName = dir + file, 239 | info = yield fs.statAsync(pathName); 240 | if (info.isDirectory()) { 241 | filesArr = filesArr.concat(yield getFiles(pathName + '/')); 242 | } 243 | if (path.extname(file) === '.js') { 244 | filesArr.push(dir + file); 245 | } 246 | } 247 | return filesArr; 248 | } catch (err) { 249 | console.warn(err); 250 | } 251 | }; 252 | exports.getAllFiles = getFiles; 253 | 254 | exports.getRoutes = function* (file) { 255 | const request = [ 'GET', 'POST', 'PUT', 'DELETE', 'INPUT', 'TRACE', 'OPTIONS', 'HEAD' 256 | , 'get', 'post', 'put', 'delete', 'input', 'trace', 'options', 'head' ]; 257 | const content = yield fs.readFileAsync(file); 258 | const dirtyRoutes = content.toString().match(/\[.*\]/g); 259 | const routes = [], 260 | data = {}; 261 | for (const dirtrRoute of dirtyRoutes) { 262 | const result = dirtrRoute.replace(/[\[\]\s\']/g, '').split(','); 263 | if (request.indexOf(result[0]) > -1) { 264 | routes.push(result); 265 | } 266 | } 267 | for (const route of routes) { 268 | const key = route[3], 269 | value = {}; 270 | value.path = route[1]; 271 | value.method = route[0]; 272 | value.group = route[2].split('.')[1] || route[2]; 273 | data[key] = value; 274 | } 275 | return data; 276 | }; 277 | 278 | 279 | 280 | const filterObj = function (routes, doxObjs) { 281 | const pureObjArr = {}; 282 | doxObjs.map((obj) => { 283 | let key = ''; 284 | const value = {}; 285 | if (obj.ctx) { 286 | const params = []; 287 | key = obj.ctx.name.replace(/[\*]/, ''); 288 | value.type = obj.ctx.type; 289 | value.description = obj.description.full; 290 | if (obj.tags && Array.isArray(obj.tags)) { 291 | for (const tag of obj.tags) { 292 | if (tag.type === 'param') { 293 | const param = {}; 294 | if (/\=/.test(tag.name)) { 295 | const name = tag.name.split('='); 296 | tag.name = name[0]; 297 | tag.defaultValue = name[1]; 298 | } 299 | param.name = tag.name || ''; 300 | param.defaultValue = tag.defaultValue || ''; 301 | param.description = tag.description || ''; 302 | param.type = (tag.types && Array.isArray(tag.types)) ? tag.types.join(' ') : ''; 303 | params.push(param); 304 | value.param = params; 305 | } else if (tag.type === 'example') { 306 | value.example = tag.html || ''; 307 | } else if (tag.type === 'return') { 308 | const returnValue = {}; 309 | returnValue.description = tag.description || ''; 310 | returnValue.type = (tag.types && Array.isArray(tag.types)) ? tag.types.join(' ') : ''; 311 | value.returnValue = returnValue; 312 | } else { 313 | const other = tag.type; 314 | value[other] = tag.string || ''; 315 | } 316 | } 317 | } 318 | pureObjArr[key] = Object.assign(value, routes[key]); 319 | } 320 | }); 321 | return pureObjArr; 322 | }; 323 | 324 | exports.buildDoxObjs = function* (routes, files) { 325 | let doxObjs = []; 326 | let count = 0; 327 | for (const file of files) { 328 | console.log(`${count}、 正在处理${file}...`); 329 | const code = yield fs.readFileAsync(file, 'utf-8'); 330 | doxObjs = doxObjs.concat(dox.parseComments(code)); 331 | console.log(config.colors.blue('处理完毕')); 332 | count++; 333 | if (count === files.length) { 334 | return filterObj(routes, doxObjs); 335 | } 336 | } 337 | }; 338 | -------------------------------------------------------------------------------- /common/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mac on 16/8/26. 3 | */ 4 | 5 | module.exports = { 6 | newInit: 7 | [ 8 | { 9 | title:'initConfirm', 10 | description:'初始化createDOC,生成doc.json.确认?(y/n) ', 11 | defaults: 'y' 12 | }, 13 | { 14 | title:'defaultConfirm', 15 | description:'是否使用默认配置.(y/n) ', 16 | defaults: 'y' 17 | }, 18 | { 19 | title:'showConfig', 20 | description:'是否显示doc.json当前配置?(y/n) ', 21 | defaults: 'y' 22 | } 23 | ], 24 | coverInit:[ 25 | { 26 | title:'modifyConfirm', 27 | description:'doc.json已存在,初始化将覆盖文件.确认?(y/n) ', 28 | defaults: 'y' 29 | }, 30 | { 31 | title:'defaultConfirm', 32 | description:'是否使用默认配置.(y/n) ', 33 | defaults: 'y' 34 | }, 35 | { 36 | title:'showConfig', 37 | description:'是否显示doc.json当前配置?(y/n) ', 38 | defaults: 'y' 39 | } 40 | ], 41 | initSuccess: 42 | '初始化 doc.json 成功, 请查看并继续详细配置。', 43 | docjson: 44 | `{ 45 | "db": { 46 | "schemas": "{{schemas }}", 47 | "markdown": { 48 | "path": "{{ dbPath }}", 49 | "file": "{{ dbFile }}" 50 | } 51 | }, 52 | "api": { 53 | "controller": "{{ controller }}", 54 | "routes": "{{ routes }}", 55 | "markdown": { 56 | "path": "{{ apiPath }}", 57 | "file": "{{ apiFile }}" 58 | } 59 | } 60 | }`, 61 | showDescription:` 62 | ======= √只表示路径存在,不代表路径配置正确 ======= 63 | ======= X只表示路径不存在 =======`, 64 | nofile: 65 | '找不到doc.json文件,请检查doc.json文件是否存在于项目根目录。', 66 | startModifyhook: 67 | '开始修改本地.git/hooks文件', 68 | nohook:`找不到prepare-commit-msg文件 69 | 可能原因如下: 70 | 1、未初始化git,.git目录不存在。 71 | 2、prepare-commit-msg文件被修改。 72 | 请检查项目文件!`, 73 | colors, 74 | }; -------------------------------------------------------------------------------- /common/func.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Created by mac on 16/8/10.{ 5 | */ 6 | 7 | //数组快速排序 8 | const sort = function (array, start, end) { 9 | if (!array || start >= end) return; 10 | let i = start, 11 | j = end; 12 | const tmp = array[i]; 13 | while (i < j) { 14 | while (i < j && array[j] >= tmp) j--; 15 | if (i < j) array[i++] = array[j]; 16 | while (i < j && array[i] <= tmp) i++; 17 | if (i < j) array[j--] = array[i]; 18 | } 19 | array[i] = tmp; 20 | sort(array, start, i - 1); 21 | sort(array, i + 1, end); 22 | }; 23 | exports.quickSort = sort; 24 | 25 | //用于markdownBuild,将undefined 转换成false 26 | exports.spaceToFalse = function (str) { 27 | if (str === undefined) return ''; 28 | else return str; 29 | }; 30 | 31 | //用于markdownBuild,根据对象属性进行赋值 32 | exports.propertyAssign = function (api) { 33 | const properties = Object.keys(api); 34 | let description = '', 35 | path = '', 36 | method = '', 37 | example = '', 38 | other = '', 39 | paramTable = '', 40 | returnTable = ''; 41 | for (const property of properties) { 42 | switch (property) { 43 | case 'description': 44 | description = api[property]; 45 | break; 46 | case 'path': 47 | path = api[property]; 48 | break; 49 | case 'method': 50 | method = api[property]; 51 | break; 52 | case 'example': 53 | example = api[property]; 54 | break; 55 | case 'other': 56 | other = api[property]; 57 | break; 58 | case 'param': 59 | if (Array.isArray(api[property])) { 60 | for (const param of api[property]) { 61 | paramTable = paramTable + 62 | '|' + param.name + 63 | '|' + param.defaultValue + 64 | '|' + param.type + 65 | '|' + param.description; 66 | paramTable += '|\n'; 67 | } 68 | } 69 | break; 70 | case 'returnValue': 71 | returnTable = returnTable + 72 | '|' + api[property].type + 73 | '|' + api[property].description + '|\n'; 74 | break; 75 | default: 76 | break; 77 | } 78 | } 79 | return { 80 | description, 81 | path, 82 | method, 83 | example, 84 | other, 85 | paramTable, 86 | returnTable 87 | }; 88 | }; 89 | 90 | //初始化命令,人机交互控制 91 | exports.initRepl = function (init, func) { 92 | let i = 1; 93 | const inputArr = []; 94 | let len = init.length; 95 | process.stdout.write(init[0].description); 96 | process.stdin.resume(); 97 | process.stdin.setEncoding('utf-8'); 98 | process.stdin.on('data', (chunk) => { 99 | chunk = chunk.replace(/[\s\n]/, ''); 100 | if (chunk !== 'y' && chunk !== 'Y' && chunk !== 'n' && chunk !== 'N') { 101 | console.log(config.colors.red('您输入的命令是: ' + chunk)); 102 | console.warn(config.colors.red('请输入正确指令:y/n')); 103 | process.exit(); 104 | } 105 | if ( 106 | (init[i - 1].title === 'modifyConfirm' || init[i - 1].title === 'initConfirm') && 107 | (chunk === 'n' || chunk === 'N') 108 | ) process.exit(); 109 | const inputJson = { 110 | title: init[i - 1].title, 111 | value: chunk, 112 | }; 113 | inputArr.push(inputJson); 114 | if ((len--) > 1) process.stdout.write(init[i++].description); 115 | else { 116 | process.stdin.pause(); 117 | func(inputArr); 118 | } 119 | }); 120 | }; 121 | 122 | //删除掉第一个'{'出现之前的内容,避免有其他信息混入,用于db文档。 123 | exports.clearHeader = function (lines) { 124 | for (const i in lines) { 125 | if (/\/\/.*/.test(lines[i])) { 126 | lines.splice(i, i); 127 | continue; 128 | } 129 | if (/\/\*[\s\S]*?/.test(lines[i])) { 130 | const start = i; 131 | let end = i; 132 | while (!/[\s\S]*?\*\//.test(lines[end])) { 133 | end++; 134 | } 135 | lines.splice(start, end + 1); 136 | continue; 137 | } 138 | if (/\{/.test(lines[i])) { 139 | if (/\/\//.test(lines[i])) continue; 140 | lines.splice(0, i); 141 | break; 142 | } 143 | } 144 | return lines; 145 | }; 146 | 147 | //查看是否含有'attributes: {',如果有则删掉之前的元素,用于db文档 148 | exports.attributesExists = function (lines) { 149 | for (const i in lines) { 150 | /attributes\:\s?\{/.test(lines[i]) && lines.splice(0, i + 1); 151 | } 152 | return lines; 153 | }; 154 | 155 | //检查目录路径合法性(末尾时候含有'/'),并修改。 156 | exports.checkPath = function (path) { 157 | (!(/\/$/.test(path))) && (path += '/'); 158 | return path; 159 | }; 160 | 161 | //去除杂乱字符,用于db文档 162 | exports.removeSymbol = function (code) { 163 | if (typeof (code) === 'string') { 164 | const result = code.replace(/[\s\,\']/g, ''); 165 | return result; 166 | } 167 | }; 168 | 169 | //type转换 170 | exports.typeTransform = function (type) { 171 | switch (type) { 172 | case 'ENUM': 173 | return 'enum'; 174 | case 'BOOLEAN': 175 | return 'boolean'; 176 | case 'INTEGER': 177 | return 'int32'; 178 | case 'BIGINT': 179 | return 'int64'; 180 | case 'FLOAT' || 'REAL': 181 | return 'float'; 182 | case 'DOUBLE': 183 | return 'double'; 184 | case 'DECIMAL': 185 | return 'number'; 186 | case 'DATE': 187 | return 'date-time'; 188 | case 'DATEONLY': 189 | return 'date'; 190 | case 'TIME': 191 | return 'time'; 192 | case 'UUID' || 'UUIDV1' || 'UUIDV4': 193 | return 'uuid'; 194 | case 'CHAR': 195 | return 'char'; 196 | case 'STRING': 197 | return 'string'; 198 | case 'TEXT': 199 | return 'text'; 200 | case 'JSON' || 'JSONB': 201 | return 'json'; 202 | case 'ARRAY': 203 | return 'array'; 204 | default: 205 | return type; 206 | } 207 | }; 208 | -------------------------------------------------------------------------------- /common/global.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | global.Promise = require('bluebird'), 3 | global.mkdirp = require('mkdirp'), 4 | global.dox = require('dox'), 5 | global.colors = require('colors/safe'), 6 | global.path = require('path'), 7 | global.co = require('co'), 8 | 9 | global.markdownBuild = require('./../src/markdownBuild'), 10 | global.func = require('./func'), 11 | global.config = require('./config'), 12 | global.asyncFunc = require('./asyncfunc.js'); -------------------------------------------------------------------------------- /doc.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "schemas":"/Users/mac/Desktop/testssss/schemas", 4 | "markdown": { 5 | "path": "/Users/mac/Desktop/testssss/doc1", 6 | "file": "db.md" 7 | } 8 | }, 9 | "api": { 10 | "controller": "/Users/mac/Desktop/testssss/router.js", 11 | "routes": "/Users/mac/Desktop/testssss/models", 12 | "markdown": { 13 | "path": "/Users/mac/Desktop/testssss/doc", 14 | "file": "api.md" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /example/prepare-commit-msg.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var exec = require('child_process').exec; 3 | 4 | exec('cd ../..', (error, stdout, stderr) => { 5 | if(error){ 6 | console.error(`exec error: ${error}`); 7 | return; 8 | } 9 | exec('createDOC run', (err) => { 10 | if(error){ 11 | console.error(`exec error: ${error}`); 12 | return; 13 | } 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./common/global'); 3 | require('./src/index.js'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "createDOC", 3 | "version": "1.0.19", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node ./index.js", 9 | "dev": "webpack-dev-server --progress --colors" 10 | }, 11 | "bin": { 12 | "createDOC": "./index.js" 13 | }, 14 | "keywords": [ 15 | "doc", 16 | "自动化文档", 17 | "文档" 18 | ], 19 | "author": { 20 | "name": "smart sweet", 21 | "url": "https://github.com/threerocks" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/threerocks/buildDOC" 26 | }, 27 | "license": "ISC", 28 | "dependencies": { 29 | "bluebird": "^3.4.1", 30 | "co": "^4.6.0", 31 | "colors": "^1.1.2", 32 | "commander": "^2.9.0", 33 | "dox": "^0.9.0", 34 | "handlebars": "^4.0.5", 35 | "linebyline": "^1.3.0", 36 | "mkdirp": "^0.5.1" 37 | }, 38 | "devDependencies": { 39 | "eslint-loader": "^1.5.0", 40 | "webpack": "^1.13.2", 41 | "webpack-dev-server": "^1.16.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/MarkdownBuild.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Created by mac on 16/8/10. 5 | */ 6 | const fs = require('fs'); 7 | 8 | exports.dbbuild = function (tables, markdown) { 9 | let text = '# 数据库文档\n\n'; 10 | text += ` 15 | `; 16 | const tableHeader = 17 | '|字段|类型|允许为空|是否主键|是否自增|说明|\n' + 18 | '|:---:|:---:|:---:|:---:|:---:|:---:|\n'; 19 | 20 | //对表名字进行排序 21 | const tablesArray = Object.keys(tables); 22 | func.quickSort(tablesArray, 0, tablesArray.length - 1); 23 | 24 | //分别处理每个表 25 | for (const table of tablesArray) { 26 | text = text + '## ' + table + '\n'; 27 | text += tableHeader; 28 | //分别处理表的每个字段 29 | for (const field in tables[table]) { 30 | text = text + '|' + field; 31 | //分别处理字段的每个属性 32 | const property = tables[table][field]; 33 | text = text + 34 | '|' + func.typeTransform(property.type) + 35 | '|' + func.spaceToFalse(property.allowNull) + 36 | '|' + func.spaceToFalse(property.primaryKey) + 37 | '|' + func.spaceToFalse(property.autoIncrement) + 38 | '|' + func.spaceToFalse(property.description); 39 | text += '|\n'; 40 | } 41 | text += '\n'; 42 | } 43 | 44 | //写入文件 45 | fs.writeFile(markdown.path + markdown.file, text, (err) => { 46 | if (err) throw err; 47 | console.log('It\'s saved!'); //文件被保存 48 | return true; 49 | }); 50 | }; 51 | 52 | 53 | exports.apibuild = function (apis, markdown) { 54 | let text = '# API文档\n\n'; 55 | text += ` 60 | `; 61 | const paramTableHeader = 62 | '|参数名字|默认值|参数类型|说明|\n' + 63 | '|:---:|:---:|:---:|:---:|\n'; 64 | const returnTableHeader = 65 | '|类型|说明|\n' + 66 | '|:---:|:---:|\n'; 67 | 68 | for (const api in apis) { 69 | const properties = func.propertyAssign(apis[api]), 70 | description = properties.description + '\n', 71 | path = '```' + properties.path + '```' + '\n', 72 | method = properties.method + '\n', 73 | example = properties.example + '\n', 74 | other = properties.other + '\n', 75 | paramTable = paramTableHeader + ((properties.paramTable === '') ? '| | | | |' : properties.paramTable) + '\n', 76 | returnTable = returnTableHeader + ((properties.returnTable === '') ? '| | |' : properties.returnTable) + '\n'; 77 | text += `## ${api} 78 | #### 简要描述: 79 | - ${description} 80 | #### 请求URL: 81 | - ${path} 82 | #### 请求方式: 83 | - ${method} 84 | #### 参数: 85 | ${paramTable} 86 | #### 返回参数说明: 87 | ${returnTable} 88 | #### 返回实例: 89 | ${example} 90 | #### 其他说明: 91 | - ${other}`; 92 | } 93 | //写入文件 94 | fs.writeFile(markdown.path + markdown.file, text, (err) => { 95 | if (err) throw err; 96 | console.log('It\'s saved!'); //文件被保存 97 | return true; 98 | }); 99 | }; -------------------------------------------------------------------------------- /src/createApiDOC.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * API description 5 | * 6 | * @param {type} name/name=default_value description 7 | * @return {String} description 8 | * @example 9 | * any example 10 | * 11 | * @other description 12 | */ 13 | 14 | exports.createDOC = function* (controller, path, markdown) { 15 | try { 16 | const routes = yield asyncFunc.getRoutes(controller), 17 | files = yield asyncFunc.getAllFiles(path), 18 | doxObjs = yield asyncFunc.buildDoxObjs(routes, files), 19 | mergerObjs = Object.assign(routes, doxObjs); 20 | markdownBuild.apibuild(mergerObjs, markdown); 21 | } catch (err) { 22 | console.log(err); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/createDbDOC.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const readline = require('linebyline'); 4 | 5 | let fileNumber = 0, //需处理的文件总数 6 | fileCount = 0; //处理文件计数 7 | const data = {}; //所有表格整体数据 8 | 9 | const createObject = function (lines, file, markdown) { 10 | func.clearHeader(lines); //去掉头部注释和其他无用信息,注意此函数以'{'为结尾删除元素。 11 | func.attributesExists(lines);//如果存在attributes属性,删掉该条元素。 12 | 13 | const singleTableData = {}; //单表数据存储 14 | let tableName = '', //当前表格名称 15 | count = 0, //遍历计数 16 | description = ''; //字段描述 17 | 18 | while (count < lines.length) { 19 | //判断是否属于 '//' 类型注释,是否属于 '/* */'类型注释,是否为key,value,并分别做处理 20 | if (/\/\/.*/.test(lines[count])) { 21 | description = lines[count].replace(/\/\//, ''); 22 | count++; 23 | continue; 24 | } else if (/\/\*[\s\S]*?/.test(lines[count])) { 25 | const start = count; 26 | lines[count] = lines[count].replace(/\/\*/, ''); 27 | while (!/[\s\S]*?\*\//.test(lines[count])) { 28 | count++; 29 | } 30 | lines[count] = lines[count].replace(/\*\//, ''); 31 | const end = count; 32 | description = '
' + lines.slice(start, end + 1).join('
') + '
'; 33 | count++; 34 | continue; 35 | } 36 | else if (/\:\s?\{/.test(lines[count])) { 37 | const key = lines[count].split(':')[0]; 38 | const value = {}; 39 | count++; 40 | while (!/\}/.test(lines[count])) { 41 | const child = lines[count].split(':'); 42 | const childKey = child[0]; 43 | let childValue = child[1]; 44 | if (childKey === 'type') { 45 | childValue = childValue.split('.')[1] || childValue; 46 | } 47 | value[childKey] = func.removeSymbol(childValue); 48 | count++; 49 | } 50 | value.description = description; 51 | description = ''; 52 | singleTableData[key] = value; 53 | count++; 54 | continue; 55 | } else if (/\:/.test(lines[count])) { 56 | const child = lines[count].split(':'); 57 | const childKey = child[0]; 58 | let childValue = child[1]; 59 | childValue = childValue.split('.')[1] || childValue; 60 | 61 | const value = {}; 62 | value.type = func.removeSymbol(childValue); 63 | value.description = description; 64 | description = ''; 65 | singleTableData[childKey] = value; 66 | count++; 67 | continue; 68 | } else { 69 | count++; 70 | } 71 | if (/tableName\:/.test(lines[count])) tableName = lines[count].split(':')[1]; 72 | } 73 | if (tableName === '') tableName = path.basename(file, '.js'); 74 | data[tableName] = singleTableData; 75 | fileCount++; 76 | if (fileCount === fileNumber) return markdownBuild.dbbuild(data, markdown); 77 | }; 78 | 79 | const build = function (file, markdown) { 80 | const lines = [], 81 | read = readline(file); 82 | read 83 | .on('line', (line, lineCount, byteCount) => { // eslint-disable-line 84 | lines.push(func.removeSymbol(line)); 85 | }) 86 | .on('error', (e) => { 87 | throw e; 88 | }) 89 | .on('end', () => { 90 | console.log(fileCount + '、 正在处理' + path.basename(file, '.js') + '...'); 91 | createObject(lines, file, markdown); 92 | console.log(config.colors.blue('处理完毕')); 93 | }); 94 | }; 95 | 96 | exports.createDOC = function* (path, markdown) { 97 | try{ 98 | const files = yield asyncFunc.getAllFiles(path); 99 | fileNumber = files.length; 100 | for (const file of files) { 101 | build(file, markdown); 102 | } 103 | }catch (err){ 104 | console.log(err); 105 | } 106 | }; 107 | 108 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const program = require('commander'); 4 | const appInfo = require('./../package.json'); 5 | 6 | program.allowUnknownOption(); 7 | program.version(appInfo.version); 8 | 9 | program 10 | .command('init') 11 | .description('初始化当前目录doc.json文件') 12 | .action(() => co(asyncFunc.initAction)); 13 | 14 | program 15 | .command('show') 16 | .description('显示配置文件状态') 17 | .action(() => co(asyncFunc.showAction)); 18 | 19 | program 20 | .command('run') 21 | .description('启动程序') 22 | .action(() => co(asyncFunc.runAction)); 23 | 24 | program 25 | .command('modifyhook') 26 | .description('修改项目下的hook文件') 27 | .action(() => co(asyncFunc.modifyhookAction)); 28 | 29 | program 30 | .command('*') 31 | .action((env) => { 32 | console.error('不存在命令 "%s"', env); 33 | }); 34 | 35 | program.on('--help', () => { 36 | console.log(' Examples:'); 37 | console.log(''); 38 | console.log(' $ createDOC --help'); 39 | console.log(' $ createDOC -h'); 40 | console.log(' $ createDOC show'); 41 | console.log(''); 42 | }); 43 | 44 | program.parse(process.argv); 45 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mac on 16/8/22. 3 | */ 4 | require('./test/test'); 5 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mac on 16/8/22. 3 | */ 4 | const co = require('co'); 5 | const asyncFunc = require('./../common/asyncfunc'); 6 | const handlebars = require('handlebars'); 7 | const config = require('./../common/config'); 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | 11 | //co(asyncFunc.initAction) 12 | //co(asyncFunc.runAction); 13 | //co(asyncFunc.showAction); 14 | --------------------------------------------------------------------------------