1 | const _ = require('lodash'), |
||
2 | fs = require('fs'), |
||
3 | Handlebars = require('handlebars'), |
||
4 | HTML = 'HTML', |
||
5 | path = require('path'), |
||
6 | BaseGenerator = require('./BaseGenerator'), |
||
7 | SwaggerParser = require('./SwaggerParser'), |
||
8 | Markdown = 'Markdown' |
||
9 | |||
10 | /** |
||
11 | * @class DocGenerator |
||
12 | * Documentation generator (html or MarkDown) |
||
13 | * |
||
14 | * @property {String} type Output documentation type |
||
15 | * @property {String} version Swagger version |
||
16 | * @property {String} destination Output destination |
||
17 | * @property {String} templatePath Path to template |
||
18 | * @property {ParserOptions} parserOptions Swagger parser options |
||
19 | * @property {Function} generatorCallback Custom generator callback |
||
20 | * @property {Object.<Object>} additionalLayouts Additional layouts for Handlebars in format <code><file-name-or-relative-path>: <template-name></code> |
||
21 | * @property {Object.<Object>} additionalHelpers Additional helpers for Handlebars in format <code><helper-name>: function (Handlebars) { |
||
22 | * // Some helper detail |
||
23 | * }</code> |
||
24 | * @property {String} moduleName Module name |
||
25 | * @property {String} className Class name |
||
26 | * @property {String} destination Output doc destination |
||
27 | * @property {String} docsPath Method definitions path |
||
28 | * @property {String} modelPath Model path |
||
29 | */ |
||
30 | class DocGenerator extends BaseGenerator { |
||
31 | |||
32 | /** |
||
33 | * DocGenerator constructor |
||
34 | * |
||
35 | * @param {String} type Documentation type (html or Markdown). One of (Markdown or HTML) |
||
36 | * @param {DocGeneratorOptionsObject} options Swagger documentation generator options |
||
37 | * @param {String} version Swagger version (default is 2.0) |
||
38 | */ |
||
39 | constructor (type, options, version) { |
||
40 | super() |
||
41 | |||
42 | version = version || '2.0' |
||
43 | options = options || {} |
||
44 | this.type = type |
||
45 | this.version = version |
||
46 | this.generatorCallback = options.generatorCallback |
||
47 | this.templatePath = options.templatePath || (path.join(__dirname, this.version, 'templates', this.type.toLowerCase())) |
||
48 | this.data = options.swagger |
||
49 | this.additionalLayouts = options.additionalLayouts || {} |
||
50 | this.parserOptions = options.parserOptions |
||
51 | this.destination = options.destination || path.join(__dirname, '/../dist') |
||
52 | this._checkFile(false, 'destination', 'Destination folder not found') |
||
53 | this.additionalHelpers = options.additionalHelpers || {} |
||
54 | this.filename = options.inputJson |
||
55 | this.moduleName = options.moduleName |
||
56 | this.className = options.className |
||
57 | this.modelPath = options.modelPath |
||
58 | this.docsPath = options.docsPath |
||
59 | |||
60 | this._checkFile() |
||
61 | this._content = fs.readFileSync(this.filename).toString() |
||
62 | |||
63 | this.data = this._parseData() |
||
64 | |||
65 | this._checkFile(false, 'templatePath', 'Template path does not exists') |
||
66 | |||
67 | let parser = new SwaggerParser(this.version, this.data, this.parserOptions || { |
||
68 | className : this.className, |
||
69 | moduleName : this.moduleName, |
||
70 | packageName : options.packageName, |
||
71 | packageVersion: options.packageVersion, |
||
72 | modelPath : this.modelPath, |
||
73 | docsPath : this.docsPath |
||
74 | }) |
||
75 | this.swaggerData = parser.parse() |
||
76 | this.swaggerData.moduleName = this.moduleName |
||
77 | this.swaggerData.className = this.className |
||
78 | |||
79 | // fs.writeFileSync(path.join(process.cwd(), 'api-data.json'), JSON.stringify(this.swaggerData)) |
||
80 | |||
81 | if (!_.size(this.data)) { |
||
82 | throw new Error('Swagger data is empty') |
||
83 | } |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * @const {String} HTML |
||
88 | * |
||
89 | * @return {string} |
||
90 | */ |
||
91 | static get HTML () { |
||
92 | return HTML |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * @const {String} Markdown |
||
97 | * |
||
98 | * @return {string} |
||
99 | */ |
||
100 | static get Markdown () { |
||
101 | return Markdown |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * Generate documentation |
||
106 | */ |
||
107 | generate () { |
||
108 | if (typeof this.generatorCallback === 'function') { |
||
109 | return this.generatorCallback.apply(this, [this.data]) |
||
110 | } |
||
111 | |||
112 | this._prepareHandlebars() |
||
113 | |||
114 | this._generateMain() |
||
115 | |||
116 | this._generateMethods() |
||
117 | this._generateModels() |
||
0 ignored issues
–
show
Best Practice
introduced
by
![]() |
|||
118 | } |
||
119 | |||
120 | _generateMain () { |
||
121 | let template = fs.readFileSync(path.join(this.templatePath, 'md.hbs')).toString(), |
||
122 | content = Handlebars.compile(template)(this.swaggerData) |
||
123 | |||
124 | this._writeContent(content, this._getMainFileName()) |
||
125 | } |
||
126 | |||
127 | _getMainFileName () { |
||
128 | return this.type === DocGenerator.HTML ? 'index.html' : 'README.MD' |
||
129 | } |
||
130 | |||
131 | _getExt () { |
||
132 | return this.type === DocGenerator.HTML ? 'html' : 'md' |
||
133 | } |
||
134 | |||
135 | _writeContent (content, fileName) { |
||
136 | fs.writeFileSync(path.join(this.destination, fileName), content) |
||
137 | } |
||
138 | |||
139 | _generateModels () { |
||
140 | let _self = this, |
||
141 | template = fs.readFileSync(path.join(this.templatePath, 'model.hbs')).toString(), |
||
142 | content = '' |
||
143 | |||
144 | _.each(this.swaggerData.definitions, (config, name) => { |
||
145 | config.modelName = name |
||
146 | content = Handlebars.compile(template)(config) |
||
147 | _self._writeContent(content, path.join(this.modelPath, name + '.' + _self._getExt())) |
||
148 | }) |
||
149 | } |
||
150 | |||
151 | _generateMethods () { |
||
152 | let template = fs.readFileSync(path.join(this.templatePath, 'method.hbs')).toString(), |
||
153 | responseTemplate = fs.readFileSync(path.join(this.templatePath, 'response.hbs')).toString(), |
||
154 | content = '', |
||
155 | _self = this |
||
156 | Handlebars.registerPartial('response', responseTemplate) |
||
157 | _.each(this.swaggerData.methods, (config, index) => { |
||
0 ignored issues
–
show
|
|||
158 | content = Handlebars.compile(template)(config) |
||
159 | _self._writeContent(content, path.join(_self.docsPath, config.methodName + '.' + _self._getExt())) |
||
160 | }) |
||
161 | } |
||
162 | |||
163 | _prepareHandlebars () { |
||
164 | this._registerPartial('methodList') |
||
165 | this._registerPartial('securityDefinitions') |
||
166 | |||
167 | let _self = this |
||
168 | |||
169 | _.each(this.additionalLayouts, (file, name) => { |
||
170 | _self._registerPartial(file, name) |
||
171 | }) |
||
172 | |||
173 | this._registerHelpers() |
||
174 | } |
||
175 | |||
176 | _registerPartial (filename, templateName) { |
||
177 | templateName = templateName || filename |
||
178 | if (_.isNumber(templateName)) { |
||
179 | templateName = filename.split('/') |
||
180 | |||
181 | let clearTemplate = '' |
||
182 | |||
183 | for (let i = 0; i < templateName.length; i++) { |
||
184 | if ((clearTemplate = _.trim(templateName[i])).length) { |
||
185 | templateName = clearTemplate |
||
186 | } |
||
187 | } |
||
188 | } |
||
189 | let fileContent = fs.readFileSync(path.join(this.templatePath, filename + '.hbs')).toString() |
||
190 | |||
191 | Handlebars.registerPartial(templateName, fileContent) |
||
192 | } |
||
193 | |||
194 | _registerHelpers () { |
||
195 | |||
196 | Handlebars.registerHelper('ifCond', function (v1, v2, options) { |
||
197 | if (v1 === v2) { |
||
198 | return options.fn(this) |
||
199 | } |
||
200 | return options.inverse(this) |
||
201 | }) |
||
202 | |||
203 | Handlebars.registerHelper('ifHas', function (object, paramName, value, options) { |
||
204 | for (let i in object) { |
||
205 | if (!object.hasOwnProperty(i)) { |
||
206 | continue |
||
207 | } |
||
208 | |||
209 | let has = object[i][paramName] === value |
||
210 | |||
211 | if (has) { |
||
212 | return options.fn(this) |
||
213 | } |
||
214 | |||
215 | return options.inverse(this) |
||
216 | } |
||
0 ignored issues
–
show
|
|||
217 | }) |
||
218 | |||
219 | Handlebars.registerHelper('breaklines', function(text) { |
||
220 | text = Handlebars.Utils.escapeExpression(text); |
||
221 | text = text.replace(/(\r\n|\n|\r)/gm, '<br>'); |
||
222 | return new Handlebars.SafeString(text); |
||
223 | }); |
||
224 | |||
225 | Handlebars.registerHelper('requireFilter', function (require) { |
||
226 | return require ? 'required' : 'optional' |
||
227 | }) |
||
228 | |||
229 | Handlebars.registerHelper('ifDefine', function (object, paramName, options) { |
||
230 | if (typeof object[paramName] !== 'undefined') { |
||
231 | return options.fn(this) |
||
232 | } |
||
233 | |||
234 | return options.inverse(this) |
||
235 | }) |
||
236 | |||
237 | Handlebars.registerHelper('ifLength', function (object, options) { |
||
238 | if (typeof object === 'undefined') { |
||
239 | return options.inverse(this) |
||
240 | } |
||
241 | return object.length ? options.fn(this) : options.inverse(this) |
||
242 | }) |
||
243 | |||
244 | _.each(this.additionalHelpers, (cb, name) => { |
||
245 | if (typeof cb === 'function') { |
||
246 | cb(name, Handlebars) |
||
247 | } |
||
248 | }) |
||
249 | } |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * @ignore module |
||
254 | * @ignore module.exports |
||
255 | */ |
||
256 | module.exports = DocGenerator |