diff --git a/.gitignore b/.gitignore index c2658d7d..c6873c31 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules/ +npm-debug.log +package-lock.json diff --git a/README.md b/README.md index c00c5774..a68c81d2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Moxygen is currently used in conjunction with GitBook to generate the API docume * **Multi page output**: Output single or multiple files * **Internal linking**: Anchors in comments and function definitions are supported * **Markdown comments**: Markdown in Doxygen comments are rendered -* **Doxygen groups**: Doxygen [grouping](https://www.stack.nl/~dimitri/doxygen/manual/grouping.html) is supported for more organised documentation +* **Doxygen groups**: Doxygen [grouping](http://www.doxygen.nl/manual/grouping.html) is supported for more organised documentation * **Custom templates**: Modify the core Markdown templates to add your own flavour * **Optional index**: Optionally render a top level index @@ -20,24 +20,28 @@ Moxygen is currently used in conjunction with GitBook to generate the API docume 3. Install `moxygen` like so: `npm install moxygen -g`. 4. Run `moxygen` providing the folder location of the XML documentation as the first argument ie. `{OUTPUT_DIRECTORY}/xml`. ``` - Usage: moxygen [options] + Usage: moxygen [options] Options: - -h, --help output usage information -V, --version output the version number - -o, --output output file (must contain %s when using groups) + -o, --output output file, must contain "%s" when using `groups` or `classes` -g, --groups output doxygen groups into separate files - -n, --noindex disable generation of the index (no effect with `groups` option + -c, --classes output doxygen classes into separate files + -p, --pages output doxygen pages into separate files + -n, --noindex disable generation of the index, ignored with `groups` or `classes` -a, --anchors add anchors to internal links + -H, --html-anchors add html anchors to internal links -l, --language programming language -t, --templates custom templates directory + -L, --logfile [file] output log messages to file -q, --quiet quiet mode + -h, --help output usage information ``` ## Multi-page Output -Moxygen supports the doxygen [groups](http://www.stack.nl/~dimitri/doxygen/manual/grouping.html#modules) syntax for generating multi page documentation. Every [\defgroup](http://www.stack.nl/~dimitri/doxygen/manual/commands.html#cmddefgroup) in your source code will be parsed and output into a separate markdown file, with internal reference updated accordingly. +Moxygen supports the doxygen [groups](http://www.doxygen.nl/manual/grouping.html#modules) syntax for generating multi page documentation. Every [\defgroup](http://www.doxygen.nl/manual/commands.html#cmddefgroup) in your source code will be parsed and output into a separate markdown file, with internal reference updated accordingly. Example: @@ -54,10 +58,23 @@ To get a feel for how Moxygen works you can play with the example which is locat * Pre-generated XML output in [example/xml](/example/xml) * Pre-generated output Markdown files in [example/doc](/example/doc) -The rebuild the example XML you can run `doxygen` from within the example folder. +To fully build the example, follow these steps (once you've installed `doxygen`. See **Development & Contribution**, below): -Now you can build the example documentation with the following command from within the example folder: +1. Rebuild the XML: run `doxygen` from within the example folder. +2. Rebuild the Moxygen output: from within this directory, ``` -moxygen --anchors --groups --output=example/doc/api-%s.md example/xml +node bin/moxygen.js --groups --pages --anchors --output=example/doc/api-%s.md example/xml ``` + +## Development & Contribution + +You can develop this project as you would any other Node project: + +1. Clone this repo. +2. `npm install` from this directory. + +This project is tested through integration, by building the `example/`. To quickly test this project: + +1. Install `doxygen` (`brew install doxygen`, `choco install doxygen`, for example). Only must be done once per computer. +2. `npm test` from this directory. This will run Doxygen on the `example/` and build the output. diff --git a/bin/moxygen.js b/bin/moxygen.js index 01158782..092681dc 100755 --- a/bin/moxygen.js +++ b/bin/moxygen.js @@ -1,36 +1,41 @@ #!/usr/bin/env node 'use strict'; -var log = require('winston'); +var logger = require('../src/logger'); var program = require('commander'); var assign = require('object-assign'); var pjson = require('../package.json'); var app = require('../index.js'); program.version(pjson.version) - .usage('[options] ') - .option('-o, --output ', 'output file (must contain %s when using groups)', String, 'api.md') + .usage('[options] ') + .option('-o, --output ', 'output file, must contain "%s" when using `groups` or `classes` (default: "api.md"/"api_%s.md")', String) .option('-g, --groups', 'output doxygen groups into separate files', false) - .option('-n, --noindex', 'disable generation of the index (no effect with `groups` option', false) + .option('-c, --classes', 'output doxygen classes into separate files', false) + .option('-p, --pages', 'output doxygen pages into separate files', false) + .option('-n, --noindex', 'disable generation of the index, ignored with `groups` or `classes`', false) .option('-a, --anchors', 'add anchors to internal links', false) + .option('-H, --html-anchors', 'add html anchors to internal links', false) .option('-l, --language ', 'programming language', String, 'cpp') - .option('-t, --templates ', 'custom templates directory', String, 'templates') + .option('-t, --templates ', 'custom templates directory (default: "built-in templates")', String) + .option('-L, --logfile [file]', 'output log messages to file, (default: console only, default file name: "moxygen.log")') .option('-q, --quiet', 'quiet mode', false) .parse(process.argv); -if (!program.quiet) { - log.level = 'verbose'; -} +logger.init(program, app.defaultOptions); if (program.args.length) { app.run(assign({}, app.defaultOptions, { directory: program.args[0], output: program.output, groups: program.groups, + pages: program.pages, + classes: program.classes, noindex: program.noindex, anchors: program.anchors, + htmlAnchors: program.htmlAnchors, language: program.language, - templates: program.templates + templates: program.templates, })); } else { diff --git a/example/doc/api-bicycle.md b/example/doc/api-bicycle.md index 6aaf8830..41986251 100644 --- a/example/doc/api-bicycle.md +++ b/example/doc/api-bicycle.md @@ -1,12 +1,12 @@ # group `bicycle` {#group__bicycle} -Bicycle module contains the bycicle class. Bicycles are a useful way of transporting oneself, without too much effort. +Bicycle module contains the bicycle class. Bicycles are a useful way of transporting oneself, without too much effort. ## Summary Members | Descriptions --------------------------------|--------------------------------------------- -`class `[`transport::Bicycle`](example/doc/api-bicycle.md#classtransport_1_1Bicycle) | Standard bicycle class. +`class `[`transport::Bicycle`](#classtransport_1_1Bicycle) | Standard bicycle class. # class `transport::Bicycle` {#classtransport_1_1Bicycle} diff --git a/example/doc/api-mountainbike.md b/example/doc/api-mountainbike.md index 22e2adc7..c052c0c1 100644 --- a/example/doc/api-mountainbike.md +++ b/example/doc/api-mountainbike.md @@ -6,7 +6,7 @@ Mountain bike module contains the `MountainBike` class. Mountain bikes are a kin Members | Descriptions --------------------------------|--------------------------------------------- -`class `[`transport::MountainBike`](example/doc/api-mountainbike.md#classtransport_1_1MountainBike) | Mountain bike implementation of a `[Bicycle](example/doc/api-bicycle.md#classtransport_1_1Bicycle)`. +`class `[`transport::MountainBike`](#classtransport_1_1MountainBike) | Mountain bike implementation of a `[Bicycle](example/doc/api-bicycle.md#classtransport_1_1Bicycle)`. # class `transport::MountainBike` {#classtransport_1_1MountainBike} @@ -15,16 +15,16 @@ class transport::MountainBike : public transport::Bicycle ``` -Mountain bike implementation of a `[Bicycle](#classtransport_1_1Bicycle)`. +Mountain bike implementation of a `[Bicycle](example/doc/api-bicycle.md#classtransport_1_1Bicycle)`. -[MountainBike](#classtransport_1_1MountainBike) is an implementation of a [Bicycle](#classtransport_1_1Bicycle) providing a bike for cycling on rough terrain. Mountain bikes are pretty cool because they have stuff like **Suspension** (and you can even adjust it using SetSuspension). If you're looking for a bike for use on the road, you might be better off using a [RacingBike](#classtransport_1_1RacingBike) though. +[MountainBike](#classtransport_1_1MountainBike) is an implementation of a [Bicycle](example/doc/api-bicycle.md#classtransport_1_1Bicycle) providing a bike for cycling on rough terrain. Mountain bikes are pretty cool because they have stuff like **Suspension** (and you can even adjust it using SetSuspension). If you're looking for a bike for use on the road, you might be better off using a [RacingBike](example/doc/api-racingbike.md#classtransport_1_1RacingBike) though. ## Summary Members | Descriptions --------------------------------|--------------------------------------------- `public bool `[`SetSuspension`](#classtransport_1_1MountainBike_1a04caecd7e5ff7572b6ac1dc283510301)`(double stiffness)` | Set suspension stiffness. the suspension stiffness. -`public template`
`inline bool `[`ChangeBreak`](#classtransport_1_1MountainBike_1afd02513876a196e98acaacdc555aeb52)`(BreakType breakType)` | Change the break type. the break type. the type of the break. +`public template<>`
`inline bool `[`ChangeBreak`](#classtransport_1_1MountainBike_1afd02513876a196e98acaacdc555aeb52)`(BreakType breakType)` | Change the break type. the break type. the type of the break. ## Members @@ -37,7 +37,7 @@ SetSuspension changes the stiffness of the suspension on the bike. The method wi #### Returns true if the suspension was adjusted successfully, false otherwise. -#### `public template`
`inline bool `[`ChangeBreak`](#classtransport_1_1MountainBike_1afd02513876a196e98acaacdc555aeb52)`(BreakType breakType)` {#classtransport_1_1MountainBike_1afd02513876a196e98acaacdc555aeb52} +#### `public template<>`
`inline bool `[`ChangeBreak`](#classtransport_1_1MountainBike_1afd02513876a196e98acaacdc555aeb52)`(BreakType breakType)` {#classtransport_1_1MountainBike_1afd02513876a196e98acaacdc555aeb52} Change the break type. the break type. the type of the break. diff --git a/example/doc/api-racingbike.md b/example/doc/api-racingbike.md index a9ae08f2..a8674a8d 100644 --- a/example/doc/api-racingbike.md +++ b/example/doc/api-racingbike.md @@ -6,7 +6,7 @@ Racing bike module contains the `RacingBike` class. Racing bikes are a special k Members | Descriptions --------------------------------|--------------------------------------------- -`class `[`transport::RacingBike`](example/doc/api-racingbike.md#classtransport_1_1RacingBike) | Racing bike class. +`class `[`transport::RacingBike`](#classtransport_1_1RacingBike) | Racing bike class. # class `transport::RacingBike` {#classtransport_1_1RacingBike} diff --git a/example/doc/page-changelog.md b/example/doc/page-changelog.md new file mode 100644 index 00000000..6aa5e944 --- /dev/null +++ b/example/doc/page-changelog.md @@ -0,0 +1,10 @@ +# page `changelog` {#changelog} + +## Version 1.0.1 + +* Pedalling harder no longer stands in the saddle by default. This is just better form. + +## Version 1.0.0 + +* Initial release + diff --git a/example/doc/page-overview.md b/example/doc/page-overview.md new file mode 100644 index 00000000..9445d8f8 --- /dev/null +++ b/example/doc/page-overview.md @@ -0,0 +1,8 @@ +# page `overview` {#overview} + +The `transport` namespace provides several differnt types of bycicles, including [transport::Bicycle](example/doc/api-bicycle.md#classtransport_1_1Bicycle), [transport::MountainBike](example/doc/api-mountainbike.md#classtransport_1_1MountainBike) (designed for rough terrain), and [transport::RacingBike](example/doc/api-racingbike.md#classtransport_1_1RacingBike) (designed specifically for challenging races). + +## Changelog + +See the changelog. + diff --git a/example/src/changelog.md b/example/src/changelog.md new file mode 100644 index 00000000..c896cf33 --- /dev/null +++ b/example/src/changelog.md @@ -0,0 +1,9 @@ +# Changelog {#changelog} + +## Version 1.0.1 + +- Pedalling harder no longer stands in the saddle by default. This is just better form. + +## Version 1.0.0 + +- Initial release \ No newline at end of file diff --git a/example/src/overview.md b/example/src/overview.md new file mode 100644 index 00000000..e3d27673 --- /dev/null +++ b/example/src/overview.md @@ -0,0 +1,7 @@ +# Transport {#overview} + +The `transport` namespace provides several differnt types of bycicles, including transport::Bicycle, transport::MountainBike (designed for rough terrain), and transport::RacingBike (designed specifically for challenging races). + +## Changelog + +See [the changelog](changelog.md). \ No newline at end of file diff --git a/example/src/transport.h b/example/src/transport.h index 231fe2e4..4e704aa2 100644 --- a/example/src/transport.h +++ b/example/src/transport.h @@ -3,7 +3,7 @@ /** @defgroup bicycle Bycicle module * - * Bicycle module contains the bycicle class. Bicycles are a useful way of + * Bicycle module contains the bicycle class. Bicycles are a useful way of * transporting oneself, without too much effort. */ diff --git a/example/xml/bicycle_8cpp.xml b/example/xml/bicycle_8cpp.xml index 5ec02e39..55561e22 100644 --- a/example/xml/bicycle_8cpp.xml +++ b/example/xml/bicycle_8cpp.xml @@ -1,19 +1,19 @@ - + bicycle.cpp bicycle.h + + + + - - - - diff --git a/example/xml/bicycle_8h.xml b/example/xml/bicycle_8h.xml index d3ab5a06..387f82fa 100644 --- a/example/xml/bicycle_8h.xml +++ b/example/xml/bicycle_8h.xml @@ -1,5 +1,5 @@ - + bicycle.h src/bicycle.cpp diff --git a/example/xml/changelog.xml b/example/xml/changelog.xml new file mode 100644 index 00000000..41da1885 --- /dev/null +++ b/example/xml/changelog.xml @@ -0,0 +1,17 @@ + + + + changelog + Changelog + + + +Version 1.0.1 + +Pedalling harder no longer stands in the saddle by default. This is just better form. +Version 1.0.0 + +Initial release + + + diff --git a/example/xml/changelog_8md.xml b/example/xml/changelog_8md.xml new file mode 100644 index 00000000..2e899367 --- /dev/null +++ b/example/xml/changelog_8md.xml @@ -0,0 +1,22 @@ + + + + changelog.md + + + + + +#Changelog{#changelog} + +##Version1.0.1 + +-Pedallinghardernolongerstandsinthesaddlebydefault.Thisisjustbetterform. + +##Version1.0.0 + +-Initialrelease + + + + diff --git a/example/xml/classtransport_1_1Bicycle.xml b/example/xml/classtransport_1_1Bicycle.xml index 617a94ae..da039342 100644 --- a/example/xml/classtransport_1_1Bicycle.xml +++ b/example/xml/classtransport_1_1Bicycle.xml @@ -1,5 +1,5 @@ - + transport::Bicycle transport::MountainBike diff --git a/example/xml/classtransport_1_1MountainBike.xml b/example/xml/classtransport_1_1MountainBike.xml index 7b3de1ce..1b2a8869 100644 --- a/example/xml/classtransport_1_1MountainBike.xml +++ b/example/xml/classtransport_1_1MountainBike.xml @@ -1,5 +1,5 @@ - + transport::MountainBike transport::Bicycle diff --git a/example/xml/classtransport_1_1RacingBike.xml b/example/xml/classtransport_1_1RacingBike.xml index b38c5f8f..60286c22 100644 --- a/example/xml/classtransport_1_1RacingBike.xml +++ b/example/xml/classtransport_1_1RacingBike.xml @@ -1,5 +1,5 @@ - + transport::RacingBike transport::Bicycle diff --git a/example/xml/compound.xsd b/example/xml/compound.xsd index 223f7192..81b5e513 100644 --- a/example/xml/compound.xsd +++ b/example/xml/compound.xsd @@ -29,6 +29,7 @@ + @@ -146,9 +147,11 @@ + + @@ -263,6 +266,7 @@ + @@ -277,12 +281,16 @@ - + + + + + @@ -937,6 +945,13 @@ + + + + + + + @@ -952,6 +967,7 @@ + diff --git a/example/xml/dir_68267d1309a1af8e8297ef4c3efbcdba.xml b/example/xml/dir_68267d1309a1af8e8297ef4c3efbcdba.xml index 853925e8..298a4fe2 100644 --- a/example/xml/dir_68267d1309a1af8e8297ef4c3efbcdba.xml +++ b/example/xml/dir_68267d1309a1af8e8297ef4c3efbcdba.xml @@ -1,5 +1,5 @@ - + src bicycle.cpp diff --git a/example/xml/group__bicycle.xml b/example/xml/group__bicycle.xml index b02bf6f3..5ac35668 100644 --- a/example/xml/group__bicycle.xml +++ b/example/xml/group__bicycle.xml @@ -1,5 +1,5 @@ - + bicycle Bycicle module @@ -7,6 +7,6 @@ -Bicycle module contains the bycicle class. Bicycles are a useful way of transporting oneself, without too much effort. +Bicycle module contains the bicycle class. Bicycles are a useful way of transporting oneself, without too much effort. diff --git a/example/xml/group__mountainbike.xml b/example/xml/group__mountainbike.xml index 622ae7c3..ef17ce7f 100644 --- a/example/xml/group__mountainbike.xml +++ b/example/xml/group__mountainbike.xml @@ -1,5 +1,5 @@ - + mountainbike Mountain bike module diff --git a/example/xml/group__racingbike.xml b/example/xml/group__racingbike.xml index 0b924fd8..2280b78b 100644 --- a/example/xml/group__racingbike.xml +++ b/example/xml/group__racingbike.xml @@ -1,5 +1,5 @@ - + racingbike Racing bike module diff --git a/example/xml/index.xml b/example/xml/index.xml index 81ea73ff..e5b56677 100644 --- a/example/xml/index.xml +++ b/example/xml/index.xml @@ -1,5 +1,5 @@ - + transport::Bicycle PedalHarder RingBell @@ -19,10 +19,14 @@ bicycle.h + changelog.md + mountainbike.cpp mountainbike.h + overview.md + racingbike.cpp racingbike.h @@ -40,6 +44,10 @@ mountainbike + changelog + + overview + src diff --git a/example/xml/mountainbike_8cpp.xml b/example/xml/mountainbike_8cpp.xml index 784b86ca..7b08c21e 100644 --- a/example/xml/mountainbike_8cpp.xml +++ b/example/xml/mountainbike_8cpp.xml @@ -1,22 +1,22 @@ - + mountainbike.cpp mountainbike.h - - - - + + + + - + - - - - + + + + diff --git a/example/xml/mountainbike_8h.xml b/example/xml/mountainbike_8h.xml index 1a9caeb2..7164ee21 100644 --- a/example/xml/mountainbike_8h.xml +++ b/example/xml/mountainbike_8h.xml @@ -1,19 +1,19 @@ - + mountainbike.h transport/bicycle.h src/mountainbike.cpp - - - - + - + + + + transport::MountainBike transport diff --git a/example/xml/namespacetransport.xml b/example/xml/namespacetransport.xml index c0a82f88..75424705 100644 --- a/example/xml/namespacetransport.xml +++ b/example/xml/namespacetransport.xml @@ -1,5 +1,5 @@ - + transport transport::Bicycle diff --git a/example/xml/overview.xml b/example/xml/overview.xml new file mode 100644 index 00000000..c65a629e --- /dev/null +++ b/example/xml/overview.xml @@ -0,0 +1,12 @@ + + + + overview + Transport + + + +The transport namespace provides several differnt types of bycicles, including transport::Bicycle, transport::MountainBike (designed for rough terrain), and transport::RacingBike (designed specifically for challenging races).Changelog +See the changelog. + + diff --git a/example/xml/overview_8md.xml b/example/xml/overview_8md.xml new file mode 100644 index 00000000..4846ccbd --- /dev/null +++ b/example/xml/overview_8md.xml @@ -0,0 +1,20 @@ + + + + overview.md + + + + + +#Transport{#overview} + +The`transport`namespaceprovidesseveraldiffernttypesofbycicles,includingtransport::Bicycle,transport::MountainBike(designedforroughterrain),andtransport::RacingBike(designedspecificallyforchallengingraces). + +##Changelog + +See[thechangelog](changelog.md). + + + + diff --git a/example/xml/racingbike_8cpp.xml b/example/xml/racingbike_8cpp.xml index f69f88b9..46a1f4d7 100644 --- a/example/xml/racingbike_8cpp.xml +++ b/example/xml/racingbike_8cpp.xml @@ -1,22 +1,22 @@ - + racingbike.cpp racingbike.h - - - - - - - + - + - + + + + + + + diff --git a/example/xml/racingbike_8h.xml b/example/xml/racingbike_8h.xml index bb99cc43..208a583c 100644 --- a/example/xml/racingbike_8h.xml +++ b/example/xml/racingbike_8h.xml @@ -1,19 +1,19 @@ - + racingbike.h transport/bicycle.h src/racingbike.cpp - + + + + - + - - - transport::RacingBike transport diff --git a/example/xml/transport_8h.xml b/example/xml/transport_8h.xml index 32641ca8..e65a5ba0 100644 --- a/example/xml/transport_8h.xml +++ b/example/xml/transport_8h.xml @@ -1,28 +1,28 @@ - + transport.h transport/bicycle.h transport/racingbike.h transport/mountainbike.h - + + + + + + + - + - + - + - - - - - - - + @@ -40,7 +40,8 @@ - + + TransportType Bycicle diff --git a/index.js b/index.js index 2536ace7..6b3e90e2 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,6 @@ 'use strict'; var path = require('path'); -var util = require('util'); var doxyparser = require('./src/parser'); var templates = require('./src/templates'); @@ -27,6 +26,10 @@ module.exports = { anchors: true, /** Generate anchors for internal links **/ language: 'cpp', /** Programming language **/ templates: 'templates', /** Templates directory **/ + pages: false, /** Output doxygen pages separately **/ + classes: false, /** Output doxygen classes separately **/ + output_s: 'api_%s.md', /** Output file for groups and classes **/ + logfile: 'moxygen.log', /** Log file **/ filters: { members: [ @@ -35,10 +38,20 @@ module.exports = { // 'enumvalue', 'func', // 'variable', + 'property', 'public-attrib', 'public-func', 'protected-attrib', - 'protected-func' + 'protected-func', + 'signal', + 'public-slot', + 'protected-slot', + 'public-type', + 'private-attrib', + 'private-func', + 'private-slot', + 'public-static-func', + 'private-static-func', ], compounds: [ 'namespace', @@ -46,9 +59,10 @@ module.exports = { 'struct', 'union', 'typedef', - // 'file' + 'interface', + // 'file', ] - } + }, }, /** @@ -57,12 +71,23 @@ module.exports = { run: function (options) { // Sanitize options - if (options.groups == options.output.indexOf('%s') === -1) - throw "The `output` file parameter must contain an '%s' for group name " + - "substitution when `groups` are enabled." + if (typeof options.output == "undefined") { + if (options.classes || options.groups) { + options.output = this.defaultOptions.output_s; + } + else { + options.output = this.defaultOptions.output; + } + } - if (options.templates == this.defaultOptions.templates) - options.templates = path.join(__dirname, 'templates', options.language); + if ((options.classes || options.groups) && options.output.indexOf('%s') === -1) { + throw "The `output` file parameter must contain an '%s' for group or class name " + + "substitution when `groups` or `classes` are enabled." + } + + if (typeof options.templates == "undefined") { + options.templates = path.join(__dirname, this.defaultOptions.templates, options.language); + } // Load templates templates.registerHelpers(options); @@ -72,7 +97,6 @@ module.exports = { doxyparser.loadIndex(options, function (err, root) { if (err) throw err; - // Output groups if (options.groups) { var groups = root.toArray('compounds', 'group'); @@ -85,11 +109,24 @@ module.exports = { var compounds = group.toFilteredArray('compounds'); compounds.unshift(group); // insert group at top - var contents = templates.renderArray(compounds); - helpers.writeFile(util.format(options.output, group.name), contents); + helpers.writeCompound(group, templates.renderArray(compounds), doxyparser.references, options); + }); + } + else if (options.classes) { + var rootCompounds = root.toArray('compounds', 'namespace'); + if (!rootCompounds.length) + throw "You have enabled `classes` output, but no classes were " + + "located in your doxygen XML files." + rootCompounds.forEach(function (comp) { + comp.filterChildren(options.filters); + var compounds = comp.toFilteredArray(); + helpers.writeCompound(comp, [templates.render(comp)], doxyparser.references, options); + compounds.forEach(function (e) { + e.filterChildren(options.filters) + helpers.writeCompound(e, [templates.render(e)], doxyparser.references, options); + }); }); } - // Output single file else { root.filterChildren(options.filters); @@ -99,8 +136,21 @@ module.exports = { compounds.unshift(root); // insert root at top if index is enabled var contents = templates.renderArray(compounds); contents.push('Generated by [Moxygen](https://sourcey.com/moxygen)') - helpers.writeFile(options.output, contents); + helpers.writeCompound(root, contents, doxyparser.references, options); } + + if(options.pages){ + var pages = root.toArray('compounds', 'page'); + if(!pages.length) + throw "You have enabled `pages` output, but no pages were " + + "located in your doxygen XML files." + pages.forEach(function(page){ + var compounds = page.toFilteredArray('compounds'); + compounds.unshift(page); + helpers.writeCompound(page, templates.renderArray(compounds), doxyparser.references, options); + }) + } + }); - } + }, } diff --git a/package.json b/package.json index d346b39a..bbcc0904 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { "name": "moxygen", - "version": "0.7.6", + "version": "0.8.0", "description": "Doxygen XML to Markdown documentation converter", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "release": "release-it", + "test": "npm run clean && cd example/ && doxygen && cd .. && node bin/moxygen.js --groups --pages --anchors --output=example/doc/api-%s.md example/xml", + "clean": "rm -rf example/doc/*.md example/xml/" }, "bin": { "moxygen": "./bin/moxygen.js" @@ -16,15 +18,16 @@ "keywords": [ "doxygen", "markdown", - "documentation" + "documentation", + "generator" ], "author": "Kam Low", "license": "MIT", "dependencies": { - "commander": "^2.9.0", - "handlebars": "^4.0.5", - "object-assign": "^4.1.0", - "winston": "^2.2.0", - "xml2js": "^0.4.16" + "commander": "^2.19.0", + "handlebars": "^4.0.12", + "object-assign": "^4.1.1", + "winston": "^3.2.1", + "xml2js": "^0.4.19" } } diff --git a/src/compound.js b/src/compound.js index 80f44912..d39606ac 100644 --- a/src/compound.js +++ b/src/compound.js @@ -6,10 +6,11 @@ **/ 'use strict'; -var log = require('winston'); +var log = require('./logger').getLogger(); -function Compound(parent, name) { +function Compound(parent, id, name) { this.parent = parent; + this.id = id; this.name = name; this.compounds = {}; this.members = []; @@ -19,11 +20,11 @@ function Compound(parent, name) { Compound.prototype = { - find: function (name, create) { - var compound = this.compounds[name]; + find: function (id, name, create) { + var compound = this.compounds[id]; if (!compound && create) { - compound = this.compounds[name] = new Compound(this, name); + compound = this.compounds[id] = new Compound(this, id, name); } return compound; @@ -83,14 +84,16 @@ Compound.prototype = { if (item.kind == 'namespace') { if ((!item.filtered.compounds || !item.filtered.compounds.length) && (!item.filtered.members || !item.filtered.members.length)) { - // log.verbose('Skip empty namespace', item.name); + // log.verbose('Skip empty namespace: ' + item.name); return; } } // skip items not belonging to current group else if (groupid && item.groupid != groupid) { - // log.verbose('Skip item from foreign group', item.kind, item.name, item.groupid, group.id); + // log.verbose('Skip item from foreign group: { item.kind: ' + item.kind + // + ', item.name: ' + item.name + ', item.groupid: ' + // + item.groupid + ', group.id: '+ group.id + '}'); return; } @@ -103,7 +106,7 @@ Compound.prototype = { }); return result; - } + }, } module.exports = Compound; diff --git a/src/helpers.js b/src/helpers.js index af99c5b0..d2d174ca 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -9,7 +9,7 @@ var fs = require('fs'); var path = require('path'); var util = require('util'); -var log = require('winston'); +var log = require('./logger').getLogger(); var handlebars = require('handlebars'); module.exports = { @@ -46,22 +46,76 @@ module.exports = { } }, - // Replace group links to point to correct output file - replaceGroupReflink: function (outfile, references, str) { - var match = str.match(/\((#(.*))\)/), - anchor = match[1], - refid = match[2], - ref = references[refid]; - if (ref && ref.groupname) { - var href = util.format(outfile, ref.groupname) + anchor; - str = str.replace(/\((#(.*))\)/, '(' + href + ')') + getAnchor: function(name, options) { + if (options.anchors) { + return '{#' + name + '}'; } - return str; + else if (options.htmlAnchors) { + return ''; + } + else { + return ''; + } + }, + + findParent: function(compound, kinds) { + while (compound) { + if (kinds.includes(compound.kind)) + return compound; + compound = compound.parent; + } + }, + + // Replace ref links to point to correct output file if needed + resolveRefs: function(content, compound, references, options) { + return content.replace(/\{#ref ([^ ]+) #\}/g, function(_, refid) { + var ref = references[refid] + var page = this.findParent(ref, ['page']); + + if (page) { + if (page.refid == compound.refid) + return '#' + refid; + return this.compoundPath(page, options) + '#' + refid; + } + + if (options.groups) { + if (compound.groupid && compound.groupid == ref.groupid) + return '#' + refid; + return this.compoundPath(ref, options) + '#' + refid; + } else if (options.classes) { + var dest = this.findParent(ref, ['namespace', 'class', 'struct']); + if (!dest || compound.refid == dest.refid) + return '#' + refid; + return this.compoundPath(dest, options) + '#' + refid; + } else { + if (compound.kind == 'page') + return this.compoundPath(compound.parent, options) + '#' + refid; + return '#' + refid; + } + }.bind(this)); + }, + + compoundPath: function(compound, options) { + if (compound.kind == 'page') { + return path.dirname(options.output) + "/page-" + compound.name + ".md"; + } else if (options.groups) { + return util.format(options.output, compound.groupname); + } else if (options.classes) { + return util.format(options.output, compound.name.replace(/\:/g, '-').replace('<', '(').replace('>', ')')); + } else { + return options.output; + } + }, + + writeCompound: function(compound, contents, references, options) { + this.writeFile(this.compoundPath(compound, options), contents.map(function(content) { + return this.resolveRefs(content, compound, references, options); + }.bind(this))); }, // Write the output file writeFile: function (filepath, contents) { - log.verbose('Writing', filepath); + log.verbose('Writing: ' + filepath); var stream = fs.createWriteStream(filepath); stream.once('open', function(fd) { contents.forEach(function (content) { @@ -70,5 +124,5 @@ module.exports = { }); stream.end(); }); - } + }, }; diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 00000000..13c67b43 --- /dev/null +++ b/src/logger.js @@ -0,0 +1,62 @@ +/** + * Original work Copyright (c) 2016 Philippe FERDINAND + * Modified work Copyright (c) 2016 Kam Low + * This file added Copyright (c) 2022 Waldemar Villamayor-Venialbo + * + * @license MIT + **/ +'use strict'; + +const winston = require('winston'); + +// Create the global logger object +let logger = winston.createLogger({ + level : 'info', +}); + +module.exports = { + + init : function (options, defaultOptions) { + // Logger transports + let logfile = null; + let logterm = null; + + // Create log console transport + logterm = new winston.transports.Console({ + consoleWarnLevels : [ 'warn', 'debug' ], + stderrLevels : [ 'error' ], + silent : options.quiet, + format : winston.format.simple(), + }); + + logger.add(logterm); + + // User defined log file? + if (typeof options.logfile != 'undefined') { + // Use default log file name? + if (options.logfile === true) { + options.logfile = defaultOptions.logfile; + } + // Create log file transport + logfile = new winston.transports.File({ + filename : options.logfile, + level : 'silly', + }); + + logger.add(logfile); + } + + // Set the logging level + if (!options.quiet) { + this.setLevel('verbose'); + } + }, + + getLogger: function () { + return logger; + }, + + setLevel: function (level) { + logger.level = level; + }, +}; diff --git a/src/markdown.js b/src/markdown.js index 5e0c862e..1712c0f0 100644 --- a/src/markdown.js +++ b/src/markdown.js @@ -8,6 +8,10 @@ module.exports = { + refLink: function (text, refid) { + return this.link(text, '{#ref ' + refid + ' #}'); + }, + link: function (text, href) { return '[' + text + '](' + href + ')'; }, diff --git a/src/parser.js b/src/parser.js index f021b83d..3b4bdcb7 100644 --- a/src/parser.js +++ b/src/parser.js @@ -7,7 +7,7 @@ 'use strict'; var fs = require('fs'); -var log = require('winston'); +var log = require('./logger').getLogger(); var path = require('path'); var xml2js = require('xml2js'); @@ -33,17 +33,31 @@ function toMarkdown(element, context) { // opening the element switch (element['#name']) { - case 'ref': return s + markdown.link(toMarkdown(element.$$), '#' + element.$.refid, true); + case 'ref': return s + markdown.refLink(toMarkdown(element.$$), element.$.refid); case '__text__': s = element._; break; case 'emphasis': s = '*'; break; case 'bold': s = '**'; break; case 'parametername': case 'computeroutput': s = '`'; break; - case 'parameterlist': s = '\n#### Parameters\n'; break; + case 'parameterlist': + if (element.$.kind == 'exception') { + s = '\n#### Exceptions\n' + } + else { + s = '\n#### Parameters\n' + } + break; + case 'parameteritem': s = '* '; break; case 'programlisting': s = '\n```cpp\n'; break; + case 'orderedlist': + context.push(element); + s = '\n\n'; + break; case 'itemizedlist': s = '\n\n'; break; - case 'listitem': s = '* '; break; + case 'listitem': + s = (context.length > 0 && context[context.length - 1]['#name'] == 'orderedlist') ? '1. ' : '* '; + break; case 'sp': s = ' '; break; case 'heading': s = '## '; break; case 'xrefsect': s += '\n> '; break; @@ -55,12 +69,34 @@ function toMarkdown(element, context) { s = '\n#### Returns\n' } else if (element.$.kind == 'see') { - s = '\n**See also**: ' + s = '**See also**: ' } else { console.assert(element.$.kind + ' not supported.'); } break; + case 'formula': + s = trim(element._); + if (s.startsWith('$') && s.endsWith('$')) return s; + if (s.startsWith('\\[') && s.endsWith('\\]')) + s = trim(s.substring(2, s.length - 2)); + return '\n$$\n' + s + '\n$$\n'; + case 'preformatted': s = '\n
'; break;
+
+          case 'sect1':
+          case 'sect2':
+          case 'sect3':
+            context.push(element);
+            s = '\n' + helpers.getAnchor(element.$.id, module.exports.parserOptions) + '\n';
+            break;
+          case 'title':
+            var level = '#'.repeat(context[context.length - 1]['#name'].slice(-1));
+            s = '\n#' + level + ' ' + element._ + '\n';
+            break;
+
+          case 'mdash': s = '—'; break;
+          case 'ndash': s = '–'; break;
+          case 'linebreak': s = '
'; break; case 'xreftitle': case 'entry': @@ -100,10 +136,21 @@ function toMarkdown(element, context) { case 'programlisting': s += '```\n'; break; case 'codeline': s += '\n'; break; case 'ulink': s = markdown.link(s, element.$.url); break; + case 'orderedlist': + context.pop(); + s += '\n'; + break; case 'itemizedlist': s += '\n'; break; case 'listitem': s += '\n'; break; case 'entry': s = ' | '; break; case 'xreftitle': s += ': '; break; + case 'preformatted': s += '
\n'; break; + case 'sect1': + case 'sect2': + case 'sect3': + context.pop(); + s += '\n'; + break; case 'row': s = '\n' + markdown.escape.row(s); if (element.$$ && element.$$[0].$.thead == "yes") { @@ -165,7 +212,7 @@ module.exports = { if (membersdef) { membersdef.forEach(function (memberdef) { - var member = { name: memberdef.name[0] }; + var member = { name: memberdef.name[0], parent: compound }; compound.members.push(member); Object.keys(memberdef.$).forEach(function(prop) { member[prop] = memberdef.$[prop]; @@ -184,15 +231,21 @@ module.exports = { var m = []; switch (member.kind) { + case 'signal': + case 'slot': + m = m.concat(['{', member.kind, '} ']); + case 'function': m = m.concat(memberdef.$.prot, ' '); // public, private, ... if (memberdef.templateparamlist) { m.push('template<'); - memberdef.templateparamlist[0].param.forEach(function (param, argn) { - m = m.concat(argn == 0 ? [] : ','); - m = m.concat([toMarkdown(param.type)]); - m = m.concat(param.declname ? [' ', toMarkdown(param.declname)] : []); - }); + if (memberdef.templateparamlist.length > 0 && memberdef.templateparamlist.param) { + memberdef.templateparamlist[0].param.forEach(function (param, argn) { + m = m.concat(argn == 0 ? [] : ','); + m = m.concat([toMarkdown(param.type)]); + m = m.concat(param.declname ? [' ', toMarkdown(param.declname)] : []); + }); + } m.push('> \n'); } m = m.concat(memberdef.$.inline == 'yes' ? ['inline', ' '] : []); @@ -201,11 +254,11 @@ module.exports = { m = m.concat(toMarkdown(memberdef.type), ' '); m = m.concat(memberdef.$.explicit == 'yes' ? ['explicit', ' '] : []); // m = m.concat(memberdef.name[0]._); - m = m.concat(markdown.link(member.name, '#' + member.refid, true)); + m = m.concat(markdown.refLink(member.name, member.refid)); m = m.concat('('); if (memberdef.param) { memberdef.param.forEach(function (param, argn) { - m = m.concat(argn == 0 ? [] : ','); + m = m.concat(argn == 0 ? [] : ', '); m = m.concat([toMarkdown(param.type)]); m = m.concat(param.declname ? [' ', toMarkdown(param.declname)] : []); }); @@ -224,7 +277,14 @@ module.exports = { m = m.concat(memberdef.$.mutable == 'yes' ? ['mutable', ' '] : []); m = m.concat(toMarkdown(memberdef.type), ' '); // m = m.concat(memberdef.name[0]._); - m = m.concat(markdown.link(member.name, '#' + member.refid, true)); + m = m.concat(markdown.refLink(member.name, member.refid)); + break; + + case 'property': + m = m.concat(['{', member.kind, '} ']); + m = m.concat(toMarkdown(memberdef.type), ' '); + // m = m.concat(memberdef.name[0]._); + m = m.concat(markdown.refLink(member.name, member.refid)); break; case 'enum': @@ -240,12 +300,12 @@ module.exports = { }); } // m.push(member.kind + ' ' + member.name); - m = m.concat([member.kind, ' ', markdown.link(member.name, '#' + member.refid, true)]); + m = m.concat([member.kind, ' ', markdown.refLink(member.name, member.refid)]); break; default: // m.push(member.kind + ' ' + member.name); - m = m.concat([member.kind, ' ', markdown.link(member.name, '#' + member.refid, true)]); + m = m.concat([member.kind, ' ', markdown.refLink(member.name, member.refid)]); break; } @@ -258,15 +318,15 @@ module.exports = { // namespaces take ownership of the child compound if (child.parent) - delete child.parent.compounds[child.name]; - compound.compounds[child.name] = child; + delete child.parent.compounds[child.id]; + compound.compounds[child.id] = child; child.parent = compound; }, assignNamespaceToGroup: function (compound, child) { // add the namespace to the group - compound.compounds[child.name] = child; + compound.compounds[child.id] = child; // remove namespace clildren from direct group children Object.keys(child.compounds).forEach(function(id) { @@ -279,7 +339,7 @@ module.exports = { // add the namespace to the group // if the child already belongs to a child namespace it will be removed // on the call to `assignNamespaceToGroup` - compound.compounds[child.name] = child; + compound.compounds[child.id] = child; // add a groupid and reference to the compound and all it's members child.groupid = compound.id; @@ -291,6 +351,19 @@ module.exports = { }); }, + extractPageSections: function(page, elements) { + elements.forEach(function(element) { + if (element['#name'] == 'sect1' || element['#name'] == 'sect2' || element['#name'] == 'sect3') { + var id = element.$.id; + var member = { section: element['#name'], id: id, name: id, refid: id, parent: page }; + page.members.push(member); + this.references[member.refid] = member; + } + if (element.$$) + this.extractPageSections(page, element.$$); + }.bind(this)); + }, + parseCompound: function (compound, compounddef) { log.verbose('Processing compound ' + compound.name); Object.keys(compounddef.$).forEach(function(prop) { @@ -328,6 +401,7 @@ module.exports = { if (compound.kind == 'group') { member.groupid = compound.id; + member.groupname = compound.name; } else if (compound.kind == 'file') { // add free members defined inside files in the default @@ -345,7 +419,7 @@ module.exports = { }.bind(this)); } - compound.proto = helpers.inline([compound.kind, ' ', markdown.link(compound.name, '#' + compound.refid, true)]); + compound.proto = helpers.inline([compound.kind, ' ', markdown.refLink(compound.name, compound.refid)]); // kind specific parsing switch (compound.kind) { @@ -364,9 +438,18 @@ module.exports = { // parse add all contained members to the root compound. break; + case 'page': + this.extractPageSections(compound, compounddef.$$); + break; + case 'namespace': case 'group': + if (compound.kind == 'group') { + compound.groupid = compound.id; + compound.groupname = compound.name; + } + // handle innerclass for groups and namespaces if (compounddef.innerclass) { compounddef.innerclass.forEach(function (innerclassdef) { @@ -404,7 +487,7 @@ module.exports = { parseIndex: function (root, index, options) { index.forEach(function (element) { - var doxygen, compound = root.find(element.name[0], true); + var doxygen, compound = root.find(element.$.refid, element.name[0], true); var xmlParser = new xml2js.Parser({ explicitChildren: true, preserveChildrenOrder: true, @@ -413,15 +496,15 @@ module.exports = { this.parseMembers(compound, element.$, element.member); - if (compound.kind !== 'page') { // && compound.kind !== 'file' + if (compound.kind !== 'file') { // && compound.kind !== 'file' log.verbose('Parsing ' + path.join(options.directory, compound.refid + '.xml')); doxygen = fs.readFileSync(path.join(options.directory, compound.refid + '.xml'), 'utf8'); xmlParser.parseString(doxygen, function (err, data) { if (err) { - log.verbose('warning - parse error for file' , path.join(options.directory, compound.refid + '.xml')) + log.verbose('warning - parse error for file: ' + path.join(options.directory, compound.refid + '.xml')) return; } - this.parseCompound(compound, data.doxygen.compounddef[0]); + this.parseCompound(compound, data.doxygen.compounddef[0]); }.bind(this)); } @@ -429,16 +512,17 @@ module.exports = { }, loadIndex: function (options, callback) { + this.parserOptions = options; log.verbose('Parsing ' + path.join(options.directory, 'index.xml')); fs.readFile(path.join(options.directory, 'index.xml'), 'utf8', function(err, data) { if (err) { - callback('Failed to load Doxygen XML: ' + err); + callback('Failed to load doxygen XML: ' + err); return; } var xmlParser = new xml2js.Parser(); xmlParser.parseString(data, function (err, result) { if (err) { - callback('Failed to parse Doxygen XML: ' + err); + callback('Failed to parse doxygen XML: ' + err); return; } this.root.kind = 'index'; @@ -446,5 +530,5 @@ module.exports = { callback(null, this.root); // TODO: return errors properly }.bind(this)); }.bind(this)); - } + }, }; diff --git a/src/templates.js b/src/templates.js index 58e01f49..cd011034 100644 --- a/src/templates.js +++ b/src/templates.js @@ -7,7 +7,7 @@ 'use strict'; var fs = require('fs'); -var log = require('winston'); +var log = require('./logger').getLogger(); var path = require('path'); var handlebars = require('handlebars'); // var tidyMarkdown = require('tidy-markdown'); @@ -42,6 +42,9 @@ module.exports = { case 'index': template = 'index'; break; + case 'page': + template = 'page' + break; case 'group': case 'namespace': if (Object.keys(compound.compounds).length === 1 @@ -52,6 +55,7 @@ module.exports = { break; case 'class': case 'struct': + case 'interface': template = 'class'; break; default: @@ -60,7 +64,11 @@ module.exports = { return undefined; } - return this.templates[template](compound).replace(/(\r\n|\r|\n){2,}/g, '$1\n'); + if (typeof this.templates[template] == "undefined") { + throw 'Template "' + template + '" not found in your templates directory.'; + } + + return this.templates[template](compound).replace(/(\r\n|\r|\n){3,}/g, '$1\n'); }, renderArray: function (compounds) { @@ -74,28 +82,17 @@ module.exports = { // Escape the code for a table cell. handlebars.registerHelper('cell', function(code) { - if (options.groups && code.indexOf('(#') !== -1) { - code = helpers.replaceGroupReflink(options.output, doxyparser.references, code); - } return code.replace(/\|/g, '\\|').replace(/\n/g, '
'); }); // Escape the code for a titles. handlebars.registerHelper('title', function(code) { - if (options.groups && code.indexOf('(#') !== -1) { - code = helpers.replaceGroupReflink(options.output, doxyparser.references, code); - } return code.replace(/\n/g, '
'); }); // Generate an anchor for internal links handlebars.registerHelper('anchor', function(name) { - if (options.anchors) { - return '{#' + name + '}'; - } - else { - return ''; - } + return helpers.getAnchor(name, options); }); - } + }, }; diff --git a/templates/cpp/class.md b/templates/cpp/class.md index 09c4aaf6..d0623177 100644 --- a/templates/cpp/class.md +++ b/templates/cpp/class.md @@ -1,4 +1,4 @@ -# {{kind}} `{{name}}` {{anchor refid}} +## {{kind}} `{{name}}` {{anchor refid}} {{#if basecompoundref}} ``` @@ -6,14 +6,14 @@ {{#each basecompoundref}} : {{prot}} {{name}} {{/each}} -``` +``` {{/if}} {{briefdescription}} {{detaileddescription}} -## Summary +### Summary Members | Descriptions --------------------------------|--------------------------------------------- @@ -21,7 +21,7 @@ {{/each}}{{#each filtered.members}}{{cell proto}} | {{cell summary}} {{/each}} -## Members +### Members {{#each filtered.compounds}} #### {{title proto}} {{anchor refid}} diff --git a/templates/cpp/page.md b/templates/cpp/page.md new file mode 100644 index 00000000..e4dacc8e --- /dev/null +++ b/templates/cpp/page.md @@ -0,0 +1,34 @@ +# {{kind}} `{{name}}` {{anchor refid}} + +{{briefdescription}} + +{{detaileddescription}} + +{{#if filtered.members}} + +## Summary + + Members | Descriptions +--------------------------------|--------------------------------------------- +{{#each filtered.members}}{{cell proto}} | {{cell summary}} +{{/each}}{{#each filtered.compounds}}{{cell proto}} | {{cell summary}} +{{/each}} + +## Members + +{{#each filtered.members}} +#### {{title proto}} {{anchor refid}} + +{{#if enumvalue}} + Values | Descriptions +--------------------------------|--------------------------------------------- +{{#each enumvalue}}{{cell name}} | {{cell summary}} +{{/each}} +{{/if}} + +{{briefdescription}} + +{{detaileddescription}} + +{{/each}} +{{/if}}