2015-05-20 10:38:19 -07:00
/ *
* Author : Heidi Pan < heidi . pan @ intel . com >
* Copyright ( c ) 2015 Intel Corporation .
*
* Permission is hereby granted , free of charge , to any person obtaining
* a copy of this software and associated documentation files ( the
* "Software" ) , to deal in the Software without restriction , including
* without limitation the rights to use , copy , modify , merge , publish ,
* distribute , sublicense , and / or sell copies of the Software , and to
* permit persons to whom the Software is furnished to do so , subject to
* the following conditions :
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND ,
* EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT . IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN ACTION
* OF CONTRACT , TORT OR OTHERWISE , ARISING FROM , OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE .
* /
// dependencies
var peg = require ( 'pegjs' )
, fs = require ( 'fs' )
, path = require ( 'path' )
, Promise = require ( 'bluebird' )
, _ = require ( 'lodash' )
, util = require ( 'util' ) ;
// use promise-style programming rather than spaghetti callbacks
Promise . promisifyAll ( fs ) ;
var xml2js = {
2015-05-26 17:22:13 -07:00
2015-05-20 10:38:19 -07:00
// js-format specs
2015-05-21 10:09:48 -07:00
// MODULE: <module name>
2015-05-20 10:38:19 -07:00
// ENUMS: {
// <enum name>: {
// type: <enum type>,
// description: <enum description>
// }, ...
// }
// ENUMS_BY_GROUP: {
// <enum type>: {
2015-05-26 17:22:13 -07:00
// description: <enum group description>
2015-05-20 10:38:19 -07:00
// members: [ <enum name>, ... ]
// }, ...
// }
// METHODS: {
// <method name>: {
// description: <method description>,
// params: {
// <param name>: {
// type: <param type>,
// description: <param description >
2015-05-26 17:22:13 -07:00
// }, ...
2015-05-20 10:38:19 -07:00
// },
// return: {
// type: <return type>,
// description: <return description>
// }
// }, ...
// }
// CLASSES: {
// <class name>: {
// description: <class description>,
2015-06-04 10:31:38 -07:00
// parent: <parent class name>,
// group: <group name>,
2015-05-20 10:38:19 -07:00
// methods: { ... },
// variables: {
// <variable name>: {
// type: <variable type>,
// description: <variable description>
// }
// },
// enums: { ... },
// enums_by_group: { ... }
// }, ...
2015-05-26 17:22:13 -07:00
// }
2015-05-21 10:09:48 -07:00
// CLASSGROUPS: {
// <group name>: {
// description: <group description>,
2015-06-04 10:31:38 -07:00
// classes: [ <class name>, ... ],
// enums: { ... },
// enums_by_group: { ... }
2015-05-21 10:09:48 -07:00
// }, ...
2015-05-20 10:38:19 -07:00
// }
MODULE : '' ,
ENUMS : { } ,
ENUMS _BY _GROUP : { } ,
METHODS : { } ,
CLASSES : { } ,
2015-05-21 10:09:48 -07:00
CLASSGROUPS : { } ,
2015-05-26 17:22:13 -07:00
2015-05-21 10:09:48 -07:00
// baseline c -> js type mapping
2015-05-20 10:38:19 -07:00
TYPEMAPS : {
'^(const)?\\s*(unsigned|signed)?\\s*(int|short|long|float|double|size_t|u?int\\d{1,2}_t)?$' : 'Number' ,
'^bool$' : 'Boolean' ,
2015-06-04 10:31:38 -07:00
'^(const)?\\s*(unsigned|signed)?\\s*(char|char\\s*\\*|std::string)$' : 'String' , // TODO: verify that swig does this mapping
'^void\\s*\\(\\s*\\*\\s*\\)\\s*\\(\\s*void\\s*\\*\\)\\s*$' : 'Function'
2015-05-20 10:38:19 -07:00
} ,
2015-05-26 17:22:13 -07:00
2015-05-21 10:09:48 -07:00
// custom c -> js type mapping for pointers
2015-05-26 17:22:13 -07:00
// ARRAY_TYPEMAPS: {
2015-05-21 10:09:48 -07:00
// <pointer data type>: {
// arrayType: <swig generated array type that will replace pointers of data type>,
// classes: [ <class that contains arrayType>, ... ]
// }, ...
2015-05-26 17:22:13 -07:00
// }
2015-05-21 10:09:48 -07:00
// POINTER_TYPEMAPS: {
// <class that contains pointerType>: {
// <c pointer data type>: <js swig generated pointer type that will replace pointers of data type>, ...
// }, ...
// }
ARRAY _TYPEMAPS : { } ,
POINTER _TYPEMAPS : { } ,
2015-05-26 17:22:13 -07:00
2015-05-20 10:38:19 -07:00
// add command line options for this module
addOptions : function ( opts ) {
xml2js . opts = opts ;
return opts
. option ( '-i, --inputdir [directory]' , 'directory for xml files' , _ _dirname + '/xml/mraa' )
2015-05-21 10:09:48 -07:00
. option ( '-c, --custom [file]' , 'json for customizations' )
. option ( '-t, --typemaps [directory]' , 'directory for custom pointer type maps' )
. option ( '-g, --imagedir [directory]' , 'directory to link to where the images will be kept' , '' )
2015-05-20 10:38:19 -07:00
. option ( '-s, --strict' , 'leave out methods/variables if unknown type' )
} ,
2015-05-26 17:22:13 -07:00
2015-05-20 10:38:19 -07:00
// parse doxygen xml -> js-format specs
// TODO: figure out whether we need to document any protected methods/variables
parse : function ( ) {
var XML _GRAMMAR _SPEC = 'grammars/xml.peg' ;
var NAMESPACE _SPEC = xml2js . opts . inputdir + '/namespace' + xml2js . opts . module + '.xml' ;
var CLASS _SPEC = function ( c ) { return xml2js . opts . inputdir + '/' + c + '.xml' ; }
var TYPES _SPEC = xml2js . opts . inputdir + '/types_8h.xml' ;
xml2js . MODULE = xml2js . opts . module ;
2015-05-26 17:22:13 -07:00
return Promise . join ( createXmlParser ( XML _GRAMMAR _SPEC ) ,
2015-05-21 10:09:48 -07:00
xml2js . opts . typemaps ? initCustomPointerTypemaps ( xml2js . opts . typemaps ) : Promise . resolve ( ) ,
2015-05-26 17:22:13 -07:00
fs . readFileAsync ( NAMESPACE _SPEC , 'utf8' ) ,
2015-05-20 10:38:19 -07:00
fs . existsSync ( TYPES _SPEC ) ? fs . readFileAsync ( TYPES _SPEC , 'utf8' ) : Promise . resolve ( null ) ,
2015-05-21 10:09:48 -07:00
function ( xmlparser , ignore , xml , xml _types ) {
2015-05-20 10:38:19 -07:00
if ( xml _types != null ) {
_ . extend ( xml2js . ENUMS , getEnums ( xmlparser . parse ( xml _types ) [ 0 ] , false ) ) ;
_ . extend ( xml2js . ENUMS _BY _GROUP , getEnums ( xmlparser . parse ( xml _types ) [ 0 ] , true ) ) ;
}
var spec _c = xmlparser . parse ( xml ) [ 0 ] ;
_ . extend ( xml2js . ENUMS , getEnums ( spec _c , false ) ) ;
_ . extend ( xml2js . ENUMS _BY _GROUP , getEnums ( spec _c , true ) ) ;
_ . extend ( xml2js . METHODS , getMethods ( spec _c ) ) ;
2015-05-21 10:09:48 -07:00
_ . each ( getSubclassNames ( spec _c ) , function ( className ) { xml2js . CLASSES [ className ] = { } } ) ;
var parseClasses = _ . map ( getSubclasses ( spec _c ) , function ( c ) {
2015-05-20 10:38:19 -07:00
return fs . readFileAsync ( CLASS _SPEC ( c ) , 'utf8' ) . then ( function ( xml ) {
try {
var spec _c = xmlparser . parse ( xml ) [ 0 ] ;
2015-05-21 10:09:48 -07:00
var className = getName ( spec _c ) ;
2015-06-04 10:31:38 -07:00
_ . extend ( xml2js . CLASSES [ className ] , {
2015-05-21 10:09:48 -07:00
description : getDescription ( spec _c ) ,
2015-06-04 10:31:38 -07:00
parent : getParent ( spec _c , className ) ,
2015-05-20 10:38:19 -07:00
enums : getEnums ( spec _c , false , className ) ,
enums _by _group : getEnums ( spec _c , true , className ) ,
variables : getVariables ( spec _c , className ) ,
2015-05-21 10:09:48 -07:00
methods : getMethods ( spec _c , className )
2015-06-04 10:31:38 -07:00
} ) ;
2015-05-20 10:38:19 -07:00
} catch ( e ) {
2015-05-21 10:09:48 -07:00
console . log ( e . toString ( ) + ': class ' + className + ' was not parsed correctly.' ) ;
2015-05-20 10:38:19 -07:00
}
} ) ;
2015-05-21 10:09:48 -07:00
} ) ;
var parseGroups = fs . readdirAsync ( xml2js . opts . inputdir ) . then ( function ( files ) {
var groupxmlfiles = _ . filter ( files , function ( fn ) {
return ( ( path . extname ( fn ) == '.xml' ) && ( path . basename ( fn ) . search ( /^group/ ) != - 1 ) ) ;
} ) ;
return Promise . all ( _ . map ( groupxmlfiles , function ( fn ) {
return fs . readFileAsync ( xml2js . opts . inputdir + '/' + fn , 'utf8' ) . then ( function ( xml ) {
var spec _c = xmlparser . parse ( xml ) [ 0 ] ;
if ( _ . isEmpty ( getSubmodules ( spec _c ) ) ) {
2015-06-04 10:31:38 -07:00
var group = getName ( spec _c ) ;
var classes = getSubclassNames ( spec _c ) ;
xml2js . CLASSGROUPS [ group ] = {
2015-05-21 10:09:48 -07:00
description : getDescription ( spec _c ) ,
2015-06-04 10:31:38 -07:00
classes : classes
} ;
_ . each ( classes , function ( c ) {
if ( _ . has ( xml2js . CLASSES , c ) ) {
xml2js . CLASSES [ c ] . group = group ;
} else {
console . log ( 'Warning: Group ' + group + ' has unknown class ' + c ) ;
}
} ) ;
2015-05-20 10:38:19 -07:00
}
} ) ;
2015-05-21 10:09:48 -07:00
} ) ) ;
2015-05-20 10:38:19 -07:00
} ) ;
2015-05-21 10:09:48 -07:00
return Promise . all ( parseClasses . concat ( parseGroups ) ) ;
2015-06-04 10:31:38 -07:00
} ) . then ( function ( ) {
if ( ! _ . isEmpty ( xml2js . CLASSGROUPS ) ) {
// try to categorize ungrouped classes, if any
var grouped = _ . flatten ( _ . pluck ( _ . values ( xml2js . CLASSGROUPS ) , 'classes' ) ) ;
var ungrouped = _ . difference ( _ . keys ( xml2js . CLASSES ) , grouped ) ;
_ . each ( ungrouped , function ( c ) {
_ . each ( findUsage ( c ) , function ( group ) {
xml2js . CLASSGROUPS [ group ] . classes . push ( c ) ;
} ) ;
} ) ;
grouped = _ . flatten ( _ . pluck ( _ . values ( xml2js . CLASSGROUPS ) , 'classes' ) ) ;
ungrouped = _ . difference ( _ . keys ( xml2js . CLASSES ) , grouped ) ;
// try to categorize ungrouped enums, if any
_ . each ( xml2js . ENUMS _BY _GROUP , function ( enumGroupSpec , enumGroupName ) {
_ . each ( findUsage ( enumGroupName , true ) , function ( c ) {
xml2js . CLASSES [ c ] . enums _by _group [ enumGroupName ] = enumGroupSpec ;
_ . each ( enumGroupSpec . members , function ( enumName ) {
xml2js . CLASSES [ c ] . enums [ enumName ] = xml2js . ENUMS [ enumName ] ;
delete xml2js . ENUMS [ enumName ] ;
} ) ;
delete xml2js . ENUMS _BY _GROUP [ enumGroupName ] ;
} ) ;
} ) ;
}
2015-05-21 10:09:48 -07:00
} ) . then ( function ( ) {
if ( xml2js . opts . custom && fs . existsSync ( xml2js . opts . custom ) ) {
return fs . readFileAsync ( xml2js . opts . custom , 'utf8' ) . then ( function ( custom ) {
try {
customizeMethods ( JSON . parse ( custom ) ) ;
} catch ( e ) {
console . log ( 'invalid custom.json, ignored. ' + e . toString ( ) ) ;
}
} ) ;
} else {
console . log ( xml2js . opts . custom ? ( 'Error: No such customization file exists: ' + xml2js . opts . custom ) : 'No customizations given.' ) ;
}
} ) . then ( function ( ) {
2015-06-04 10:31:38 -07:00
generateCustomPointerClasses ( ) ;
2015-05-21 10:09:48 -07:00
validateMethods ( ) ;
validateVars ( ) ;
2015-05-26 17:22:13 -07:00
return _ . pick ( xml2js , 'MODULE' , 'ENUMS' , 'ENUMS_BY_GROUP' , 'METHODS' , 'CLASSES' , 'CLASSGROUPS' ) ;
2015-05-20 10:38:19 -07:00
} ) ;
}
} ;
// create an xml parser
function createXmlParser ( XML _GRAMMAR _SPEC ) {
return fs . readFileAsync ( XML _GRAMMAR _SPEC , 'utf8' ) . then ( function ( xmlgrammar ) {
return peg . buildParser ( xmlgrammar ) ;
} ) ;
}
2015-05-26 17:22:13 -07:00
// configure c->js typemaps from custom swig directives
2015-05-21 10:09:48 -07:00
// TODO: many built in assumptions based on current upm file structures & .i customizations
function initCustomPointerTypemaps ( typemapsdir ) {
return fs . readdirAsync ( typemapsdir ) . then ( function ( dirs ) {
return Promise . all ( _ . map ( dirs , function ( dir ) {
// get all js*.i directives from class-specific subdirectories, to be parsed below for %typemaps directives
return fs . readdirAsync ( typemapsdir + '/' + dir ) . then ( function ( files ) {
var directive = _ . find ( files , function ( fn ) {
return ( ( path . extname ( fn ) == '.i' ) && ( path . basename ( fn ) . search ( /^js/ ) != - 1 ) ) ;
} ) ;
var data = { } ;
if ( directive ) {
data [ dir ] = typemapsdir + '/' + dir + '/' + directive ;
}
return data ;
} ) . catch ( function ( e ) {
// get all .i directives from top level directory, and parse for %array_class directives
if ( e . code == 'ENOTDIR' ) {
var fn = dir ;
if ( path . extname ( fn ) == '.i' ) {
return fs . readFileAsync ( typemapsdir + '/' + fn , 'utf8' ) . then ( function ( directives ) {
var arraytypes = _ . filter ( directives . split ( /\n/ ) , function ( line ) {
return ( line . search ( /^%array_class/ ) != - 1 ) ;
} ) ;
_ . each ( arraytypes , function ( arraytype ) {
var parsed = arraytype . match ( /%array_class\(([A-Za-z0-9_]+)[\s]*,[\s]*([A-Za-z0-9_]+)\)/ ) ;
if ( parsed ) {
var from = parsed [ 1 ] ;
var to = parsed [ 2 ] ;
xml2js . ARRAY _TYPEMAPS [ from ] = { arrayType : to , classes : [ ] } ;
} else {
console . log ( 'Incorrectly parsed array_class from ' + fn + ': ' + arraytype ) ;
}
} ) ;
} ) ;
}
} else {
throw e ;
}
} ) ;
} ) ) ;
} ) . then ( function ( _ _directivesFiles ) {
// parse for %typemaps & %pointer_functions directives
var _directivesFiles = _ . filter ( _ _directivesFiles , function ( data ) { return ! _ . isEmpty ( data ) ; } ) ;
var directivesFiles = _ . object ( _ . map ( _directivesFiles , _ . keys ) , _ . flatten ( _ . map ( _directivesFiles , _ . values ) ) ) ;
return Promise . all ( _ . map ( directivesFiles , function ( directivesFn , className ) {
return fs . readFileAsync ( directivesFn , 'utf8' ) . then ( function ( directives ) {
var typemaps = _ . filter ( directives . split ( /\n/ ) , function ( line ) {
return ( line . search ( /^%typemap/ ) != - 1 ) ;
} ) ;
_ . each ( typemaps , function ( typemap ) {
var parsed = typemap . match ( /%typemap\((in|out)\)[\s]+([A-Za-z0-9_]+[\s]*[\*])/ ) ;
if ( parsed ) {
var dir = parsed [ 1 ] ; // TODO: ignored for now
var type = normalizePointer ( parsed [ 2 ] ) ;
var datatype = getPointerDataType ( type ) ;
if ( _ . has ( xml2js . ARRAY _TYPEMAPS , datatype ) ) {
xml2js . ARRAY _TYPEMAPS [ datatype ] . classes . push ( className ) ;
} else {
console . log ( 'Ignored typemap from ' + directivesFn + ': ' + typemap . replace ( '{' , '' ) + ' (no %array_class directive found for ' + datatype + ')' ) ;
}
} else {
console . log ( 'Ignored typemap from ' + directivesFn + ': ' + typemap . replace ( '{' , '' ) + ' (only considering in/out typemaps of pointer types)' ) ;
}
} ) ;
var ptrfns = _ . filter ( directives . split ( /\n/ ) , function ( line ) {
return ( line . search ( /^%pointer_functions/ ) != - 1 ) ;
} ) ;
_ . each ( ptrfns , function ( ptrfn ) {
var parsed = ptrfn . match ( /%pointer_functions\(([A-Za-z0-9_]+)[\s]*,[\s]*([A-Za-z0-9_]+)\)/ ) ;
if ( parsed ) {
var from = parsed [ 1 ] ;
var to = parsed [ 2 ] ;
if ( ! _ . has ( xml2js . POINTER _TYPEMAPS , className ) ) {
xml2js . POINTER _TYPEMAPS [ className ] = { } ;
}
xml2js . POINTER _TYPEMAPS [ className ] [ from ] = to ;
}
} ) ;
} ) ;
} ) ) ;
} ) ;
}
// generate class specs for custom pointer types
function generateCustomPointerClasses ( ) {
var arrayTypes = _ . pluck ( _ . values ( xml2js . ARRAY _TYPEMAPS ) , 'arrayType' ) ;
var pointerTypes = _ . uniq ( _ . flatten ( _ . map ( _ . values ( xml2js . POINTER _TYPEMAPS ) , _ . values ) ) ) ;
_ . each ( arrayTypes , function ( arrayType ) {
var dataType = _ . findKey ( xml2js . ARRAY _TYPEMAPS , function ( to ) { return to . arrayType == arrayType ; } ) ;
xml2js . CLASSES [ arrayType ] = {
description : 'Array of type ' + dataType + '.' ,
enums : { } ,
enums _by _group : { } ,
variables : { } ,
methods : { }
} ;
xml2js . CLASSES [ arrayType ] . methods [ arrayType ] = {
description : 'Instantiates the array.' ,
params : {
nelements : {
type : 'Number' ,
description : 'number of elements in the array'
}
} ,
return : { }
} ;
xml2js . CLASSES [ arrayType ] . methods . getitem = {
description : 'Access a particular element in the array.' ,
params : {
index : {
type : 'Number' ,
description : 'index of array to read from'
} ,
} ,
return : {
type : getType ( dataType ) ,
description : 'the value of the element found at the given index of the array'
}
} ;
xml2js . CLASSES [ arrayType ] . methods . setitem = {
description : 'Modify a particular element in the array.' ,
params : {
index : {
type : 'Number' ,
description : 'index of array to write to'
} ,
value : {
type : getType ( dataType ) ,
description : 'the value to set the element found at the given index of the array'
}
} ,
return : { }
} ;
} ) ;
var pointerDataTypeMap = _ . reduce ( _ . map ( _ . values ( xml2js . POINTER _TYPEMAPS ) , _ . invert ) , function ( memo , typemap ) {
return _ . extend ( memo , typemap ) ;
} , { } ) ;
_ . each ( pointerTypes , function ( pointerType ) {
var dataType = pointerDataTypeMap [ pointerType ] ;
xml2js . CLASSES [ pointerType ] = {
description : 'Proxy object to data of type ' + dataType + '.' ,
enums : { } ,
enums _by _group : { } ,
variables : { } ,
methods : { }
} ;
xml2js . CLASSES [ pointerType ] . methods [ pointerType ] = {
description : 'Instantiates the proxy object.' ,
params : { } ,
return : { }
} ;
xml2js . CLASSES [ pointerType ] . methods . value = {
description : 'Get the value of the object.' ,
params : { } ,
return : {
type : getType ( dataType ) ,
description : 'the value of the object'
}
} ;
xml2js . CLASSES [ pointerType ] . methods . assign = {
description : 'Set the value of the object.' ,
params : {
value : {
type : getType ( dataType ) ,
description : 'the value to set the object to'
}
} ,
return : { }
} ;
} ) ;
}
2015-06-04 10:31:38 -07:00
// search for usage of a type
function findUsage ( type , classOnly ) {
var filterClasses = function ( fn ) { return _ . without ( _ . map ( xml2js . CLASSES , fn ) , undefined ) ; } ;
var usesType = function ( classSpec , className ) {
var methodsOfType = ( _ . find ( classSpec . methods , function ( methodSpec , methodName ) {
return ( ( ! _ . isEmpty ( methodSpec . return ) && methodSpec . return . type == type ) ||
( _ . contains ( _ . pluck ( methodSpec . params , 'type' ) , type ) ) ) ;
} ) != undefined ) ;
var variablesOfType = _ . contains ( _ . pluck ( classSpec . variable , 'type' ) , type ) ;
return ( ( methodsOfType || variablesOfType ) ? className : undefined ) ;
} ;
var extendsType = function ( classSpec , className ) {
return ( ( classSpec . parent == type ) ? className : undefined ) ;
} ;
var classes = _ . union ( filterClasses ( usesType ) , filterClasses ( extendsType ) ) ;
if ( classOnly ) {
return classes ;
} else {
return _ . without ( _ . uniq ( _ . pluck ( _ . pick ( xml2js . CLASSES , classes ) , 'group' ) ) , undefined ) ;
}
}
// override autogenerated methods with custom configuration
2015-05-20 10:38:19 -07:00
function customizeMethods ( custom ) {
_ . each ( custom , function ( classMethods , className ) {
_ . extend ( xml2js . CLASSES [ className ] . methods , _ . pick ( classMethods , function ( methodSpec , methodName ) {
return isValidMethodSpec ( methodSpec , className + '.' + methodName ) ;
} ) ) ;
} ) ;
}
2015-05-21 10:09:48 -07:00
2015-05-26 17:22:13 -07:00
// make sure methods have valid types, otherwise warn (& don't include if strict)
2015-05-20 10:38:19 -07:00
function validateMethods ( ) {
xml2js . METHODS = _ . pick ( xml2js . METHODS , function ( methodSpec , methodName ) {
2015-05-21 10:09:48 -07:00
return hasValidTypes ( methodSpec , methodName ) ;
2015-05-20 10:38:19 -07:00
} ) ;
_ . each ( xml2js . CLASSES , function ( classSpec , className ) {
var valid = _ . pick ( classSpec . methods , function ( methodSpec , methodName ) {
return hasValidTypes ( methodSpec , className + '.' + methodName , className ) ;
} ) ;
if ( xml2js . opts . strict ) {
xml2js . CLASSES [ className ] . methods = valid ;
}
} ) ;
}
// make sure variables have valid types, otherwise warn (& don't include if strict)
function validateVars ( ) {
_ . each ( xml2js . CLASSES , function ( classSpec , className ) {
var valid = _ . pick ( classSpec . variables , function ( varSpec , varName ) {
return ofValidType ( varSpec , className + '.' + varName , className ) ;
} ) ;
if ( xml2js . opts . strict ) {
xml2js . CLASSES [ className ] . variables = valid ;
}
} ) ;
}
2015-05-26 17:22:13 -07:00
2015-05-20 10:38:19 -07:00
// verify that the json spec is well formatted
function isValidMethodSpec ( methodSpec , methodName ) {
var valid = true ;
var printIgnoredMethodOnce = _ . once ( function ( ) { console . log ( methodName + ' from ' + path . basename ( xml2js . opts . custom ) + ' is omitted from JS documentation.' ) ; } ) ;
2015-05-26 17:22:13 -07:00
function checkRule ( rule , errMsg ) {
2015-05-20 10:38:19 -07:00
if ( ! rule ) {
printIgnoredMethodOnce ( ) ;
console . log ( ' ' + errMsg ) ;
valid = false ;
2015-05-26 17:22:13 -07:00
}
2015-05-20 10:38:19 -07:00
}
checkRule ( _ . has ( methodSpec , 'description' ) , 'no description given' ) ;
checkRule ( _ . has ( methodSpec , 'params' ) , 'no params given (specify "params": {} for no params)' ) ;
_ . each ( methodSpec . params , function ( paramSpec , paramName ) {
checkRule ( _ . has ( paramSpec , 'type' ) , 'no type given for param ' + paramName ) ;
checkRule ( _ . has ( paramSpec , 'description' ) , 'no description given for param ' + paramName ) ;
} ) ;
checkRule ( _ . has ( methodSpec , 'return' ) , 'no return given (specify "return": {} for no return value)' ) ;
checkRule ( _ . has ( methodSpec . return , 'type' ) , 'no type given for return value' ) ;
checkRule ( _ . has ( methodSpec . return , 'description' ) , 'no description given for return value' ) ;
return valid ;
}
2015-05-26 17:22:13 -07:00
2015-05-20 10:38:19 -07:00
// get enum specifications
function getEnums ( spec _c , bygroup , parent ) {
var spec _js = { } ;
2015-05-26 17:22:13 -07:00
var enumGroups = _ . find ( getChildren ( spec _c , 'sectiondef' ) , function ( section ) {
2015-05-20 10:38:19 -07:00
var kind = getAttr ( section , 'kind' ) ;
2015-05-26 17:22:13 -07:00
return ( ( kind == 'enum' ) || ( kind == 'public-type' ) ) ;
2015-05-20 10:38:19 -07:00
} ) ;
if ( enumGroups ) {
2015-05-26 17:22:13 -07:00
_ . each ( enumGroups . children , function ( enumGroup ) {
2015-05-20 10:38:19 -07:00
var enumGroupName = getText ( getChild ( enumGroup , 'name' ) , 'name' ) ;
var enumGroupDescription = getText ( getChild ( enumGroup , 'detaileddescription' ) , 'description' ) ;
var enumGroupVals = getChildren ( enumGroup , 'enumvalue' ) ;
if ( bygroup ) {
spec _js [ enumGroupName ] = {
description : enumGroupDescription ,
members : [ ]
} ;
}
_ . each ( enumGroupVals , function ( e ) {
// TODO: get prefix as option
var enumName = getText ( getChild ( e , 'name' ) , 'name' ) . replace ( /^MRAA_/ , '' ) ;
var enumDescription = getText ( getChild ( e , 'detaileddescription' ) , 'description' ) ;
if ( ! bygroup ) {
spec _js [ enumName ] = {
type : enumGroupName ,
description : enumDescription
} ;
} else {
spec _js [ enumGroupName ] . members . push ( enumName ) ;
}
} ) ;
} ) ;
}
return spec _js ;
}
2015-05-21 10:09:48 -07:00
// get the name for the module/group/class
function getName ( spec _c ) {
return getText ( getChild ( spec _c , 'compoundname' ) , 'name' ) . replace ( xml2js . opts . module + '::' , '' ) ;
}
// get the description for the module/group/class
function getDescription ( spec _c ) {
return getText ( getChild ( spec _c , 'detaileddescription' ) , 'description' ) ;
}
2015-05-26 17:22:13 -07:00
// get the classes (xml file names) for the given module
2015-05-21 10:09:48 -07:00
function getSubclasses ( spec _c ) {
2015-05-20 10:38:19 -07:00
return _ . map ( getChildren ( spec _c , 'innerclass' ) , function ( innerclass ) {
return getAttr ( innerclass , 'refid' ) ;
} ) ;
}
2015-05-26 17:22:13 -07:00
// get the classes (class names) for the given module
2015-05-21 10:09:48 -07:00
function getSubclassNames ( spec _c ) {
return _ . map ( getChildren ( spec _c , 'innerclass' ) , function ( innerclass ) {
return getText ( innerclass ) . replace ( xml2js . opts . module + '::' , '' ) ;
} ) ;
}
// get the submodules (xml file names) for the given module
function getSubmodules ( spec _c ) {
return _ . map ( getChildren ( spec _c , 'innergroup' ) , function ( innergroup ) {
return getAttr ( innergroup , 'refid' ) ;
} ) ;
2015-05-20 10:38:19 -07:00
}
2015-06-04 10:31:38 -07:00
// get parent class, if any
function getParent ( spec _c , className ) {
var parent = getChild ( spec _c , 'basecompoundref' ) ;
if ( parent ) {
parent = getText ( parent ) ;
if ( ! _ . has ( xml2js . CLASSES , parent ) ) {
console . log ( 'WARNING: Class ' + className + ' has unknown parent class ' + parent ) ;
}
}
return parent ;
}
2015-05-20 10:38:19 -07:00
function hasParams ( paramsSpec ) {
2015-05-26 17:22:13 -07:00
return ! ( _ . isEmpty ( paramsSpec ) ||
2015-05-20 10:38:19 -07:00
( ( _ . size ( paramsSpec ) == 1 ) && getText ( getChild ( paramsSpec [ 0 ] , 'type' ) ) == 'void' ) ) ;
}
// get method specifications for top-level module or a given class
// TODO: overloaded functions
// TODO: functions w/ invalid parameter(s)/return
function getMethods ( spec _c , parent ) {
var spec _js = { } ;
var methods = _ . find ( getChildren ( spec _c , 'sectiondef' ) , function ( section ) {
var kind = getAttr ( section , 'kind' ) ;
return ( ( kind == 'public-func' ) || ( kind == 'func' ) ) ;
} ) ;
if ( methods ) {
_ . each ( methods . children , function ( method ) {
var methodName = getText ( getChild ( method , 'name' ) , 'name' ) ;
2015-05-26 17:22:13 -07:00
if ( methodName [ 0 ] != '~' ) { // filter out destructors
2015-05-20 10:38:19 -07:00
try {
var description = getChild ( method , 'detaileddescription' ) ;
var methodDescription = getText ( description , 'description' ) ;
var paramsSpec = getChildren ( method , 'param' ) ;
var params = { } ;
if ( hasParams ( paramsSpec ) ) {
2015-05-21 10:09:48 -07:00
params = getParams ( paramsSpec , getParamsDetails ( description ) , methodName , parent ) ;
2015-05-20 10:38:19 -07:00
}
var returnSpec = getChild ( method , 'type' ) ;
var retval = { } ;
if ( ! _ . isEmpty ( returnSpec ) ) {
2015-05-21 10:09:48 -07:00
retval = getReturn ( returnSpec , getReturnDetails ( description ) , methodName , parent ) ;
2015-05-20 10:38:19 -07:00
}
2015-06-04 10:31:38 -07:00
methodName = getUniqueMethodName ( methodName , spec _js , parent ) ;
2015-05-20 10:38:19 -07:00
spec _js [ methodName ] = {
description : methodDescription ,
params : params ,
return : retval
} ;
} catch ( e ) {
console . log ( ( parent ? ( parent + '.' ) : '' ) + methodName + ' is omitted from JS documentation.' ) ;
console . log ( ' ' + e . toString ( ) ) ;
}
}
} ) ;
}
return spec _js ;
}
2015-06-04 10:31:38 -07:00
// get a unique string to represent the name of an overloaded method
function getUniqueMethodName ( methodName , module , parent ) {
if ( methodName in module ) {
do {
methodName += '!' ;
} while ( methodName in module ) ;
}
return methodName ;
}
2015-05-20 10:38:19 -07:00
// get variable specifications for a class
function getVariables ( spec _c , parent ) {
var spec _js = { } ;
var vars = _ . find ( getChildren ( spec _c , 'sectiondef' ) , function ( section ) {
var kind = getAttr ( section , 'kind' ) ;
return ( kind == 'public-attrib' ) ;
} ) ;
if ( vars ) {
_ . each ( _ . filter ( vars . children , function ( variable ) {
return ( getAttr ( variable , 'kind' ) == 'variable' ) ;
} ) , function ( variable ) {
var varName = getText ( getChild ( variable , 'name' ) , 'name' ) ;
2015-05-21 10:09:48 -07:00
var varType = getType ( getText ( getChild ( variable , 'type' ) ) , parent ) ;
2015-05-20 10:38:19 -07:00
var varDescription = getText ( getChild ( variable , 'detaileddescription' ) ) ;
spec _js [ varName ] = {
type : varType ,
description : varDescription
}
} ) ;
}
return spec _js ;
}
// get return value specs of a method
2015-05-21 10:09:48 -07:00
function getReturn ( spec _c , details , method , parent ) {
var retType = getType ( getText ( spec _c , 'type' ) , parent ) ;
2015-05-20 10:38:19 -07:00
var retDescription = ( details ? getText ( details , 'description' ) : '' ) ;
return ( ( retType == 'void' ) ? { } : {
type : retType ,
description : retDescription
} ) ;
}
// get paramater specs of a method
2015-05-21 10:09:48 -07:00
function getParams ( spec _c , details , method , parent ) {
2015-05-20 10:38:19 -07:00
var spec _js = { } ;
_ . each ( spec _c , function ( param ) {
try {
2015-05-21 10:09:48 -07:00
var paramType = getType ( getText ( getChild ( param , 'type' ) , 'type' ) , parent ) ;
2015-05-26 17:22:13 -07:00
var paramName = getText ( getChild ( param , 'declname' ) , 'name' ) ;
2015-05-20 10:38:19 -07:00
spec _js [ paramName ] = { type : paramType } ;
} catch ( e ) {
if ( paramType == '...' ) {
spec _js [ 'arguments' ] = { type : paramType } ;
} else {
throw e ;
}
}
} ) ;
_ . each ( details , function ( param ) {
var getParamName = function ( p ) { return getText ( getChild ( getChild ( p , 'parameternamelist' ) , 'parametername' ) , 'name' ) ; }
var paramName = getParamName ( param ) ;
var paramDescription = getText ( getChild ( param , 'parameterdescription' ) , 'description' ) ;
if ( _ . has ( spec _js , paramName ) ) {
spec _js [ paramName ] . description = paramDescription ;
} else {
var msg = ' has documentation for an unknown parameter: ' + paramName + '. ' ;
var suggestions = _ . difference ( _ . keys ( spec _js ) , _ . map ( details , getParamName ) ) ;
var msgAddendum = ( ! _ . isEmpty ( suggestions ) ? ( 'Did you mean ' + suggestions . join ( ', or ' ) + '?' ) : '' ) ;
2015-05-21 10:09:48 -07:00
console . log ( 'Warning: ' + ( parent ? ( parent + '.' ) : '' ) + method + msg + msgAddendum ) ;
2015-05-20 10:38:19 -07:00
}
} ) ;
return spec _js ;
}
// get the equivalent javascript type from the given c type
2015-05-21 10:09:48 -07:00
function getType ( type _c , parent ) {
2015-05-20 10:38:19 -07:00
var type _js = type _c ;
_ . find ( xml2js . TYPEMAPS , function ( to , from ) {
var pattern = new RegExp ( from , 'i' ) ;
if ( type _c . search ( pattern ) == 0 ) {
type _js = to ;
2015-05-21 10:09:48 -07:00
return true ;
2015-05-20 10:38:19 -07:00
}
} ) ;
2015-05-21 10:09:48 -07:00
if ( isPointer ( type _js ) ) {
var dataType = getPointerDataType ( type _js ) ;
var className = parent . toLowerCase ( ) ;
if ( _ . has ( xml2js . ARRAY _TYPEMAPS , dataType ) && _ . contains ( xml2js . ARRAY _TYPEMAPS [ dataType ] . classes , className ) ) {
type _js = xml2js . ARRAY _TYPEMAPS [ dataType ] . arrayType ;
} else if ( _ . has ( xml2js . POINTER _TYPEMAPS , className ) && _ . has ( xml2js . POINTER _TYPEMAPS [ className ] , dataType ) ) {
type _js = xml2js . POINTER _TYPEMAPS [ className ] [ dataType ] ;
} else if ( _ . has ( xml2js . CLASSES , dataType ) ) { // TODO: verify that swig does this mapping
type _js = dataType ;
} else {
type _js = dataType + ' *'
}
2015-05-20 10:38:19 -07:00
}
return type _js ;
}
// verify that all types associated with the method are valid
function hasValidTypes ( methodSpec , methodName , parent ) {
2015-05-26 17:22:13 -07:00
var valid = true ;
2015-05-20 10:38:19 -07:00
var msg = ( xml2js . opts . strict ? ' is omitted from JS documentation.' : ' has invalid type(s).' ) ;
var printIgnoredMethodOnce = _ . once ( function ( ) { console . log ( methodName + msg ) ; } ) ;
_ . each ( methodSpec . params , function ( paramSpec , paramName ) {
if ( ! isValidType ( paramSpec . type , parent ) ) {
valid = false ;
printIgnoredMethodOnce ( ) ;
2015-05-21 10:09:48 -07:00
console . log ( ' Error: parameter ' + paramName + ' has invalid type ' + typeToString ( paramSpec . type ) ) ;
2015-05-20 10:38:19 -07:00
}
} ) ;
if ( ! _ . isEmpty ( methodSpec . return ) && ! isValidType ( methodSpec . return . type , parent ) ) {
valid = false ;
printIgnoredMethodOnce ( ) ;
2015-05-21 10:09:48 -07:00
console . log ( ' Error: returns invalid type ' + typeToString ( methodSpec . return . type ) ) ;
2015-05-20 10:38:19 -07:00
}
return valid ;
}
// verify that type of variable is valid
function ofValidType ( varSpec , varName , parent ) {
if ( isValidType ( varSpec . type , parent ) ) {
return true ;
} else {
var msgAddendum = ( xml2js . opts . strict ? ' Omitted from JS documentation.' : '' ) ;
2015-05-21 10:09:48 -07:00
console . log ( 'Error: ' + varName + ' is of invalid type ' + typeToString ( varSpec . type ) + '.' + msgAddendum ) ;
2015-05-20 10:38:19 -07:00
return false ;
}
}
// verify whether the given type is valid JS
function isValidType ( type , parent ) {
return ( _ . contains ( _ . values ( xml2js . TYPEMAPS ) , type ) ||
_ . has ( xml2js . CLASSES , type ) ||
_ . has ( xml2js . ENUMS _BY _GROUP , type ) ||
_ . contains ( [ 'Buffer' , 'Function' , 'mraa_result_t' ] , type ) ||
2015-05-21 10:09:48 -07:00
_ . has ( ( parent ? xml2js . CLASSES [ parent ] . enums _by _group : [ ] ) , type ) ||
isValidPointerType ( type , parent ) ) ;
}
function isValidPointerType ( type , parent ) {
var className = parent . toLowerCase ( ) ;
var arrayTypemap = _ . find ( xml2js . ARRAY _TYPEMAPS , function ( to ) { return to . arrayType == type ; } ) ;
var valid = ( ( arrayTypemap && _ . contains ( arrayTypemap . classes , className ) ) ||
( _ . has ( xml2js . POINTER _TYPEMAPS , className ) && ( _ . contains ( _ . values ( xml2js . POINTER _TYPEMAPS [ className ] ) , type ) ) ) ) ;
return valid ;
2015-05-20 10:38:19 -07:00
}
// determines whether a type looks like a c pointer
function isPointer ( type ) {
2015-05-21 10:09:48 -07:00
return ( type . search ( /\w+\s*(\*|&)$/ ) != - 1 ) ;
}
// remove extraneous whitespace from pointer types as canonical representation
function normalizePointer ( ptr ) {
return ptr . replace ( /\s*$/ , '' ) ;
}
// get the data type of a pointer (e.g. int is the data type of int*)
function getPointerDataType ( ptr ) {
return ptr . replace ( /\s*(\*|&)$/ , '' ) ;
}
// print more human friendly type for error messages
function typeToString ( type ) {
return type . replace ( '*' , '*' ) ;
2015-05-20 10:38:19 -07:00
}
// get the detailed description of a method's parameters
function getParamsDetails ( spec _c ) {
var paras = getChildren ( spec _c , 'para' ) ;
var details = _ . find ( _ . map ( paras , function ( para ) {
return getChild ( para , 'parameterlist' ) ;
} ) , function ( obj ) { return ( obj != undefined ) ; } ) ;
return ( details ? details . children : undefined ) ;
}
// get the detailed description of a method's return value
function getReturnDetails ( spec _c ) {
var paras = getChildren ( spec _c , 'para' ) ;
return _ . find ( _ . map ( paras , function ( para ) {
return getChild ( para , 'simplesect' ) ;
} ) , function ( obj ) { return ( ( obj != undefined ) && ( getAttr ( obj , 'kind' ) == 'return' ) ) ; } ) ;
}
// get (and flatten) the text of the given object
function getText ( obj , why ) {
2015-05-26 17:22:13 -07:00
// TODO: links ignored for now, patched for types for
2015-05-20 10:38:19 -07:00
var GENERATE _LINK = function ( x ) { return x + ' ' ; }
return _ . reduce ( obj . children , function ( text , elem ) {
if ( _ . isString ( elem ) ) {
return text += elem . trim ( ) + ' ' ;
} else if ( _ . isPlainObject ( elem ) ) {
switch ( elem . name ) {
case 'para' :
return text += getText ( elem , why ) + ' \n' ;
case 'ref' :
return text += GENERATE _LINK ( getText ( elem , why ) ) ;
case 'parameterlist' :
case 'simplesect' :
return text ; // to be handled elsewhere
case 'programlisting' :
case 'htmlonly' :
return text ; // ignored
2015-05-21 10:09:48 -07:00
// TODO: html doesn't seem to work for yuidoc, using markdown for now
2015-05-20 10:38:19 -07:00
case 'itemizedlist' :
2015-05-21 10:09:48 -07:00
return text += '\n' + getText ( elem , why ) + ' \n \n' ;
2015-05-20 10:38:19 -07:00
case 'listitem' :
return text += '+ ' + getText ( elem , why ) + '\n' ;
case 'bold' :
return text += '__' + getText ( elem , why ) . trim ( ) + '__ ' ;
case 'ulink' :
return text += '[' + getText ( elem , why ) . trim ( ) + '](' + getAttr ( elem , 'url' ) . trim ( ) + ') ' ;
case 'image' :
// TODO: copy images over; hard coded for now
var fn = getAttr ( elem , 'name' ) ;
2015-05-21 10:09:48 -07:00
return text += ' \n \n ' ;
2015-05-20 10:38:19 -07:00
case 'linebreak' :
return text += ' \n' ;
case 'ndash' :
return text += '– ' ;
default :
// TODO: incomplete list of doxygen xsd implemented
2015-05-26 17:22:13 -07:00
console . warn ( 'NYI Unknown Object Type: ' + elem . name ) ;
return text ;
//throw new Error('NYI Unknown Object Type: ' + elem.name);
2015-05-20 10:38:19 -07:00
}
} else {
throw new Error ( 'NYI Unknown Type: ' + ( typeof elem ) ) ;
}
} , '' ) . trim ( ) ;
}
// get the value of attribute with the given name of the given object
function getAttr ( obj , name ) {
return _ . find ( obj . attr , function ( item ) {
return item . name == name ;
} ) . value ;
}
// get the child object with the given name of the given object
function getChild ( obj , name ) {
return _ . find ( obj . children , function ( child ) {
return child . name == name ;
} ) ;
}
// get all children objects with the given name of the given object
function getChildren ( obj , name ) {
return _ . filter ( obj . children , function ( child ) {
return child . name == name ;
} ) ;
}
// debug helper: print untruncated object
function printObj ( obj ) {
console . log ( util . inspect ( obj , false , null ) ) ;
}
2015-05-26 17:22:13 -07:00
2015-05-21 10:09:48 -07:00
module . exports = xml2js ;