-
Notifications
You must be signed in to change notification settings - Fork 62
Improved Interface Generator #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
4a534ad
Improve Interface Generator
Niels-Be 70b8cd7
fix option desc
Niels-Be 182d11a
comment out service decorators for js interface template
Niels-Be 844803e
fix Set propetry type in ts interface template
Niels-Be 1afbbed
Generate the same interface for js and ts classes
Niels-Be 1a20eff
Add JS service lcass template
Niels-Be c5b5264
Only add PropertiesChanged listener if requested by user
Niels-Be 5e592fb
Allow class creation without Introspect call using Connect
Niels-Be 9fa8913
fix xml escaping
Niels-Be bfdf796
fix removal of PropertiesChanged listener
Niels-Be 4cd313c
fix service name in connect method
Niels-Be 8d8b4ed
Simplify template argument
Niels-Be bad9d30
Add Recursive option
Niels-Be c6f5d87
Improve documentation of generated interfaces
Niels-Be 0f05e74
find common object paths using wildcards
Niels-Be f4d7fe6
fix Variant type generation
Niels-Be eed0300
fix typecript BigInt type
Niels-Be c4b9845
add missing types
Niels-Be File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,290 @@ | ||
| #!/usr/bin/env node | ||
|
|
||
| const fs = require('fs'); | ||
| const xml2js = require('xml2js'); | ||
| const Handlebars = require('handlebars'); | ||
| let parser = new xml2js.Parser(); | ||
| const program = require('commander'); | ||
| const dbus = require('../'); | ||
| const Message = dbus.Message; | ||
| const { | ||
| METHOD_RETURN, | ||
| ERROR, | ||
| SIGNAL, | ||
| METHOD_CALL | ||
| } = dbus.MessageType; | ||
| const { | ||
| isObjectPathValid, | ||
| isMemberNameValid, | ||
| isInterfaceNameValid, | ||
| isBusNameValid | ||
| } = dbus.validators; | ||
|
|
||
| function exitError(message) { | ||
| program.outputHelp(); | ||
| console.error(); | ||
| console.error(message); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| program | ||
| .version('0.1.0') | ||
| .description('Generate an interface from a DBus object') | ||
| .option('--system', 'Use the system bus') | ||
| .option('--session', 'Use the session bus') | ||
| .option('-t, --template [path]', 'Template to use for interface generation') | ||
| .option('--full', 'Do not exclude DBus standart interfaces') | ||
| .option('-p, --prefix', 'Prefix class names with full interface path') | ||
| .option('-o, --output [path]', 'The output file path for Typescript classes (default: stdout)') | ||
| .arguments('<destination> <objectPath>') | ||
| .parse(process.argv); | ||
|
|
||
|
|
||
|
|
||
| if (program.system && program.session) { | ||
| exitError('Only one of --system or --session may be passed'); | ||
| } | ||
|
|
||
| if (!program.args[0]) { | ||
| exitError('<destination> positional argument is required'); | ||
| } | ||
|
|
||
| if (!program.args[1]) { | ||
| exitError('<objectPath> positional argument is required'); | ||
| } | ||
|
|
||
| const destination = program.args[0]; | ||
| const objectPath = program.args[1]; | ||
|
|
||
| if (!isObjectPathValid(objectPath)) { | ||
| exitError(`got invalid object path: ${objectPath}`); | ||
| } | ||
|
|
||
| if (!isBusNameValid(destination) && !destination.match(/^:\d+/)) { | ||
| exitError(`got invalid destination: ${destination}`); | ||
| } | ||
|
|
||
| program.template = program.template || __dirname + "/../templates/javascript-class.hbs"; | ||
|
|
||
| if (!fs.existsSync(program.template)) { | ||
| exitError(`template file '${program.template}' does not exists`); | ||
| } | ||
|
|
||
| const bus = (program.system ? dbus.systemBus() : dbus.sessionBus()); | ||
|
|
||
|
|
||
| function getInterfaceDesc(destination, objectPath) { | ||
| const message = new Message({ | ||
| type: METHOD_CALL, | ||
| destination: destination, | ||
| path: objectPath, | ||
| interface: "org.freedesktop.DBus.Introspectable", | ||
| member: "Introspect", | ||
| signature: "", | ||
| body: [] | ||
| }); | ||
|
|
||
| return bus.call(message).then((reply) => reply.body[0]); | ||
| } | ||
|
|
||
|
|
||
| function collapseSignature(args, dir) { | ||
| let signature = ''; | ||
| args = args || []; | ||
| for (arg of args) { | ||
| if (!dir || arg['$'].direction === dir) { | ||
| signature += arg['$'].type; | ||
| } | ||
| } | ||
| return signature; | ||
| } | ||
|
|
||
| function tsType(type) { | ||
| switch (type) { | ||
| case "b": return "boolean"; | ||
| case "y": | ||
| case "n": | ||
| case "q": | ||
| case "i": | ||
| case "u": | ||
| case "h": | ||
| case "d": | ||
| return "number"; | ||
| case "x": | ||
| case "t": | ||
| return "DBus.BigInt"; | ||
| case "g": | ||
| case "s": | ||
| return "string"; | ||
| case "o": | ||
| return "DBus.ObjectPath"; | ||
| case "v": | ||
| return "DBus.Variant"; | ||
| } | ||
| if (type[0] === "a") { | ||
| if (type[1] === "{") { | ||
| if (type.match(/^a\{\w\w\}$/)) { | ||
| return `{[key: ${tsType(type[2])}]: ${tsType(type[3])}}` | ||
| } | ||
| //TODO: handle more complex types | ||
| return `/* ${type} */ {[key:string]: any}`; | ||
| } | ||
| // array of bytes is a NodeJS.Buffer | ||
| if (type[1] === "y") { | ||
| return "Buffer"; | ||
| } | ||
| return new Handlebars.SafeString("Array<" + tsType(type.substr(1)) + ">"); | ||
| } | ||
| if (type[0] === "(") { | ||
| //TODO: handle more complex types | ||
| return `/* ${type} */ any[]`; | ||
| } | ||
|
|
||
| return `/* ${type} */ any`; | ||
| } | ||
|
|
||
| const helpers = { | ||
| ifeq(a, b, options) { | ||
| if (a == b) { return options.fn(this); } | ||
| return options.inverse(this); | ||
| }, | ||
|
|
||
| tsType: tsType, | ||
| outType(args) { | ||
| args = (args || []).map(p => p["$"]).filter((p) => p.direction === "out"); | ||
|
|
||
| if (args.length === 0) return "void"; | ||
| if (args.length === 1) return tsType(args[0].type); | ||
| return "any" | ||
| }, | ||
|
|
||
| canRead(access, options) { | ||
| if (access === "read" || access === "readwrite") { return options.fn(this); } | ||
| return options.inverse(this); | ||
| }, | ||
| canWrite(access, options) { | ||
| if (access === "write" || access === "readwrite") { return options.fn(this); } | ||
| return options.inverse(this); | ||
| }, | ||
|
|
||
| className(ifaceName) { | ||
| if (program.prefix) { | ||
| let name = ifaceName.split(''); | ||
| name[0] = name[0].toUpperCase(); | ||
| let dots = 0; | ||
| for (let i = 0; i < name.length - dots; ++i) { | ||
| if (name[i + dots] === '.') { | ||
| name[i] = name[i + dots + 1].toUpperCase(); | ||
| ++dots; | ||
| } else { | ||
| name[i] = name[i + dots]; | ||
| } | ||
| } | ||
|
|
||
| return name.slice(0, -1 * dots).join(''); | ||
| } else { | ||
| const path = ifaceName.split("."); | ||
| const name = path[path.length - 1]; | ||
| return name.charAt(0).toUpperCase() + name.slice(1); | ||
| } | ||
| }, | ||
| accessConst(access) { | ||
| if (access === 'read') { | ||
| return 'ACCESS_READ'; | ||
| } else if (access === 'write') { | ||
| return 'ACCESS_WRITE'; | ||
| } else if (access === 'readwrite') { | ||
| return 'ACCESS_READWRITE'; | ||
| } else { | ||
| throw new Error(`got unknown access: ${access}`); | ||
| } | ||
| }, | ||
| inSignature(args) { | ||
| return collapseSignature(args, 'in'); | ||
| }, | ||
| outSignature(args) { | ||
| return collapseSignature(args, 'out'); | ||
| }, | ||
| signature(args) { | ||
| return collapseSignature(args); | ||
| }, | ||
| countArgs(args, dir) { | ||
| let count = 0; | ||
| for (arg of args) { | ||
| if (!dir || arg['$'].direction === dir) { | ||
| count++; | ||
| } | ||
| } | ||
| return count; | ||
| } | ||
| }; | ||
|
|
||
| Handlebars.registerHelper(helpers); | ||
|
|
||
| async function parseXml(data) { | ||
| return new Promise((resolve, reject) => { | ||
| parser.parseString(data, (err, xml) => { | ||
| if (err) { | ||
| reject(err); | ||
| } | ||
| resolve(xml); | ||
| }) | ||
| }); | ||
| } | ||
|
|
||
| async function templateXmlData(template, data) { | ||
| let interfaces = []; | ||
|
|
||
| let xml = await parseXml(data); | ||
| if (!xml.node) { | ||
| console.error('xml document did not contain a root node') | ||
| process.exit(1); | ||
| } | ||
| if (!xml.node.interface) { | ||
| console.error('xml document did not contain any interfaces'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| for (let iface of xml.node.interface) { | ||
| if (!iface['$'] || !iface['$'].name) { | ||
| console.log('got an interface without a name') | ||
| process.exit(1); | ||
| } | ||
| } | ||
|
|
||
| for (let iface of xml.node.interface) { | ||
| if (!program.full && iface['$'].name.startsWith('org.freedesktop.DBus.')) { | ||
| // ignore standard interfaces | ||
| continue; | ||
| } | ||
| interfaces.push(iface); | ||
| } | ||
|
|
||
| return template({ interfaces: interfaces }); | ||
| } | ||
|
|
||
| async function main() { | ||
| const templateStr = await (fs.promises ? fs.promises.readFile : fs.readFileSync)(program.template, { encoding: "utf8" }); | ||
|
|
||
| const template = Handlebars.compile(templateStr); | ||
| const desc = await getInterfaceDesc(destination, objectPath); | ||
| //console.log(desc); | ||
| const result = await templateXmlData(template, desc); | ||
|
|
||
| if (program.output) { | ||
| await (fs.promises ? fs.promises.writeFile : fs.writeFileSync)(program.output, result); | ||
| } else { | ||
| console.log(result); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| main() | ||
| .then(() => { | ||
| bus.disconnect(); | ||
| }) | ||
| .catch((err) => { | ||
| console.error(`Error:`, err); | ||
| process.exit(1); | ||
| }); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| let dbus = require('dbus-next'); | ||
| let Variant = dbus.Variant; | ||
|
|
||
| /* | ||
| * Generated by dbus-next interface generator | ||
| */ | ||
|
|
||
| let { | ||
| Interface, property, method, signal, DBusError, | ||
| ACCESS_READ, ACCESS_WRITE, ACCESS_READWRITE | ||
| } = dbus.interface; | ||
|
|
||
| {{#each interfaces}} | ||
| module.exports.{{className $.name}} = class {{className $.name}} extends Interface { | ||
| constructor() { | ||
| super('{{$.name}}'); | ||
| } | ||
|
|
||
| {{#each property}} | ||
| @property({ name: '{{$.name}}', signature: '{{$.type}}', access: {{accessConst $.access}} }) | ||
| get {{$.name}}() { | ||
| // TODO: implement property getter for {{$.name}} | ||
| } | ||
|
|
||
| set {{$.name}}(value) { | ||
| // TODO: implement property setter for {{$.name}} | ||
| } | ||
|
|
||
| {{/each}} | ||
|
|
||
| {{#each method}} | ||
| @method({ name: '{{$.name}}', inSignature: '{{inSignature arg}}', outSignature: '{{outSignature arg}}' }) | ||
| {{$.name}}({{#each arg}}{{#ifeq $.direction "in"}}{{$.name}}{{#unless @last}}, {{/unless}}{{/ifeq}}{{/each}}) { | ||
| // TODO: implement the {{$.name}} method | ||
| } | ||
|
|
||
| {{/each}} | ||
| {{#each signal}} | ||
| @signal({ name: '{{$.name}}', signature: '{{signature arg}}' }) | ||
| {{$.name}}({{#each arg}}{{$.name}}{{#unless @last}}, {{/unless}}{{/each}}) { | ||
| // TODO: implement the {{$.name}} signal | ||
| } | ||
|
|
||
| {{/each}} | ||
| } | ||
|
|
||
| {{/each}} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.