1/* 2 * Author: Heidi Pan <heidi.pan@intel.com> 3 * Copyright (c) 2015 Intel Corporation. 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining 6 * a copy of this software and associated documentation files (the 7 * "Software"), to deal in the Software without restriction, including 8 * without limitation the rights to use, copy, modify, merge, publish, 9 * distribute, sublicense, and/or sell copies of the Software, and to 10 * permit persons to whom the Software is furnished to do so, subject to 11 * the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be 14 * included in all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 */ 24 25// dependencies 26var peg = require('pegjs') 27 , fs = require('fs') 28 , path = require('path') 29 , Promise = require('bluebird') 30 , _ = require('lodash') 31 , util = require('util'); 32 33 34// use promise-style programming rather than spaghetti callbacks 35Promise.promisifyAll(fs); 36 37 38var xml2js = { 39 40 // js-format specs 41 // MODULE: <module name> 42 // ENUMS: { 43 // <enum name>: { 44 // type: <enum type>, 45 // description: <enum description> 46 // }, ... 47 // } 48 // ENUMS_BY_GROUP: { 49 // <enum type>: { 50 // description: <enum group description> 51 // members: [ <enum name>, ... ] 52 // }, ... 53 // } 54 // METHODS: { 55 // <method name>: { 56 // description: <method description>, 57 // params: { 58 // <param name>: { 59 // type: <param type>, 60 // description: <param description > 61 // }, ... 62 // }, 63 // return: { 64 // type: <return type>, 65 // description: <return description> 66 // } 67 // }, ... 68 // } 69 // CLASSES: { 70 // <class name>: { 71 // description: <class description>, 72 // parent: <parent class name>, 73 // group: <group name>, 74 // methods: { ... }, 75 // variables: { 76 // <variable name>: { 77 // type: <variable type>, 78 // description: <variable description> 79 // } 80 // }, 81 // enums: { ... }, 82 // enums_by_group: { ... } 83 // }, ... 84 // } 85 // CLASSGROUPS: { 86 // <group name>: { 87 // description: <group description>, 88 // classes: [ <class name>, ... ], 89 // enums: { ... }, 90 // enums_by_group: { ... } 91 // }, ... 92 // } 93 MODULE: '', 94 ENUMS: {}, 95 ENUMS_BY_GROUP: {}, 96 METHODS: {}, 97 CLASSES: {}, 98 CLASSGROUPS: {}, 99 100 101 // baseline c -> js type mapping 102 TYPEMAPS: { 103 '^(const)?\\s*(unsigned|signed)?\\s*(int|short|long|float|double|size_t|u?int\\d{1,2}_t)?$': 'Number', 104 '^bool$': 'Boolean', 105 '^(const)?\\s*(unsigned|signed)?\\s*(char|char\\s*\\*|std::string)$': 'String', // TODO: verify that swig does this mapping 106 '^void\\s*\\(\\s*\\*\\s*\\)\\s*\\(\\s*void\\s*\\*\\)\\s*$': 'Function' 107 }, 108 109 110 // custom c -> js type mapping for pointers 111 // ARRAY_TYPEMAPS: { 112 // <pointer data type>: { 113 // arrayType: <swig generated array type that will replace pointers of data type>, 114 // classes: [ <class that contains arrayType>, ... ] 115 // }, ... 116 // } 117 // POINTER_TYPEMAPS: { 118 // <class that contains pointerType>: { 119 // <c pointer data type>: <js swig generated pointer type that will replace pointers of data type>, ... 120 // }, ... 121 // } 122 ARRAY_TYPEMAPS: {}, 123 POINTER_TYPEMAPS: {}, 124 125 126 // add command line options for this module 127 addOptions: function(opts) { 128 xml2js.opts = opts; 129 return opts 130 .option('-i, --inputdir [directory]', 'directory for xml files', __dirname + '/xml/mraa') 131 .option('-c, --custom [file]', 'json for customizations') 132 .option('-t, --typemaps [directory]', 'directory for custom pointer type maps') 133 .option('-g, --imagedir [directory]', 'directory to link to where the images will be kept', '') 134 .option('-s, --strict', 'leave out methods/variables if unknown type') 135 }, 136 137 138 // parse doxygen xml -> js-format specs 139 // TODO: figure out whether we need to document any protected methods/variables 140 parse: function() { 141 var XML_GRAMMAR_SPEC = 'grammars/xml.peg'; 142 var NAMESPACE_SPEC = xml2js.opts.inputdir + '/namespace' + xml2js.opts.module + '.xml'; 143 var CLASS_SPEC = function(c) { return xml2js.opts.inputdir + '/' + c + '.xml'; } 144 var TYPES_SPEC = xml2js.opts.inputdir + '/types_8h.xml'; 145 xml2js.MODULE = xml2js.opts.module; 146 return Promise.join(createXmlParser(XML_GRAMMAR_SPEC), 147 xml2js.opts.typemaps ? initCustomPointerTypemaps(xml2js.opts.typemaps) : Promise.resolve(), 148 fs.readFileAsync(NAMESPACE_SPEC, 'utf8'), 149 fs.existsSync(TYPES_SPEC) ? fs.readFileAsync(TYPES_SPEC, 'utf8') : Promise.resolve(null), 150 function(xmlparser, ignore, xml, xml_types) { 151 if (xml_types != null) { 152 _.extend(xml2js.ENUMS, getEnums(xmlparser.parse(xml_types)[0], false)); 153 _.extend(xml2js.ENUMS_BY_GROUP, getEnums(xmlparser.parse(xml_types)[0], true)); 154 } 155 var spec_c = xmlparser.parse(xml)[0]; 156 _.extend(xml2js.ENUMS, getEnums(spec_c, false)); 157 _.extend(xml2js.ENUMS_BY_GROUP, getEnums(spec_c, true)); 158 _.extend(xml2js.METHODS, getMethods(spec_c)); 159 _.each(getSubclassNames(spec_c), function(className) { xml2js.CLASSES[className] = {} }); 160 var parseClasses = _.map(getSubclasses(spec_c), function(c) { 161 return fs.readFileAsync(CLASS_SPEC(c), 'utf8').then(function(xml) { 162 try { 163 var spec_c = xmlparser.parse(xml)[0]; 164 var className = getName(spec_c); 165 _.extend(xml2js.CLASSES[className], { 166 description: getDescription(spec_c), 167 parent: getParent(spec_c, className), 168 enums: getEnums(spec_c, false, className), 169 enums_by_group: getEnums(spec_c, true, className), 170 variables: getVariables(spec_c, className), 171 methods: getMethods(spec_c, className) 172 }); 173 } catch(e) { 174 console.log(e.toString() + ': class ' + className + ' was not parsed correctly.'); 175 } 176 }); 177 }); 178 var parseGroups = fs.readdirAsync(xml2js.opts.inputdir).then(function(files) { 179 var groupxmlfiles = _.filter(files, function(fn) { 180 return ((path.extname(fn) == '.xml') && (path.basename(fn).search(/^group/) != -1)); 181 }); 182 return Promise.all(_.map(groupxmlfiles, function(fn) { 183 return fs.readFileAsync(xml2js.opts.inputdir + '/' + fn, 'utf8').then(function(xml) { 184 var spec_c = xmlparser.parse(xml)[0]; 185 if (_.isEmpty(getSubmodules(spec_c))) { 186 var group = getName(spec_c); 187 var classes = getSubclassNames(spec_c); 188 xml2js.CLASSGROUPS[group] = { 189 description: getDescription(spec_c), 190 classes: classes 191 }; 192 _.each(classes, function(c) { 193 if (_.has(xml2js.CLASSES, c)) { 194 xml2js.CLASSES[c].group = group; 195 } else { 196 console.log('Warning: Group ' + group + ' has unknown class ' + c); 197 } 198 }); 199 } 200 }); 201 })); 202 }); 203 return Promise.all(parseClasses.concat(parseGroups)); 204 }).then(function() { 205 if (!_.isEmpty(xml2js.CLASSGROUPS)) { 206 // try to categorize ungrouped classes, if any 207 var grouped = _.flatten(_.pluck(_.values(xml2js.CLASSGROUPS), 'classes')); 208 var ungrouped = _.difference(_.keys(xml2js.CLASSES), grouped); 209 _.each(ungrouped, function(c) { 210 _.each(findUsage(c), function(group) { 211 xml2js.CLASSGROUPS[group].classes.push(c); 212 }); 213 }); 214 grouped = _.flatten(_.pluck(_.values(xml2js.CLASSGROUPS), 'classes')); 215 ungrouped = _.difference(_.keys(xml2js.CLASSES), grouped); 216 // try to categorize ungrouped enums, if any 217 _.each(xml2js.ENUMS_BY_GROUP, function(enumGroupSpec, enumGroupName) { 218 _.each(findUsage(enumGroupName, true), function(c) { 219 xml2js.CLASSES[c].enums_by_group[enumGroupName] = enumGroupSpec; 220 _.each(enumGroupSpec.members, function(enumName) { 221 xml2js.CLASSES[c].enums[enumName] = xml2js.ENUMS[enumName]; 222 delete xml2js.ENUMS[enumName]; 223 }); 224 delete xml2js.ENUMS_BY_GROUP[enumGroupName]; 225 }); 226 }); 227 } 228 }).then(function() { 229 if (xml2js.opts.custom && fs.existsSync(xml2js.opts.custom)) { 230 return fs.readFileAsync(xml2js.opts.custom, 'utf8').then(function(custom) { 231 try { 232 customizeMethods(JSON.parse(custom)); 233 } catch(e) { 234 console.log('invalid custom.json, ignored. ' + e.toString()); 235 } 236 }); 237 } else { 238 console.log(xml2js.opts.custom ? ('Error: No such customization file exists: ' + xml2js.opts.custom) : 'No customizations given.'); 239 } 240 }).then(function() { 241 generateCustomPointerClasses(); 242 validateMethods(); 243 validateVars(); 244 return _.pick(xml2js, 'MODULE', 'ENUMS', 'ENUMS_BY_GROUP', 'METHODS', 'CLASSES', 'CLASSGROUPS'); 245 }); 246 } 247}; 248 249 250// create an xml parser 251function createXmlParser(XML_GRAMMAR_SPEC) { 252 return fs.readFileAsync(XML_GRAMMAR_SPEC, 'utf8').then(function(xmlgrammar) { 253 return peg.buildParser(xmlgrammar); 254 }); 255} 256 257 258// configure c->js typemaps from custom swig directives 259// TODO: many built in assumptions based on current upm file structures & .i customizations 260function initCustomPointerTypemaps(typemapsdir) { 261 return fs.readdirAsync(typemapsdir).then(function(dirs) { 262 return Promise.all(_.map(dirs, function(dir) { 263 // get all js*.i directives from class-specific subdirectories, to be parsed below for %typemaps directives 264 return fs.readdirAsync(typemapsdir + '/' + dir).then(function(files) { 265 var directive = _.find(files, function(fn) { 266 return ((path.extname(fn) == '.i') && (path.basename(fn).search(/^js/) != -1)); 267 }); 268 var data = {}; 269 if (directive) { 270 data[dir] = typemapsdir + '/' + dir + '/' + directive; 271 } 272 return data; 273 }).catch(function(e) { 274 // get all .i directives from top level directory, and parse for %array_class directives 275 if (e.code == 'ENOTDIR') { 276 var fn = dir; 277 if (path.extname(fn) == '.i') { 278 return fs.readFileAsync(typemapsdir + '/' + fn, 'utf8').then(function(directives) { 279 var arraytypes = _.filter(directives.split(/\n/), function(line) { 280 return (line.search(/^%array_class/) != -1); 281 }); 282 _.each(arraytypes, function(arraytype) { 283 var parsed = arraytype.match(/%array_class\(([A-Za-z0-9_]+)[\s]*,[\s]*([A-Za-z0-9_]+)\)/); 284 if (parsed) { 285 var from = parsed[1]; 286 var to = parsed[2]; 287 xml2js.ARRAY_TYPEMAPS[from] = { arrayType: to, classes: [] }; 288 } else { 289 console.log('Incorrectly parsed array_class from ' + fn + ': ' + arraytype); 290 } 291 }); 292 }); 293 } 294 } else { 295 throw e; 296 } 297 }); 298 })); 299 }).then(function(__directivesFiles) { 300 // parse for %typemaps & %pointer_functions directives 301 var _directivesFiles = _.filter(__directivesFiles, function(data) { return !_.isEmpty(data); }); 302 var directivesFiles = _.object(_.map(_directivesFiles, _.keys), _.flatten(_.map(_directivesFiles, _.values))); 303 return Promise.all(_.map(directivesFiles, function(directivesFn, className) { 304 return fs.readFileAsync(directivesFn, 'utf8').then(function(directives) { 305 var typemaps = _.filter(directives.split(/\n/), function(line) { 306 return (line.search(/^%typemap/) != -1); 307 }); 308 _.each(typemaps, function(typemap) { 309 var parsed = typemap.match(/%typemap\((in|out)\)[\s]+([A-Za-z0-9_]+[\s]*[\*])/); 310 if (parsed) { 311 var dir = parsed[1]; // TODO: ignored for now 312 var type = normalizePointer(parsed[2]); 313 var datatype = getPointerDataType(type); 314 if (_.has(xml2js.ARRAY_TYPEMAPS, datatype)) { 315 xml2js.ARRAY_TYPEMAPS[datatype].classes.push(className); 316 } else { 317 console.log('Ignored typemap from ' + directivesFn + ': ' + typemap.replace('{', '') + ' (no %array_class directive found for ' + datatype + ')'); 318 } 319 } else { 320 console.log('Ignored typemap from ' + directivesFn + ': ' + typemap.replace('{', '') + ' (only considering in/out typemaps of pointer types)'); 321 } 322 }); 323 var ptrfns = _.filter(directives.split(/\n/), function(line) { 324 return (line.search(/^%pointer_functions/) != -1); 325 }); 326 _.each(ptrfns, function(ptrfn) { 327 var parsed = ptrfn.match(/%pointer_functions\(([A-Za-z0-9_]+)[\s]*,[\s]*([A-Za-z0-9_]+)\)/); 328 if (parsed) { 329 var from = parsed[1]; 330 var to = parsed[2]; 331 if (!_.has(xml2js.POINTER_TYPEMAPS, className)) { 332 xml2js.POINTER_TYPEMAPS[className] = {}; 333 } 334 xml2js.POINTER_TYPEMAPS[className][from] = to; 335 } 336 }); 337 }); 338 })); 339 }); 340} 341 342 343// generate class specs for custom pointer types 344function generateCustomPointerClasses() { 345 var arrayTypes = _.pluck(_.values(xml2js.ARRAY_TYPEMAPS), 'arrayType'); 346 var pointerTypes = _.uniq(_.flatten(_.map(_.values(xml2js.POINTER_TYPEMAPS), _.values))); 347 _.each(arrayTypes, function(arrayType) { 348 var dataType = _.findKey(xml2js.ARRAY_TYPEMAPS, function(to) { return to.arrayType == arrayType; }); 349 xml2js.CLASSES[arrayType] = { 350 description: 'Array of type ' + dataType + '.', 351 enums: {}, 352 enums_by_group: {}, 353 variables: {}, 354 methods: {} 355 }; 356 xml2js.CLASSES[arrayType].methods[arrayType] = { 357 description: 'Instantiates the array.', 358 params: { 359 nelements: { 360 type: 'Number', 361 description: 'number of elements in the array' 362 } 363 }, 364 return: {} 365 }; 366 xml2js.CLASSES[arrayType].methods.getitem = { 367 description: 'Access a particular element in the array.', 368 params: { 369 index: { 370 type: 'Number', 371 description: 'index of array to read from' 372 }, 373 }, 374 return: { 375 type: getType(dataType), 376 description: 'the value of the element found at the given index of the array' 377 } 378 }; 379 xml2js.CLASSES[arrayType].methods.setitem = { 380 description: 'Modify a particular element in the array.', 381 params: { 382 index: { 383 type: 'Number', 384 description: 'index of array to write to' 385 }, 386 value: { 387 type: getType(dataType), 388 description: 'the value to set the element found at the given index of the array' 389 } 390 }, 391 return: {} 392 }; 393 }); 394 var pointerDataTypeMap = _.reduce(_.map(_.values(xml2js.POINTER_TYPEMAPS), _.invert), function(memo, typemap) { 395 return _.extend(memo, typemap); 396 }, {}); 397 _.each(pointerTypes, function(pointerType) { 398 var dataType = pointerDataTypeMap[pointerType]; 399 xml2js.CLASSES[pointerType] = { 400 description: 'Proxy object to data of type ' + dataType + '.', 401 enums: {}, 402 enums_by_group: {}, 403 variables: {}, 404 methods: {} 405 }; 406 xml2js.CLASSES[pointerType].methods[pointerType] = { 407 description: 'Instantiates the proxy object.', 408 params: {}, 409 return: {} 410 }; 411 xml2js.CLASSES[pointerType].methods.value = { 412 description: 'Get the value of the object.', 413 params: {}, 414 return: { 415 type: getType(dataType), 416 description: 'the value of the object' 417 } 418 }; 419 xml2js.CLASSES[pointerType].methods.assign = { 420 description: 'Set the value of the object.', 421 params: { 422 value: { 423 type: getType(dataType), 424 description: 'the value to set the object to' 425 } 426 }, 427 return: {} 428 }; 429 }); 430} 431 432 433// search for usage of a type 434function findUsage(type, classOnly) { 435 var filterClasses = function(fn) { return _.without(_.map(xml2js.CLASSES, fn), undefined); }; 436 var usesType = function(classSpec, className) { 437 var methodsOfType = (_.find(classSpec.methods, function(methodSpec, methodName) { 438 return ((!_.isEmpty(methodSpec.return) && methodSpec.return.type == type) || 439 (_.contains(_.pluck(methodSpec.params, 'type'), type))); 440 }) != undefined); 441 var variablesOfType = _.contains(_.pluck(classSpec.variable, 'type'), type); 442 return ((methodsOfType || variablesOfType) ? className : undefined); 443 }; 444 var extendsType = function(classSpec, className) { 445 return ((classSpec.parent == type) ? className : undefined); 446 }; 447 var classes = _.union(filterClasses(usesType), filterClasses(extendsType)); 448 if (classOnly) { 449 return classes; 450 } else { 451 return _.without(_.uniq(_.pluck(_.pick(xml2js.CLASSES, classes), 'group')), undefined); 452 } 453} 454 455 456// override autogenerated methods with custom configuration 457function customizeMethods(custom) { 458 _.each(custom, function(classMethods, className) { 459 _.extend(xml2js.CLASSES[className].methods, _.pick(classMethods, function(methodSpec, methodName) { 460 return isValidMethodSpec(methodSpec, className + '.' + methodName); 461 })); 462 }); 463} 464 465 466// make sure methods have valid types, otherwise warn (& don't include if strict) 467function validateMethods() { 468 xml2js.METHODS = _.pick(xml2js.METHODS, function(methodSpec, methodName) { 469 return hasValidTypes(methodSpec, methodName); 470 }); 471 _.each(xml2js.CLASSES, function(classSpec, className) { 472 var valid = _.pick(classSpec.methods, function(methodSpec, methodName) { 473 return hasValidTypes(methodSpec, className + '.' + methodName, className); 474 }); 475 if (xml2js.opts.strict) { 476 xml2js.CLASSES[className].methods = valid; 477 } 478 }); 479} 480 481 482// make sure variables have valid types, otherwise warn (& don't include if strict) 483function validateVars() { 484 _.each(xml2js.CLASSES, function(classSpec, className) { 485 var valid = _.pick(classSpec.variables, function(varSpec, varName) { 486 return ofValidType(varSpec, className + '.' + varName, className); 487 }); 488 if (xml2js.opts.strict) { 489 xml2js.CLASSES[className].variables = valid; 490 } 491 }); 492} 493 494 495// verify that the json spec is well formatted 496function isValidMethodSpec(methodSpec, methodName) { 497 var valid = true; 498 var printIgnoredMethodOnce = _.once(function() { console.log(methodName + ' from ' + path.basename(xml2js.opts.custom) + ' is omitted from JS documentation.'); }); 499 function checkRule(rule, errMsg) { 500 if (!rule) { 501 printIgnoredMethodOnce(); 502 console.log(' ' + errMsg); 503 valid = false; 504 } 505 } 506 checkRule(_.has(methodSpec, 'description'), 'no description given'); 507 checkRule(_.has(methodSpec, 'params'), 'no params given (specify "params": {} for no params)'); 508 _.each(methodSpec.params, function(paramSpec, paramName) { 509 checkRule(_.has(paramSpec, 'type'), 'no type given for param ' + paramName); 510 checkRule(_.has(paramSpec, 'description'), 'no description given for param ' + paramName); 511 }); 512 checkRule(_.has(methodSpec, 'return'), 'no return given (specify "return": {} for no return value)'); 513 checkRule(_.has(methodSpec.return, 'type'), 'no type given for return value'); 514 checkRule(_.has(methodSpec.return, 'description'), 'no description given for return value'); 515 return valid; 516} 517 518 519// get enum specifications 520function getEnums(spec_c, bygroup, parent) { 521 var spec_js = {}; 522 var enumGroups = _.find(getChildren(spec_c, 'sectiondef'), function(section) { 523 var kind = getAttr(section, 'kind'); 524 return ((kind == 'enum') || (kind == 'public-type')); 525 }); 526 if (enumGroups) { 527 _.each(enumGroups.children, function(enumGroup) { 528 var enumGroupName = getText(getChild(enumGroup, 'name'), 'name'); 529 var enumGroupDescription = getText(getChild(enumGroup, 'detaileddescription'), 'description'); 530 var enumGroupVals = getChildren(enumGroup, 'enumvalue'); 531 if (bygroup) { 532 spec_js[enumGroupName] = { 533 description: enumGroupDescription, 534 members: [] 535 }; 536 } 537 _.each(enumGroupVals, function(e) { 538 // TODO: get prefix as option 539 var enumName = getText(getChild(e, 'name'), 'name').replace(/^MRAA_/, ''); 540 var enumDescription = getText(getChild(e, 'detaileddescription'), 'description'); 541 if (!bygroup) { 542 spec_js[enumName] = { 543 type: enumGroupName, 544 description: enumDescription 545 }; 546 } else { 547 spec_js[enumGroupName].members.push(enumName); 548 } 549 }); 550 }); 551 } 552 return spec_js; 553} 554 555 556// get the name for the module/group/class 557function getName(spec_c) { 558 return getText(getChild(spec_c, 'compoundname'), 'name').replace(xml2js.opts.module + '::', ''); 559} 560 561 562// get the description for the module/group/class 563function getDescription(spec_c) { 564 return getText(getChild(spec_c, 'detaileddescription'), 'description'); 565} 566 567 568// get the classes (xml file names) for the given module 569function getSubclasses(spec_c) { 570 return _.map(getChildren(spec_c, 'innerclass'), function(innerclass) { 571 return getAttr(innerclass, 'refid'); 572 }); 573} 574 575 576// get the classes (class names) for the given module 577function getSubclassNames(spec_c) { 578 return _.map(getChildren(spec_c, 'innerclass'), function(innerclass) { 579 return getText(innerclass).replace(xml2js.opts.module + '::', ''); 580 }); 581} 582 583 584// get the submodules (xml file names) for the given module 585function getSubmodules(spec_c) { 586 return _.map(getChildren(spec_c, 'innergroup'), function(innergroup) { 587 return getAttr(innergroup, 'refid'); 588 }); 589} 590 591 592// get parent class, if any 593function getParent(spec_c, className) { 594 var parent = getChild(spec_c, 'basecompoundref'); 595 if (parent) { 596 parent = getText(parent); 597 if (!_.has(xml2js.CLASSES, parent)) { 598 console.log('WARNING: Class ' + className + ' has unknown parent class ' + parent); 599 } 600 } 601 return parent; 602} 603 604 605function hasParams(paramsSpec) { 606 return !(_.isEmpty(paramsSpec) || 607 ((_.size(paramsSpec) == 1) && getText(getChild(paramsSpec[0], 'type')) == 'void')); 608} 609 610 611// get method specifications for top-level module or a given class 612// TODO: overloaded functions 613// TODO: functions w/ invalid parameter(s)/return 614function getMethods(spec_c, parent) { 615 var spec_js = {}; 616 var methods = _.find(getChildren(spec_c, 'sectiondef'), function(section) { 617 var kind = getAttr(section, 'kind'); 618 return ((kind == 'public-func') || (kind == 'func')); 619 }); 620 if (methods) { 621 _.each(methods.children, function(method) { 622 var methodName = getText(getChild(method, 'name'), 'name'); 623 if (methodName[0] != '~') { // filter out destructors 624 try { 625 var description = getChild(method, 'detaileddescription'); 626 var methodDescription = getText(description, 'description'); 627 var paramsSpec = getChildren(method, 'param'); 628 var params = {}; 629 if (hasParams(paramsSpec)) { 630 params = getParams(paramsSpec, getParamsDetails(description), methodName, parent); 631 } 632 var returnSpec = getChild(method, 'type'); 633 var retval = {}; 634 if (!_.isEmpty(returnSpec)) { 635 retval = getReturn(returnSpec, getReturnDetails(description), methodName, parent); 636 } 637 methodName = getUniqueMethodName(methodName, spec_js, parent); 638 spec_js[methodName] = { 639 description: methodDescription, 640 params: params, 641 return: retval 642 }; 643 } catch(e) { 644 console.log((parent ? (parent + '.') : '') + methodName + ' is omitted from JS documentation.'); 645 console.log(' ' + e.toString()); 646 } 647 } 648 }); 649 } 650 return spec_js; 651} 652 653 654// get a unique string to represent the name of an overloaded method 655function getUniqueMethodName(methodName, module, parent) { 656 if (methodName in module) { 657 do { 658 methodName += '!'; 659 } while (methodName in module); 660 } 661 return methodName; 662} 663 664 665// get variable specifications for a class 666function getVariables(spec_c, parent) { 667 var spec_js = {}; 668 var vars = _.find(getChildren(spec_c, 'sectiondef'), function(section) { 669 var kind = getAttr(section, 'kind'); 670 return (kind == 'public-attrib'); 671 }); 672 if (vars) { 673 _.each(_.filter(vars.children, function(variable) { 674 return (getAttr(variable, 'kind') == 'variable'); 675 }), function(variable) { 676 var varName = getText(getChild(variable, 'name'), 'name'); 677 var varType = getType(getText(getChild(variable, 'type')), parent); 678 var varDescription = getText(getChild(variable, 'detaileddescription')); 679 spec_js[varName] = { 680 type: varType, 681 description: varDescription 682 } 683 }); 684 } 685 return spec_js; 686} 687 688 689// get return value specs of a method 690function getReturn(spec_c, details, method, parent) { 691 var retType = getType(getText(spec_c, 'type'), parent); 692 var retDescription = (details ? getText(details, 'description') : ''); 693 return ((retType == 'void') ? {} : { 694 type: retType, 695 description: retDescription 696 }); 697} 698 699 700// get paramater specs of a method 701function getParams(spec_c, details, method, parent) { 702 var spec_js = {}; 703 _.each(spec_c, function(param) { 704 try { 705 var paramType = getType(getText(getChild(param, 'type'), 'type'), parent); 706 var paramName = getText(getChild(param, 'declname'), 'name'); 707 spec_js[paramName] = { type: paramType }; 708 } catch(e) { 709 if (paramType == '...') { 710 spec_js['arguments'] = { type: paramType }; 711 } else { 712 throw e; 713 } 714 } 715 }); 716 _.each(details, function(param) { 717 var getParamName = function(p) { return getText(getChild(getChild(p, 'parameternamelist'), 'parametername'), 'name'); } 718 var paramName = getParamName(param); 719 var paramDescription = getText(getChild(param, 'parameterdescription'), 'description'); 720 if (_.has(spec_js, paramName)) { 721 spec_js[paramName].description = paramDescription; 722 } else { 723 var msg = ' has documentation for an unknown parameter: ' + paramName + '. '; 724 var suggestions = _.difference(_.keys(spec_js), _.map(details, getParamName)); 725 var msgAddendum = (!_.isEmpty(suggestions) ? ('Did you mean ' + suggestions.join(', or ') + '?') : ''); 726 console.log('Warning: ' + (parent ? (parent + '.') : '') + method + msg + msgAddendum); 727 } 728 }); 729 return spec_js; 730} 731 732 733// get the equivalent javascript type from the given c type 734function getType(type_c, parent) { 735 var type_js = type_c; 736 _.find(xml2js.TYPEMAPS, function(to, from) { 737 var pattern = new RegExp(from, 'i'); 738 if (type_c.search(pattern) == 0) { 739 type_js = to; 740 return true; 741 } 742 }); 743 if (isPointer(type_js)) { 744 var dataType = getPointerDataType(type_js); 745 var className = parent.toLowerCase(); 746 if (_.has(xml2js.ARRAY_TYPEMAPS, dataType) && _.contains(xml2js.ARRAY_TYPEMAPS[dataType].classes, className)) { 747 type_js = xml2js.ARRAY_TYPEMAPS[dataType].arrayType; 748 } else if (_.has(xml2js.POINTER_TYPEMAPS, className) && _.has(xml2js.POINTER_TYPEMAPS[className], dataType)) { 749 type_js = xml2js.POINTER_TYPEMAPS[className][dataType]; 750 } else if (_.has(xml2js.CLASSES, dataType)) { // TODO: verify that swig does this mapping 751 type_js = dataType; 752 } else { 753 type_js = dataType + ' *' 754 } 755 } 756 return type_js; 757} 758 759 760// verify that all types associated with the method are valid 761function hasValidTypes(methodSpec, methodName, parent) { 762 var valid = true; 763 var msg = (xml2js.opts.strict ? ' is omitted from JS documentation.' : ' has invalid type(s).'); 764 var printIgnoredMethodOnce = _.once(function() { console.log(methodName + msg); }); 765 _.each(methodSpec.params, function(paramSpec, paramName) { 766 if (!isValidType(paramSpec.type, parent)) { 767 valid = false; 768 printIgnoredMethodOnce(); 769 console.log(' Error: parameter ' + paramName + ' has invalid type ' + typeToString(paramSpec.type)); 770 } 771 }); 772 if (!_.isEmpty(methodSpec.return) && !isValidType(methodSpec.return.type, parent)) { 773 valid = false; 774 printIgnoredMethodOnce(); 775 console.log(' Error: returns invalid type ' + typeToString(methodSpec.return.type)); 776 } 777 return valid; 778} 779 780 781// verify that type of variable is valid 782function ofValidType(varSpec, varName, parent) { 783 if (isValidType(varSpec.type, parent)) { 784 return true; 785 } else { 786 var msgAddendum = (xml2js.opts.strict ? ' Omitted from JS documentation.' : ''); 787 console.log('Error: ' + varName + ' is of invalid type ' + typeToString(varSpec.type) + '.' + msgAddendum); 788 return false; 789 } 790} 791 792 793// verify whether the given type is valid JS 794function isValidType(type, parent) { 795 return (_.contains(_.values(xml2js.TYPEMAPS), type) || 796 _.has(xml2js.CLASSES, type) || 797 _.has(xml2js.ENUMS_BY_GROUP, type) || 798 _.contains(['Buffer', 'Function', 'mraa_result_t'], type) || 799 _.has((parent ? xml2js.CLASSES[parent].enums_by_group : []), type) || 800 isValidPointerType(type, parent)); 801} 802 803 804function isValidPointerType(type, parent) { 805 var className = parent.toLowerCase(); 806 var arrayTypemap = _.find(xml2js.ARRAY_TYPEMAPS, function(to) { return to.arrayType == type; }); 807 var valid = ((arrayTypemap && _.contains(arrayTypemap.classes, className)) || 808 (_.has(xml2js.POINTER_TYPEMAPS, className) && (_.contains(_.values(xml2js.POINTER_TYPEMAPS[className]), type)))); 809 return valid; 810} 811 812 813// determines whether a type looks like a c pointer 814function isPointer(type) { 815 return (type.search(/\w+\s*(\*|&)$/) != -1); 816} 817 818 819// remove extraneous whitespace from pointer types as canonical representation 820function normalizePointer(ptr) { 821 return ptr.replace(/\s*$/, ''); 822} 823 824 825// get the data type of a pointer (e.g. int is the data type of int*) 826function getPointerDataType(ptr) { 827 return ptr.replace(/\s*(\*|&)$/, ''); 828} 829 830 831// print more human friendly type for error messages 832function typeToString(type) { 833 return type.replace('*', '*'); 834} 835 836 837// get the detailed description of a method's parameters 838function getParamsDetails(spec_c) { 839 var paras = getChildren(spec_c, 'para'); 840 var details = _.find(_.map(paras, function(para) { 841 return getChild(para, 'parameterlist'); 842 }), function(obj) { return (obj != undefined); }); 843 return (details ? details.children : undefined); 844} 845 846 847// get the detailed description of a method's return value 848function getReturnDetails(spec_c) { 849 var paras = getChildren(spec_c, 'para'); 850 return _.find(_.map(paras, function(para) { 851 return getChild(para, 'simplesect'); 852 }), function(obj) { return ((obj != undefined) && (getAttr(obj, 'kind') == 'return')); }); 853} 854 855 856// get (and flatten) the text of the given object 857function getText(obj, why) { 858 // TODO: links ignored for now, patched for types for 859 var GENERATE_LINK = function(x) { return x + ' '; } 860 return _.reduce(obj.children, function(text, elem) { 861 if (_.isString(elem)) { 862 return text += elem.trim() + ' '; 863 } else if (_.isPlainObject(elem)) { 864 switch(elem.name) { 865 case 'para': 866 return text += getText(elem, why) + ' \n'; 867 case 'ref': 868 return text += GENERATE_LINK(getText(elem, why)); 869 case 'parameterlist': 870 case 'simplesect': 871 return text; // to be handled elsewhere 872 case 'programlisting': 873 case 'htmlonly': 874 return text; // ignored 875 // TODO: html doesn't seem to work for yuidoc, using markdown for now 876 case 'itemizedlist': 877 return text += '\n' + getText(elem, why) + ' \n \n'; 878 case 'listitem': 879 return text += '+ ' + getText(elem, why) + '\n'; 880 case 'bold': 881 return text += '__' + getText(elem, why).trim() + '__ '; 882 case 'ulink': 883 return text += '[' + getText(elem, why).trim() + '](' + getAttr(elem, 'url').trim() + ') '; 884 case 'image': 885 // TODO: copy images over; hard coded for now 886 var fn = getAttr(elem, 'name'); 887 return text += ' \n \n![' + fn + '](' + xml2js.opts.imagedir + '/' + fn + ') '; 888 case 'linebreak': 889 return text += ' \n'; 890 case 'ndash': 891 return text += '– '; 892 default: 893 // TODO: incomplete list of doxygen xsd implemented 894 console.warn('NYI Unknown Object Type: ' + elem.name); 895 return text; 896 //throw new Error('NYI Unknown Object Type: ' + elem.name); 897 } 898 } else { 899 throw new Error('NYI Unknown Type: ' + (typeof elem)); 900 } 901 }, '').trim(); 902} 903 904 905// get the value of attribute with the given name of the given object 906function getAttr(obj, name) { 907 return _.find(obj.attr, function(item) { 908 return item.name == name; 909 }).value; 910} 911 912 913// get the child object with the given name of the given object 914function getChild(obj, name) { 915 return _.find(obj.children, function(child) { 916 return child.name == name; 917 }); 918} 919 920 921// get all children objects with the given name of the given object 922function getChildren(obj, name) { 923 return _.filter(obj.children, function(child) { 924 return child.name == name; 925 }); 926} 927 928 929// debug helper: print untruncated object 930function printObj(obj) { 931 console.log(util.inspect(obj, false, null)); 932} 933 934 935module.exports = xml2js;