├── test ├── mocha.opts ├── data │ ├── simple.puml │ ├── car.coffeescript.coffee │ ├── car.puml │ ├── car.ruby.rb │ ├── car.typescript.ts │ ├── car.csharp.cs │ ├── car.java.java │ ├── car.php.php │ ├── car.ecmascript5.js │ ├── car.python.py │ ├── car.ecmascript6.js │ └── car.cpp.h ├── .eslintrc.json ├── unittests.js └── cli.js ├── logic.png ├── src ├── parser │ ├── Composition.js │ ├── Package.js │ ├── Extension.js │ ├── index.js │ ├── AbstractClass.js │ ├── InterfaceClass.js │ ├── Parameter.js │ ├── Connection.js │ ├── Method.js │ ├── Field.js │ ├── Namespace.js │ ├── UMLBlock.js │ ├── Class.js │ └── plantuml.pegjs ├── logger.js ├── templates │ ├── coffeescript.hbs │ ├── ruby.hbs │ ├── typescript.hbs │ ├── php.hbs │ ├── csharp.hbs │ ├── java.hbs │ ├── ecmascript5.hbs │ ├── python.hbs │ ├── ecmascript6.hbs │ └── cpp.hbs ├── Output.js ├── cli.js └── index.js ├── .eslintignore ├── examples ├── sample.js └── sample.puml ├── bin └── puml2code ├── .snyk ├── .github ├── ISSUE_TEMPLATE │ ├── grammar.md │ └── output.md ├── issue_template.md └── pull_request_template.md ├── .eslintrc.js ├── logic.puml ├── LICENSE ├── .gitignore ├── package.json ├── .circleci └── config.yml └── README.md /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --exclude test/data/*.js 2 | -------------------------------------------------------------------------------- /logic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupe/puml2code/HEAD/logic.png -------------------------------------------------------------------------------- /src/parser/Composition.js: -------------------------------------------------------------------------------- 1 | 2 | class Composition {} 3 | module.exports = Composition; 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | doc/ 3 | coverage/ 4 | src/parser/plantuml.js 5 | test/data 6 | -------------------------------------------------------------------------------- /test/data/simple.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | class Hep { 3 | +test(A a, B b) 4 | +Query find(A a) 5 | } 6 | EventEmitter -left-|> Hep 7 | 8 | @enduml 9 | -------------------------------------------------------------------------------- /examples/sample.js: -------------------------------------------------------------------------------- 1 | const PlantUmlToCode = require('../src'); 2 | 3 | 4 | const platnuml = PlantUmlToCode.fromFile('./examples/sample.puml'); 5 | platnuml.generate() 6 | .then(out => out.print()); 7 | -------------------------------------------------------------------------------- /bin/puml2code: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../src/cli')() 4 | .then(process.exit.bind(process)) 5 | .catch((error) => { 6 | console.error(error); 7 | process.exit(1); 8 | }); -------------------------------------------------------------------------------- /src/parser/Package.js: -------------------------------------------------------------------------------- 1 | class Package { 2 | constructor(namespaceName, fileLines) { 3 | this.namespaceName = namespaceName; 4 | this.fileLines = fileLines; 5 | } 6 | } 7 | module.exports = Package; 8 | -------------------------------------------------------------------------------- /src/parser/Extension.js: -------------------------------------------------------------------------------- 1 | class Extension { 2 | constructor(bIsLeft) { 3 | this.bIsLeft = bIsLeft; 4 | } 5 | 6 | isLeft() { 7 | return this.bIsLeft; 8 | } 9 | } 10 | module.exports = Extension; 11 | -------------------------------------------------------------------------------- /src/parser/index.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | const parser = require('./plantuml'); 3 | 4 | module.exports = data => Promise.try(() => parser.parse(data)); 5 | module.exports.SyntaxError = parser.SyntaxError; 6 | -------------------------------------------------------------------------------- /src/parser/AbstractClass.js: -------------------------------------------------------------------------------- 1 | const Class = require('./Class'); 2 | 3 | class AbstractClass extends Class { 4 | isAbstract() { // eslint-disable-line class-methods-use-this 5 | return true; 6 | } 7 | } 8 | module.exports = AbstractClass; 9 | -------------------------------------------------------------------------------- /src/logger.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('puml2code'); 2 | 3 | const nullLogger = new Proxy({}, { 4 | get: (m, level) => (line) => { // eslint-disable-line no-unused-vars 5 | debug(`[${level}] ${line}`); 6 | }, 7 | }); 8 | 9 | module.exports = nullLogger; 10 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.14.1 3 | ignore: {} 4 | # patches apply the minimum changes required to fix a vulnerability 5 | patch: 6 | SNYK-JS-LODASH-567746: 7 | - lodash: 8 | patched: '2020-05-01T01:18:59.017Z' 9 | -------------------------------------------------------------------------------- /src/parser/InterfaceClass.js: -------------------------------------------------------------------------------- 1 | const Class = require('./Class'); 2 | 3 | class InterfaceClass extends Class { 4 | constructor(...args) { 5 | super(...args); 6 | this.members.forEach((member) => { 7 | member.setInterface(); 8 | }); 9 | } 10 | 11 | isInterface() { // eslint-disable-line class-methods-use-this 12 | return true; 13 | } 14 | } 15 | module.exports = InterfaceClass; 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/grammar.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | - Type: Bug 3 | 4 | --------------------------------------------------------------- 5 | ## Bug 6 | 7 | **puml2code version** 8 | 9 | (`git describe --tags` / `puml2code -V`) 10 | 11 | **plantuml file that reproduce issue** 12 | 13 | ``` 14 | @startuml 15 | ... 16 | @enduml 17 | ``` 18 | **Actual Behavior** 19 | 20 | ```bash 21 | ...error print?... 22 | ``` -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/output.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | - Type: Bug 3 | 4 | --------------------------------------------------------------- 5 | ## Bug 6 | 7 | **puml2code version** 8 | 9 | (`git describe --tags` / `puml2code -V`) 10 | 11 | **Selected output language** 12 | 13 | ecmascript6 14 | 15 | **Expected output** 16 | ```js 17 | ... 18 | ``` 19 | 20 | **Actual output** 21 | ```js 22 | ... 23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /src/templates/coffeescript.hbs: -------------------------------------------------------------------------------- 1 | class {{getFullName}}{{#if getExtends}} extends {{#with getExtends}}{{getFullName}}{{/with}}{{/if}} 2 | {{#each getFields}} 3 | {{this.getName}}: undefined 4 | {{/each}} 5 | {{#each getMethods}} 6 | {{this.getName}}: {{#if this.getParameters}}({{#each this.getParameters}}{{#if @first}}{{else}},{{/if}}{{#if this.getName}}{{this.getName}}{{else}}param{{@index}}{{/if}}{{/each}}){{/if}} -> 7 | {{/each}} 8 | -------------------------------------------------------------------------------- /src/templates/ruby.hbs: -------------------------------------------------------------------------------- 1 | class {{getFullName}}{{#if getExtends}} < {{#with getExtends}}{{getFullName}}{{/with}}{{/if}} 2 | {{#each getFields}} 3 | @{{this.getName}} 4 | {{/each}} 5 | {{#each getMethods}} 6 | def {{this.getName}}{{#if this.getParameters}} ({{#each this.getParameters}}{{#if @first}}{{else}},{{/if}}{{#if this.getName}}{{this.getName}}{{else}}param{{@index}}{{/if}}{{/each}}){{/if}} 7 | return 8 | end 9 | {{/each}} 10 | end 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es6: true, 4 | node: true, 5 | }, 6 | extends: 'airbnb-base', 7 | globals: { 8 | Atomics: 'readonly', 9 | SharedArrayBuffer: 'readonly', 10 | }, 11 | parserOptions: { 12 | ecmaVersion: 2018, 13 | sourceType: 'module', 14 | }, 15 | rules: { 16 | "max-len": ["error", {"code": 120, "tabWidth": 4, "ignoreUrls": true}], 17 | "no-underscore-dangle": ["off"] 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/parser/Parameter.js: -------------------------------------------------------------------------------- 1 | class Parameter { 2 | constructor(returnType, memberName, defaultValue) { 3 | this.sReturnType = returnType; 4 | this.sParameterName = memberName; 5 | this.sDefaultValue = defaultValue; 6 | } 7 | 8 | getDefaultValue() { 9 | return this.sDefaultValue; 10 | } 11 | 12 | getReturnType() { 13 | return this.sReturnType; 14 | } 15 | 16 | getName() { 17 | return this.sParameterName; 18 | } 19 | } 20 | module.exports = Parameter; 21 | -------------------------------------------------------------------------------- /src/templates/typescript.hbs: -------------------------------------------------------------------------------- 1 | class {{getFullName}}{{#if getExtends}} extends {{#with getExtends}}{{getFullName}}{{/with}}{{/if}} { 2 | {{#each getFields}} 3 | private {{this.getName}} : {{this.getReturnType}}; 4 | {{/each}} 5 | {{#each getMethods}} 6 | {{this.getName}}{{#if this.getParameters}}({{#each this.getParameters}}{{#if @first}}{{else}},{{/if}}{{#if this.getName}}{{this.getName}}{{else}}param{{@index}}{{/if}}{{/each}}){{else}}(){{/if}} { 7 | return; 8 | } 9 | {{/each}} 10 | } -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "plugins": [ 6 | "mocha", 7 | "chai-expect" 8 | ], 9 | "rules": { 10 | "mocha/no-exclusive-tests": "error", 11 | "chai-expect/missing-assertion": "error", 12 | "chai-expect/terminating-properties": "error", 13 | "no-unused-expressions": "off", 14 | "prefer-arrow-callback": "off", 15 | "func-names": "off", 16 | "no-underscore-dangle": "off" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/parser/Connection.js: -------------------------------------------------------------------------------- 1 | 2 | class Connection { 3 | constructor(leftObject, connector, rightObject) { 4 | this.leftObject = leftObject; 5 | this.connector = connector; 6 | this.pNamespace = null; 7 | this.rightObject = rightObject; 8 | } 9 | 10 | setNamespace(namespace) { 11 | this.pNamespace = namespace; 12 | } 13 | 14 | getConnector() { 15 | return this.connector; 16 | } 17 | 18 | getNamespace() { 19 | return this.pNamespace; 20 | } 21 | } 22 | 23 | module.exports = Connection; 24 | -------------------------------------------------------------------------------- /src/templates/php.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/csharp.hbs: -------------------------------------------------------------------------------- 1 | {{#if isAbstract}}abstract {{/if}}class {{getFullName}}{{#if getExtends}} : {{#with getExtends}}{{getFullName}}{{/with}}{{/if}} { 2 | {{#each getFields}} 3 | private {{this.getReturnType}} {{this.getName}}; 4 | {{/each}} 5 | {{#each getMethods}} 6 | public {{this.getReturnType}} {{this.getName}}{{#if this.getParameters}}({{#each this.getParameters}}{{#if @first}}{{else}},{{/if}}{{this.getReturnType}} {{#if this.getName}}{{this.getName}}{{else}}param{{@index}}{{/if}}{{/each}}){{else}}(){{/if}} { 7 | {{#if this.needsReturnStatement}} 8 | return null; 9 | {{/if}} 10 | } 11 | {{/each}} 12 | } 13 | -------------------------------------------------------------------------------- /src/templates/java.hbs: -------------------------------------------------------------------------------- 1 | {{#if isAbstract}}abstract {{/if}}class {{getFullName}}{{#if getExtends}} extends {{#with getExtends}}{{getFullName}}{{/with}}{{/if}} { 2 | {{#each getFields}} 3 | private {{this.getReturnType}} {{this.getName}}; 4 | {{/each}} 5 | {{#each getMethods}} 6 | public {{this.getReturnType}} {{this.getName}}{{#if this.getParameters}}({{#each this.getParameters}}{{#if @first}}{{else}},{{/if}}{{this.getReturnType}} {{#if this.getName}}{{this.getName}}{{else}}param{{@index}}{{/if}}{{/each}}){{else}}(){{/if}} { 7 | {{#if this.needsReturnStatement}} 8 | return null; 9 | {{/if}} 10 | } 11 | {{/each}} 12 | } 13 | -------------------------------------------------------------------------------- /test/data/car.coffeescript.coffee: -------------------------------------------------------------------------------- 1 | class Vehicle 2 | getType: -> 3 | 4 | class Car extends Vehicle 5 | model: undefined 6 | make: undefined 7 | year: undefined 8 | setModel: (model) -> 9 | setMake: (make) -> 10 | setYear: (param0) -> 11 | getModel: -> 12 | getMake: -> 13 | getYear: -> 14 | 15 | class NamesInThings 16 | field: undefined 17 | field1: undefined 18 | _some_private: undefined 19 | field_2: undefined 20 | member: -> 21 | member2: -> 22 | member3: -> 23 | member_s: -> 24 | 25 | class Toyota extends Car 26 | 27 | class Honda extends Car 28 | 29 | class Ford extends Car 30 | 31 | class Hyundai extends Car 32 | 33 | -------------------------------------------------------------------------------- /examples/sample.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | header 4 | DRAFT 5 | Authors: xxx 6 | Version: 0.1 7 | endheader 8 | 9 | hide empty members 10 | 11 | title Allocation queue - Class Diagram 12 | 13 | 14 | interface Type { 15 | +URI getIdentifier() 16 | } 17 | 18 | class Scheduler { 19 | +Scheduler(Queue queue, Resources resource) 20 | -{abstract}Type getType() 21 | -bool setTypes(List value=[]) 22 | -int privFunc(Queue q) 23 | -Object *iter() 24 | -async privAsyncFunc(Queue queue=[]) 25 | +{abstract} async iterator(Object sort={}) 26 | -Queue queue 27 | -Resources resoures 28 | } 29 | 30 | 31 | class EventEmitter 32 | Scheduler -up-|> EventEmitter 33 | 34 | 35 | @enduml 36 | -------------------------------------------------------------------------------- /logic.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | start 4 | 5 | :parse cli; 6 | note right 7 | cli parser based on commander 8 | end note 9 | 10 | if (args.input) then ("stdin") 11 | :read stdin stream; 12 | else if (exists) 13 | :read file; 14 | else (no) 15 | :exit; 16 | end 17 | endif 18 | 19 | :parse plantuml string; 20 | note right 21 | parser based on PEG.js 22 | end note 23 | 24 | if (select template) then (exists) 25 | :select code template; 26 | else (not exists) 27 | :exit; 28 | end 29 | endif 30 | 31 | :fill code template; 32 | note right 33 | handlebars.js as a template engine 34 | end note 35 | 36 | if (args.output) then (given) 37 | :generate source code files; 38 | else 39 | :print to console; 40 | endif 41 | 42 | stop 43 | 44 | @enduml -------------------------------------------------------------------------------- /src/parser/Method.js: -------------------------------------------------------------------------------- 1 | const Field = require('./Field'); 2 | 3 | 4 | class Method extends Field { 5 | constructor(accessType, returnType, fieldName, aParameters) { 6 | super(accessType, returnType, fieldName); 7 | this.aParameters = aParameters; 8 | this.sReturnType = returnType; 9 | } 10 | 11 | getReturnType() { 12 | return this.sReturnType; 13 | } 14 | 15 | needsReturnStatement() { 16 | return ['void', 'async'].indexOf(this.sReturnType) === -1; 17 | } 18 | 19 | isAsync() { 20 | return this.sReturnType === 'async'; 21 | } 22 | 23 | isLambda() { 24 | return this.sFieldName.startsWith('*'); 25 | } 26 | 27 | getParameters() { 28 | return this.aParameters; 29 | } 30 | } 31 | 32 | module.exports = Method; 33 | -------------------------------------------------------------------------------- /src/templates/ecmascript5.hbs: -------------------------------------------------------------------------------- 1 | {{#if getExtends}} 2 | function {{getFullName}}() { 3 | {{#with getExtends}}{{getFullName}}{{/with}}.prototype.constructor.apply(this, arguments); 4 | } 5 | {{getFullName}}.prototype = Object.create({{#with getExtends}}{{getFullName}}{{/with}}.prototype); 6 | {{getFullName}}.prototype.constructor = {{getFullName}}; 7 | {{else}} 8 | function {{getFullName}}() {} 9 | {{/if}} 10 | {{#each getFields}} 11 | {{#call ../this ../getFullName}}{{/call}}.prototype.{{getName}} = undefined; 12 | {{/each}} 13 | {{#each getMethods}} 14 | {{#call ../this ../getFullName}}{{/call}}.prototype.{{getName}} = function ({{#if getParameters}}{{#each getParameters}}{{#if @first}}{{else}}, {{/if}}{{#if getName}}{{getName}}{{else}}param{{@index}}{{/if}}{{/each}}{{/if}}) {}; 15 | {{/each}} 16 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | Note: This is just a template, so feel free to use/remove the unnecessary things 2 | 3 | ### Description 4 | 5 | - Type: Bug | Enhancement | Question 6 | - Related Issue: `#abc` 7 | 8 | --------------------------------------------------------------- 9 | ## Bug 10 | 11 | **puml2code version** 12 | 13 | (`git describe --tags` / `puml2code -V`) 14 | 15 | **Expected Behavior** 16 | 17 | **Actual Behavior** 18 | 19 | **Steps to Reproduce** 20 | 21 | ---------------------------------------------------------------- 22 | ## Enhancement 23 | 24 | **Reason to enhance/problem with existing solution** 25 | 26 | **Suggested enhancement** 27 | 28 | **Pros** 29 | 30 | **Cons** 31 | 32 | ----------------------------------------------------------------- 33 | 34 | ## Question 35 | 36 | **How to?** 37 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Status 2 | **READY/IN DEVELOPMENT/HOLD** 3 | 4 | ## Migrations 5 | YES | NO 6 | 7 | ## Description 8 | A few sentences describing the overall goals of the pull request's commits. 9 | 10 | ## Related PRs 11 | List related PRs against other branches: 12 | 13 | branch | PR 14 | ------ | ------ 15 | other_pr_production | [link]() 16 | other_pr_master | [link]() 17 | 18 | 19 | ## Todos 20 | - [ ] Tests 21 | - [ ] Documentation 22 | 23 | 24 | ## Deploy Notes 25 | Notes regarding deployment the contained body of work. These should note any 26 | db migrations, etc. 27 | 28 | ## Steps to Test or Reproduce 29 | Outline the steps to test or reproduce the PR here. 30 | 31 | ```sh 32 | ... 33 | ``` 34 | 35 | 1. grunt 36 | 37 | ## Impacted Areas in Application 38 | List components, applications or use-cases that this PR will affect: 39 | 40 | * 41 | -------------------------------------------------------------------------------- /test/data/car.puml: -------------------------------------------------------------------------------- 1 | 2 | @startuml 3 | 4 | hide empty members 5 | 6 | ' This is a comment line 7 | 8 | interface Vehicle { 9 | + String getType() 10 | } 11 | 12 | abstract Car { 13 | + void setModel(String model='lada') 14 | + void setMake(String make) 15 | + void setYear(Number) 16 | + String getModel() 17 | + String getMake() 18 | + Number getYear() 19 | - String model 20 | - String make 21 | - Number year 22 | } 23 | 24 | class NamesInThings { 25 | + String field 26 | + String1 field1 27 | - String _some_private 28 | - String_2 field_2 29 | + void member() 30 | - String1 member2() 31 | # void member3() 32 | - String2 member_s() 33 | } 34 | 35 | class Toyota 36 | class Honda 37 | class Ford 38 | class Hyundai 39 | 40 | Car -up-|> Vehicle 41 | Toyota -left-|> Car 42 | Honda -right-|> Car 43 | Ford -up-|> Car 44 | Hyundai -up-|> Car 45 | 46 | @enduml -------------------------------------------------------------------------------- /test/data/car.ruby.rb: -------------------------------------------------------------------------------- 1 | class Vehicle 2 | def getType 3 | return 4 | end 5 | end 6 | 7 | class Car < Vehicle 8 | @model 9 | @make 10 | @year 11 | def setModel (model) 12 | return 13 | end 14 | def setMake (make) 15 | return 16 | end 17 | def setYear (param0) 18 | return 19 | end 20 | def getModel 21 | return 22 | end 23 | def getMake 24 | return 25 | end 26 | def getYear 27 | return 28 | end 29 | end 30 | 31 | class NamesInThings 32 | @field 33 | @field1 34 | @_some_private 35 | @field_2 36 | def member 37 | return 38 | end 39 | def member2 40 | return 41 | end 42 | def member3 43 | return 44 | end 45 | def member_s 46 | return 47 | end 48 | end 49 | 50 | class Toyota < Car 51 | end 52 | 53 | class Honda < Car 54 | end 55 | 56 | class Ford < Car 57 | end 58 | 59 | class Hyundai < Car 60 | end 61 | 62 | -------------------------------------------------------------------------------- /test/data/car.typescript.ts: -------------------------------------------------------------------------------- 1 | class Vehicle { 2 | getType() { 3 | return; 4 | } 5 | } 6 | class Car extends Vehicle { 7 | private model : String; 8 | private make : String; 9 | private year : Number; 10 | setModel(model) { 11 | return; 12 | } 13 | setMake(make) { 14 | return; 15 | } 16 | setYear(param0) { 17 | return; 18 | } 19 | getModel() { 20 | return; 21 | } 22 | getMake() { 23 | return; 24 | } 25 | getYear() { 26 | return; 27 | } 28 | } 29 | class NamesInThings { 30 | private field : String; 31 | private field1 : String1; 32 | private _some_private : String; 33 | private field_2 : String_2; 34 | member() { 35 | return; 36 | } 37 | member2() { 38 | return; 39 | } 40 | member3() { 41 | return; 42 | } 43 | member_s() { 44 | return; 45 | } 46 | } 47 | class Toyota extends Car { 48 | } 49 | class Honda extends Car { 50 | } 51 | class Ford extends Car { 52 | } 53 | class Hyundai extends Car { 54 | } 55 | -------------------------------------------------------------------------------- /test/data/car.csharp.cs: -------------------------------------------------------------------------------- 1 | class Vehicle { 2 | public String getType() { 3 | return null; 4 | } 5 | } 6 | 7 | abstract class Car : Vehicle { 8 | private String model; 9 | private String make; 10 | private Number year; 11 | public void setModel(String model) { 12 | } 13 | public void setMake(String make) { 14 | } 15 | public void setYear(Number param0) { 16 | } 17 | public String getModel() { 18 | return null; 19 | } 20 | public String getMake() { 21 | return null; 22 | } 23 | public Number getYear() { 24 | return null; 25 | } 26 | } 27 | 28 | class NamesInThings { 29 | private String field; 30 | private String1 field1; 31 | private String _some_private; 32 | private String_2 field_2; 33 | public void member() { 34 | } 35 | public String1 member2() { 36 | return null; 37 | } 38 | public void member3() { 39 | } 40 | public String2 member_s() { 41 | return null; 42 | } 43 | } 44 | 45 | class Toyota : Car { 46 | } 47 | 48 | class Honda : Car { 49 | } 50 | 51 | class Ford : Car { 52 | } 53 | 54 | class Hyundai : Car { 55 | } 56 | 57 | -------------------------------------------------------------------------------- /test/data/car.java.java: -------------------------------------------------------------------------------- 1 | class Vehicle { 2 | public String getType() { 3 | return null; 4 | } 5 | } 6 | 7 | abstract class Car extends Vehicle { 8 | private String model; 9 | private String make; 10 | private Number year; 11 | public void setModel(String model) { 12 | } 13 | public void setMake(String make) { 14 | } 15 | public void setYear(Number param0) { 16 | } 17 | public String getModel() { 18 | return null; 19 | } 20 | public String getMake() { 21 | return null; 22 | } 23 | public Number getYear() { 24 | return null; 25 | } 26 | } 27 | 28 | class NamesInThings { 29 | private String field; 30 | private String1 field1; 31 | private String _some_private; 32 | private String_2 field_2; 33 | public void member() { 34 | } 35 | public String1 member2() { 36 | return null; 37 | } 38 | public void member3() { 39 | } 40 | public String2 member_s() { 41 | return null; 42 | } 43 | } 44 | 45 | class Toyota extends Car { 46 | } 47 | 48 | class Honda extends Car { 49 | } 50 | 51 | class Ford extends Car { 52 | } 53 | 54 | class Hyundai extends Car { 55 | } 56 | 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jussi Vatjus-Anttila 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/data/car.php.php: -------------------------------------------------------------------------------- 1 | 8 | 30 | 48 | 52 | 56 | 60 | 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | browser-test.js 61 | .idea 62 | junit 63 | plantuml.js 64 | -------------------------------------------------------------------------------- /src/Output.js: -------------------------------------------------------------------------------- 1 | // native modules 2 | const { join } = require('path'); 3 | const { writeFile, mkdir } = require('fs'); 4 | // 3rd party modules 5 | const Promise = require('bluebird'); 6 | const _ = require('lodash'); 7 | 8 | 9 | class Output { 10 | constructor(files, { logger }) { 11 | this.logger = logger; 12 | this._files = files; 13 | } 14 | 15 | print(printer = console.log) { // eslint-disable-line no-console 16 | _.each(this._files, (content, file) => { 17 | this.logger.info(`${file}:`); 18 | printer(`${content}`); 19 | }); 20 | } 21 | 22 | static mkdir(path) { 23 | return new Promise( 24 | (resolve, reject) => mkdir(path, 25 | err => ((err && err.code !== 'EEXIST') ? reject(err) : resolve())), 26 | ); 27 | } 28 | 29 | async save(path) { 30 | await Output.mkdir(path); // ensure that folder exists or create it 31 | this.logger.debug(`Write files ${_.map(this._files, (content, file) => file)} to path: ${path}`); 32 | const writer = (content, file) => Promise.fromCallback(cb => writeFile(join(path, file), content, cb)); 33 | const pendings = _.map(this._files, writer); 34 | return Promise.all(pendings); 35 | } 36 | } 37 | 38 | module.exports = Output; 39 | -------------------------------------------------------------------------------- /src/parser/Field.js: -------------------------------------------------------------------------------- 1 | class Field { 2 | constructor(accessType, returnType, fieldName, abstract) { 3 | this.sAccessType = accessType; 4 | this.sReturnType = returnType; 5 | this.sFieldName = fieldName; 6 | this.bInterface = false; 7 | this.bAbstract = !!abstract; 8 | this.isConstructor = undefined; 9 | } 10 | 11 | isPrivate() { 12 | return this.getAccessType() === '-'; 13 | } 14 | 15 | isProtected() { 16 | return this.getAccessType() === '#'; 17 | } 18 | 19 | isPublic() { 20 | return this.getAccessType() === '+'; 21 | } 22 | 23 | setIsConstructor(isConstructor) { 24 | this.isConstructor = isConstructor; 25 | } 26 | 27 | isNotConstructor() { 28 | return !this.isConstructor(this.getName()); 29 | } 30 | 31 | setInterface() { 32 | this.bInterface = true; 33 | } 34 | 35 | isAbstract() { 36 | return this.bAbstract; 37 | } 38 | 39 | isInterface() { 40 | return this.bInterface; 41 | } 42 | 43 | getAccessType() { 44 | return this.sAccessType; 45 | } 46 | 47 | getReturnType() { 48 | return this.sReturnType; 49 | } 50 | 51 | getName() { 52 | return this.sFieldName; 53 | } 54 | 55 | getParameters() { // eslint-disable-line class-methods-use-this 56 | return []; 57 | } 58 | } 59 | module.exports = Field; 60 | -------------------------------------------------------------------------------- /src/templates/python.hbs: -------------------------------------------------------------------------------- 1 | {{#if isInterface}}import abc 2 | {{/if}} 3 | """ 4 | @package {{#if isAbstract}}Abstract {{/if}}{{#if isInterface}}Interface {{/if}}{{getFullName}} 5 | """ 6 | class {{getFullName}}({{#if isInterface}}abs.ABC{{/if}}{{#if getExtends}}{{#with getExtends}}{{getFullName}}{{/with}}{{/if}}): 7 | def __init__(self{{#each getConstructorArgs}}, {{#if getName}}{{getName}}{{else}}param{{@index}}{{/if}}{{/each}}): 8 | """ 9 | Constructor for {{getFullName}}{{#each getFields}} 10 | :param {{getName}}: TBD{{/each}} 11 | """{{#if hasFields}}{{#each getFields}} 12 | self.{{this.getName}} = None{{/each}}{{else}} 13 | pass{{/if}} 14 | 15 | {{#each getMethods}}{{#if isNotConstructor}}{{#if isInterface}} 16 | @abc.abstractmethod 17 | {{/if}} 18 | def {{#if this.isPrivate}}_{{/if}}{{this.getName}}{{#if this.getParameters}}(self, {{#each this.getParameters}}{{#if @first}}{{else}}, {{/if}}{{#if this.getName}}{{this.getName}}{{else}}param{{@index}}{{/if}}{{/each}}){{else}}(self){{/if}}: 19 | """ 20 | {{#each getParameters}} :param {{getName}}: TBD 21 | {{/each}}{{#if needsReturnStatement}} :return: {{getReturnType}}{{/if}} 22 | """ 23 | {{#if this.needsReturnStatement}} 24 | return null 25 | {{else}} 26 | pass 27 | {{/if}}{{/if}}{{/each}} -------------------------------------------------------------------------------- /src/parser/Namespace.js: -------------------------------------------------------------------------------- 1 | 2 | const Class = require('./Class'); 3 | const AbstractClass = require('./AbstractClass'); 4 | const InterfaceClass = require('./InterfaceClass'); 5 | const Connection = require('./Connection'); 6 | 7 | class Namespace { 8 | constructor(namespaceName, fileLines) { 9 | this.namespaceName = namespaceName; 10 | this.aItems = fileLines; 11 | this.nNamespace = null; 12 | this.init(); 13 | } 14 | 15 | getName() { 16 | return this.namespaceName; 17 | } 18 | 19 | getItems() { 20 | return this.aItems; 21 | } 22 | 23 | setNamespace(namespace) { 24 | this.nNamespace = namespace; 25 | } 26 | 27 | getNamespace() { 28 | return this.nNamespace; 29 | } 30 | 31 | getFullName() { 32 | const aFull = [this.getName()]; 33 | let nSpace = this.getNamespace(); 34 | while (nSpace !== null) { 35 | aFull.unshift(nSpace.getName()); 36 | nSpace = nSpace.getNamespace(); 37 | } 38 | return aFull.join('.'); 39 | } 40 | 41 | init() { 42 | for (let i = 0, { length } = this.aItems; i < length; i += 1) { 43 | if (this.aItems[i] instanceof Namespace) { 44 | this.aItems[i].setNamespace(this); 45 | } else if (this.aItems[i] instanceof Class || this.aItems[i] instanceof AbstractClass 46 | || this.aItems[i] instanceof InterfaceClass) { 47 | this.aItems[i].setNamespace(this); 48 | } else if (this.aItems[i] instanceof Connection) { 49 | this.aItems[i].setNamespace(this); 50 | } 51 | } 52 | } 53 | } 54 | module.exports = Namespace; 55 | -------------------------------------------------------------------------------- /test/unittests.js: -------------------------------------------------------------------------------- 1 | // native modules 2 | const path = require('path'); 3 | // 3rd party modules 4 | const chai = require('chai'); 5 | const { spy } = require('sinon'); 6 | const chaiAsPromised = require('chai-as-promised'); 7 | 8 | 9 | const { expect } = chai; 10 | chai.use(chaiAsPromised); 11 | // modules under test 12 | const Puml = require('../src'); 13 | 14 | 15 | describe('pumlgen', () => { 16 | it('ok', () => { 17 | const puml = new Puml(); 18 | expect(puml).is.ok; 19 | expect(Puml.fromFile).to.be.an('function'); 20 | expect(Puml.fromString).to.be.an('function'); 21 | }); 22 | 23 | describe('_toCode', () => { 24 | let puml; 25 | beforeEach(() => { 26 | puml = new Puml(); 27 | }); 28 | it('unknown', () => { 29 | expect(puml._toCode({}, 'unknown')).to.eventually.rejectedWith(TypeError); 30 | }); 31 | }); 32 | 33 | describe('from', () => { 34 | it('String with invalid parameter', () => { 35 | expect(() => Puml.fromString({})).to.throw(); 36 | }); 37 | it('String', async () => { 38 | const puml = Puml.fromString('@startuml\nclass Hep {\n}\n@enduml\n'); 39 | const output = await puml.generate('ecmascript6'); 40 | expect(output).to.be.ok; 41 | const log = spy(); 42 | output.print(log); 43 | expect(log.calledOnce).to.be.true; 44 | }); 45 | it('File', async () => { 46 | const puml = Puml.fromFile(path.join(__dirname, './data/simple.puml')); 47 | const output = await puml.generate('ecmascript6'); 48 | expect(output).to.be.ok; 49 | const log = spy(); 50 | output.print(log); 51 | expect(log.calledOnce).to.be.true; 52 | }); 53 | it('File not found', () => { 54 | expect(() => Puml.fromFile('not-exists.puml')).to.be.throw; 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/data/car.ecmascript5.js: -------------------------------------------------------------------------------- 1 | function Vehicle() {} 2 | Vehicle.prototype.getType = function () {}; 3 | 4 | function Car() { 5 | Vehicle.prototype.constructor.apply(this, arguments); 6 | } 7 | Car.prototype = Object.create(Vehicle.prototype); 8 | Car.prototype.constructor = Car; 9 | Car.prototype.model = undefined; 10 | Car.prototype.make = undefined; 11 | Car.prototype.year = undefined; 12 | Car.prototype.setModel = function (model) {}; 13 | Car.prototype.setMake = function (make) {}; 14 | Car.prototype.setYear = function (param0) {}; 15 | Car.prototype.getModel = function () {}; 16 | Car.prototype.getMake = function () {}; 17 | Car.prototype.getYear = function () {}; 18 | 19 | function NamesInThings() {} 20 | NamesInThings.prototype.field = undefined; 21 | NamesInThings.prototype.field1 = undefined; 22 | NamesInThings.prototype._some_private = undefined; 23 | NamesInThings.prototype.field_2 = undefined; 24 | NamesInThings.prototype.member = function () {}; 25 | NamesInThings.prototype.member2 = function () {}; 26 | NamesInThings.prototype.member3 = function () {}; 27 | NamesInThings.prototype.member_s = function () {}; 28 | 29 | function Toyota() { 30 | Car.prototype.constructor.apply(this, arguments); 31 | } 32 | Toyota.prototype = Object.create(Car.prototype); 33 | Toyota.prototype.constructor = Toyota; 34 | 35 | function Honda() { 36 | Car.prototype.constructor.apply(this, arguments); 37 | } 38 | Honda.prototype = Object.create(Car.prototype); 39 | Honda.prototype.constructor = Honda; 40 | 41 | function Ford() { 42 | Car.prototype.constructor.apply(this, arguments); 43 | } 44 | Ford.prototype = Object.create(Car.prototype); 45 | Ford.prototype.constructor = Ford; 46 | 47 | function Hyundai() { 48 | Car.prototype.constructor.apply(this, arguments); 49 | } 50 | Hyundai.prototype = Object.create(Car.prototype); 51 | Hyundai.prototype.constructor = Hyundai; 52 | 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puml2code", 3 | "version": "0.8.0", 4 | "description": "PlantUML to code generator", 5 | "main": "index.js", 6 | "bin": { 7 | "puml2code": "./bin/puml2code" 8 | }, 9 | "scripts": { 10 | "cli": "node src/cli.js", 11 | "test": "MOCHA_FILE=junit/test-results.xml istanbul cover -- _mocha --recursive -R mocha-circleci-reporter", 12 | "lint": "eslint .", 13 | "lint-fix": "eslint . --fix", 14 | "install": "node node_modules/pegjs/bin/pegjs src/parser/plantuml.pegjs", 15 | "snyk-protect": "snyk protect", 16 | "prepare": "npm run snyk-protect" 17 | }, 18 | "engines": { 19 | "node": ">=10.0.0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/jupe/puml2code.git" 24 | }, 25 | "keywords": [ 26 | "plantuml", 27 | "puml", 28 | "code", 29 | "pegjs", 30 | "generator", 31 | "uml", 32 | "class", 33 | "diagram" 34 | ], 35 | "author": "Jussi Vatjus-Anttila", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/jupe/puml2code/issues" 39 | }, 40 | "homepage": "https://github.com/jupe/puml2code#readme", 41 | "dependencies": { 42 | "bluebird": "^3.7.2", 43 | "camelcase": "^5.2.0", 44 | "commander": "^4.1.1", 45 | "debug": "^4.3.4", 46 | "handlebars": "^4.1.1", 47 | "lodash": "^4.17.21", 48 | "pegjs": "^0.10.0", 49 | "snyk": "^1.518.0" 50 | }, 51 | "devDependencies": { 52 | "chai": "^4.2.0", 53 | "chai-as-promised": "^7.1.1", 54 | "eslint": "^5.15.3", 55 | "eslint-config-airbnb-base": "^13.1.0", 56 | "eslint-plugin-chai-expect": "^2.0.1", 57 | "eslint-plugin-import": "^2.16.0", 58 | "eslint-plugin-mocha": "^5.3.0", 59 | "istanbul": "^1.1.0-alpha.1", 60 | "mocha-circleci-reporter": "0.0.3", 61 | "sinon": "^9.0.0" 62 | }, 63 | "snyk": true 64 | } 65 | -------------------------------------------------------------------------------- /test/cli.js: -------------------------------------------------------------------------------- 1 | // native modules 2 | const { readFileSync } = require('fs'); 3 | // 3rd party modules 4 | const chai = require('chai'); 5 | const { stub } = require('sinon'); 6 | const chaiAsPromised = require('chai-as-promised'); 7 | // module under test 8 | const cli = require('../src/cli'); 9 | const { languages, getExtension } = require('../src'); 10 | // list of files to use for test 11 | const inputPumlList = ['./test/data/car']; 12 | 13 | const { expect } = chai; 14 | chai.use(chaiAsPromised); 15 | 16 | 17 | describe('cli', () => { 18 | let exit; 19 | beforeEach(() => { 20 | exit = stub(process, 'exit'); 21 | }); 22 | afterEach(() => { 23 | exit.restore(); 24 | }); 25 | it('version', async () => { 26 | process.exit.callsFake(() => { 27 | throw new Error('ok'); 28 | }); 29 | await cli(['node', 'puml2code', '-V']).catch(() => {}); 30 | expect(process.exit.calledOnceWith(0)).to.be.true; 31 | }); 32 | it('help', async () => { 33 | process.exit.callsFake(() => { 34 | throw new Error('ok'); 35 | }); 36 | await cli(['node', 'puml2code', '-h']).catch(() => {}); 37 | expect(process.exit.calledOnceWith(0)).to.be.true; 38 | }); 39 | it('invalid args', async () => { 40 | process.exit.callsFake(() => { 41 | throw new Error('ok'); 42 | }); 43 | await cli(['node', 'puml2code', '-a']).catch(() => {}); 44 | expect(process.exit.calledOnceWith(1)).to.be.true; 45 | }); 46 | inputPumlList.forEach((input) => { 47 | describe(input, () => { 48 | languages.forEach((lang) => { 49 | it(`${lang}`, async () => { 50 | let stdout = ''; 51 | const printer = (data) => { stdout += `${data}\n`; }; 52 | const shoulFile = `${input}.${lang}.${getExtension(lang)}`; 53 | const retcode = await cli(['node', 'puml2code', '-l', lang, '-i', `${input}.puml`], printer); 54 | expect(stdout).to.be.equal(readFileSync(shoulFile).toString()); 55 | expect(retcode).to.be.equal(0); 56 | }); 57 | }); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/templates/ecmascript6.hbs: -------------------------------------------------------------------------------- 1 | {{#if getNativeModules}}// native modules 2 | {{#each getNativeModules}}const {{this}} = require('{{this}}');{{/each}} 3 | {{/if}}{{#if get3rdPartyModules}}// 3rd party modules 4 | {{#each get3rdPartyModules}}const {{this}} = require('{{this}}'); 5 | {{/each}} 6 | {{/if}}{{#if getAppModules}}// application modules 7 | {{#each getAppModules}}const {{this}} = require('{{this}}');{{/each}} 8 | {{/if}} 9 | 10 | /** 11 | * {{#if isInterface}}Interface {{/if}}Class {{getFullName}}{{#if getNote}} 12 | * {{getNote}}{{/if}}{{#if isInterface}} 13 | * @interface{{/if}} 14 | */ 15 | class {{getFullName}}{{#if getExtends}} extends {{#with getExtends}}{{getFullName}}{{/with}}{{/if}} { 16 | /** 17 | * Constructor for {{getFullName}}{{#if getNote}} 18 | * {{getNote}}{{/if}}{{#each getConstructorArgs}} 19 | * @param { {{getReturnType}} } {{getName}} TBD{{/each}}{{#if isPrivate}} 20 | * @private{{/if}} 21 | */ 22 | constructor({{#each getConstructorArgs}}{{#if @first}}{{else}}, {{/if}}{{#if getName}}{{getName}}{{else}}param{{@index}}{{/if}}{{/each}}) { 23 | {{#if getExtends}} super(); 24 | {{/if}}{{#each getFields}} this.{{getName}} = undefined; 25 | {{/each}} 26 | }{{#each getMethods}}{{#if isNotConstructor}} 27 | 28 | /**{{#if getNote}} 29 | * {{getNote}}{{/if}}{{#each getParameters}} 30 | * @param { {{SafeString this getReturnType}} } {{getName}}{{#if getDefaultValue}}={{SafeString this getDefaultValue}}{{/if}} TBD{{/each}}{{#if needsReturnStatement}} 31 | * @return { {{getReturnType}} }{{/if}}{{#if isPrivate}} 32 | * @private{{/if}} 33 | */ 34 | {{#if isAsync}}async {{/if}}{{getName}}({{#if getParameters}}{{#each getParameters}}{{#if @first}}{{else}}, {{/if}}{{#if getName}}{{getName}}{{#if getDefaultValue}}={{SafeString this getDefaultValue}}{{/if}}{{else}}param{{@index}}{{/if}}{{/each}}{{/if}}) { 35 | {{#if isInterface}}throw new Error('Not implemented');{{else}}{{#if this.needsReturnStatement}}return {{getReturnType}};{{else}}// TBD{{/if}}{{/if}}{{/if}} 36 | }{{/each}} 37 | } 38 | 39 | module.exports = {{getFullName}}; 40 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | // 3rd party modules 2 | const _ = require('lodash'); 3 | const program = require('commander'); 4 | // application modules 5 | const Puml = require('./'); 6 | const logger = require('./logger'); 7 | 8 | const options = new RegExp(`^(${_.reduce(Puml.languages, (acc, ext) => `${acc}${ext}|`, '')})$`, 'i'); 9 | const parseArgs = argv => program 10 | .version('0.1.0') 11 | .option('-i, --input [file]', 'input .puml file, or "stdin"') 12 | .option('-l, --lang [lang]', 'Optional output source code language', options, 'ecmascript6') 13 | .option('-o, --out [path]', 'Output path. When not given output is printed to console.') 14 | .on('--help', () => { 15 | const print = console.log; // eslint-disable-line no-console 16 | print(''); 17 | print(`Supported languages: ${Puml.languages.join(', ')}`); 18 | print(''); 19 | print('Examples:'); 20 | print(' $ puml2code -i input.puml -l ecmascript6'); 21 | print(' $ puml2code -h'); 22 | print('Use DEBUG=puml2code env variable to get traces. Example:'); 23 | print(' $ DEBUG=puml2code puml2code -i input.puml'); 24 | }) 25 | .parse(argv); 26 | 27 | const fromStdin = () => { 28 | process.stdin.resume(); 29 | process.stdin.setEncoding('utf8'); 30 | return Promise.resolve(new Puml(process.stdin)); 31 | }; 32 | const fromFile = input => Promise.resolve(Puml.fromFile(input)); 33 | 34 | 35 | const getSource = (args) => { 36 | if (!args.input) { 37 | console.error('Error: input option is required'); // eslint-disable-line no-console 38 | args.help(); 39 | } 40 | if (args.input !== 'stdin') { 41 | logger.debug(`Reading file: ${args.input}`); // eslint-disable-line no-console 42 | return fromFile(args.input); 43 | } 44 | console.log('Reading puml from stdin..'); // eslint-disable-line no-console 45 | return fromStdin(); 46 | }; 47 | 48 | const execute = async (argv = process.argv, printer = console.log) => { // eslint-disable-line no-console 49 | let args = { removeAllListeners: () => {} }; 50 | try { 51 | args = parseArgs(argv); 52 | const puml = await getSource(args); 53 | const output = await puml.generate(args.lang); 54 | if (args.out) { 55 | await output.save(args.out); 56 | } else { 57 | output.print(printer); 58 | } 59 | logger.debug('ready'); 60 | args.removeAllListeners(); 61 | return 0; 62 | } catch (error) { 63 | logger.error(error); 64 | args.removeAllListeners(); 65 | throw error; 66 | } 67 | }; 68 | module.exports = execute; 69 | module.exports.parseArgs = parseArgs; 70 | 71 | if (require.main === module) { 72 | execute(); 73 | } 74 | -------------------------------------------------------------------------------- /test/data/car.python.py: -------------------------------------------------------------------------------- 1 | import abc 2 | """ 3 | @package Interface Vehicle 4 | """ 5 | class Vehicle(abs.ABC): 6 | def __init__(self): 7 | """ 8 | Constructor for Vehicle 9 | """ 10 | pass 11 | 12 | 13 | @abc.abstractmethod 14 | def getType(self): 15 | """ 16 | :return: String 17 | """ 18 | return null 19 | 20 | """ 21 | @package Abstract Car 22 | """ 23 | class Car(Vehicle): 24 | def __init__(self): 25 | """ 26 | Constructor for Car 27 | :param model: TBD 28 | :param make: TBD 29 | :param year: TBD 30 | """ 31 | self.model = None 32 | self.make = None 33 | self.year = None 34 | 35 | def setModel(self, model): 36 | """ 37 | :param model: TBD 38 | 39 | """ 40 | pass 41 | def setMake(self, make): 42 | """ 43 | :param make: TBD 44 | 45 | """ 46 | pass 47 | def setYear(self, param0): 48 | """ 49 | :param : TBD 50 | 51 | """ 52 | pass 53 | def getModel(self): 54 | """ 55 | :return: String 56 | """ 57 | return null 58 | def getMake(self): 59 | """ 60 | :return: String 61 | """ 62 | return null 63 | def getYear(self): 64 | """ 65 | :return: Number 66 | """ 67 | return null 68 | 69 | """ 70 | @package NamesInThings 71 | """ 72 | class NamesInThings(): 73 | def __init__(self): 74 | """ 75 | Constructor for NamesInThings 76 | :param field: TBD 77 | :param field1: TBD 78 | :param _some_private: TBD 79 | :param field_2: TBD 80 | """ 81 | self.field = None 82 | self.field1 = None 83 | self._some_private = None 84 | self.field_2 = None 85 | 86 | def member(self): 87 | """ 88 | 89 | """ 90 | pass 91 | def _member2(self): 92 | """ 93 | :return: String1 94 | """ 95 | return null 96 | def member3(self): 97 | """ 98 | 99 | """ 100 | pass 101 | def _member_s(self): 102 | """ 103 | :return: String2 104 | """ 105 | return null 106 | 107 | """ 108 | @package Toyota 109 | """ 110 | class Toyota(Car): 111 | def __init__(self): 112 | """ 113 | Constructor for Toyota 114 | """ 115 | pass 116 | 117 | 118 | """ 119 | @package Honda 120 | """ 121 | class Honda(Car): 122 | def __init__(self): 123 | """ 124 | Constructor for Honda 125 | """ 126 | pass 127 | 128 | 129 | """ 130 | @package Ford 131 | """ 132 | class Ford(Car): 133 | def __init__(self): 134 | """ 135 | Constructor for Ford 136 | """ 137 | pass 138 | 139 | 140 | """ 141 | @package Hyundai 142 | """ 143 | class Hyundai(Car): 144 | def __init__(self): 145 | """ 146 | Constructor for Hyundai 147 | """ 148 | pass 149 | 150 | 151 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | workflows: 4 | version: 2 5 | test-publish: 6 | jobs: 7 | - test-node10: 8 | filters: # required since `deploy` has tag filters AND requires `build` 9 | tags: 10 | only: /.*/ 11 | - test-node12: 12 | filters: # required since `deploy` has tag filters AND requires `build` 13 | tags: 14 | only: /.*/ 15 | - test-node14: 16 | filters: # required since `deploy` has tag filters AND requires `build` 17 | tags: 18 | only: /.*/ 19 | - test-node-latest: 20 | filters: # required since `deploy` has tag filters AND requires `build` 21 | tags: 22 | only: /.*/ 23 | - publish: 24 | requires: 25 | - test-node10 26 | - test-node12 27 | - test-node14 28 | - test-node-latest 29 | filters: 30 | tags: 31 | only: /^v.*/ 32 | branches: 33 | ignore: /.*/ 34 | 35 | 36 | defaults: &defaults 37 | working_directory: ~/repo 38 | docker: 39 | - image: circleci/node:16 40 | steps: 41 | - checkout 42 | - run: node --version > _tmp_file 43 | - restore_cache: 44 | key: dependency-cache-{{ checksum "_tmp_file" }}-{{ checksum "package.json" }} 45 | - run: 46 | name: npm-install 47 | command: npm install 48 | 49 | - save_cache: 50 | key: dependency-cache-{{ checksum "_tmp_file" }}-{{ checksum "package.json" }} 51 | paths: 52 | - ./node_modules 53 | - run: 54 | name: test 55 | command: npm test 56 | environment: 57 | REPORTER: mocha-circleci-reporter 58 | MOCHA_FILE: junit/test-results.xml 59 | #- run: 60 | # name: coveralls 61 | # command: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 62 | - run: 63 | name: lint 64 | command: npm run lint 65 | when: always 66 | - store_test_results: 67 | path: junit 68 | - store_artifacts: 69 | path: junit 70 | - store_artifacts: 71 | path: coverage 72 | prefix: coverage 73 | jobs: 74 | test-node10: 75 | <<: *defaults 76 | docker: 77 | - image: circleci/node:10.14.1 78 | test-node12: 79 | <<: *defaults 80 | docker: 81 | - image: circleci/node:12 82 | test-node14: 83 | <<: *defaults 84 | docker: 85 | - image: circleci/node:14 86 | test-node-latest: 87 | <<: *defaults 88 | docker: 89 | - image: circleci/node:latest 90 | publish: 91 | <<: *defaults 92 | steps: 93 | - checkout 94 | - run: node --version > _tmp_file 95 | - restore_cache: 96 | key: dependency-cache-{{ checksum "_tmp_file" }}-{{ checksum "package.json" }} 97 | - run: 98 | name: Authenticate with registry 99 | command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc 100 | - run: 101 | name: Publish package 102 | command: npm publish 103 | -------------------------------------------------------------------------------- /src/templates/cpp.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * \file {{getFullName}}.h 3 | */ 4 | 5 | #ifndef {{getFullName}}_h 6 | #define {{getFullName}}_h 7 | 8 | {{#if getExtends}}#include "{{#with getExtends}}{{getFullName}}{{/with}}.h"{{/if}} 9 | 10 | 11 | class {{getFullName}}{{#if getExtends}}: public {{#with getExtends}}{{getFullName}}{{/with}}{{/if}} { 12 | {{!#if hasPrivateFields}} 13 | private:{{#each getPrivateFields}} 14 | {{this.getReturnType}} {{this.getName}};{{/each}}{{!/if}} 15 | protected:{{#each getFields}}{{#if this.isProtected}} 16 | {{this.getReturnType}} {{this.getName}};{{/if}}{{/each}} 17 | public:{{#each getFields}}{{#if this.isPublic}} 18 | {{this.getReturnType}} {{this.getName}};{{/if}}{{/each}} 19 | 20 | public: 21 | {{getFullName}}({{#each getConstructorArgs}}{{#if @first}}{{else}}, {{/if}}{{#if getName}}{{getName}}{{#if getDefaultValue}}={{SafeString this getDefaultValue}}{{/if}}{{else}}param{{@index}}{{/if}}{{/each}}){{#if getExtends}}: {{#with getExtends}}{{getFullName}}{{/with}}(){{/if}} 22 | { 23 | // @todo 24 | } 25 | // Public methods 26 | {{#each getMethods}}{{#if isNotConstructor}}{{#if isPublic}} 27 | /**{{#each this.getParameters}} 28 | * @param {{getName}} TBD{{/each}}{{#if this.needsReturnStatement}} 29 | * @return {{this.getReturnType}}{{/if}} 30 | */ 31 | {{this.getReturnType}} {{this.getName}}{{#if this.getParameters}}({{#each this.getParameters}}{{#if @first}}{{else}}, {{/if}}{{this.getReturnType}} {{#if this.getName}}{{this.getName}}{{#if getDefaultValue}}={{SafeString this getDefaultValue}}{{/if}}{{else}}param{{@index}}{{/if}}{{/each}}){{else}}(){{/if}} { 32 | // @todo {{#if this.needsReturnStatement}} 33 | return {{this.getReturnType}}();{{/if}} 34 | } 35 | {{/if}}{{/if}}{{/each}} 36 | 37 | {{!#if hasProtectedMethods}} 38 | // Protected methods 39 | protected:{{#each getMethods}}{{#if isNotConstructor}}{{#if isProtected}} 40 | /**{{#each this.getParameters}} 41 | * @param {{getName}} TBD{{/each}}{{#if this.needsReturnStatement}} 42 | * @return {{this.getReturnType}}{{/if}} 43 | */ 44 | {{this.getReturnType}} {{this.getName}}{{#if this.getParameters}}({{#each this.getParameters}}{{#if @first}}{{else}}, {{/if}}{{this.getReturnType}} {{#if this.getName}}{{this.getName}}{{#if getDefaultValue}}={{SafeString this getDefaultValue}}{{/if}}{{else}}param{{@index}}{{/if}}{{/each}}){{else}}(){{/if}} { 45 | // @todo {{#if this.needsReturnStatement}} 46 | return {{this.getReturnType}}();{{/if}} 47 | } 48 | {{/if}}{{/if}}{{/each}}{{!/if}} 49 | 50 | {{!#if hasPrivateMethods}} 51 | // Private methods 52 | private:{{#each getMethods}}{{#if isNotConstructor}}{{#if isPrivate}} 53 | /**{{#each this.getParameters}} 54 | * @param {{getName}} TBD{{/each}}{{#if this.needsReturnStatement}} 55 | * @return {{this.getReturnType}}{{/if}} 56 | */ 57 | {{this.getReturnType}} {{this.getName}}{{#if this.getParameters}}({{#each this.getParameters}}{{#if @first}}{{else}}, {{/if}}{{this.getReturnType}} {{#if this.getName}}{{this.getName}}{{#if getDefaultValue}}={{SafeString this getDefaultValue}}{{/if}}{{else}}param{{@index}}{{/if}}{{/each}}){{else}}(){{/if}} { 58 | // @todo {{#if this.needsReturnStatement}} 59 | return {{this.getReturnType}}();{{/if}} 60 | } 61 | {{/if}}{{/if}}{{/each}}{{!/if}} 62 | } 63 | 64 | #endif // {{getFullName}}_h 65 | -------------------------------------------------------------------------------- /test/data/car.ecmascript6.js: -------------------------------------------------------------------------------- 1 | // 3rd party modules 2 | const String = require('String'); 3 | 4 | /** 5 | * Interface Class Vehicle 6 | * @interface 7 | */ 8 | class Vehicle { 9 | /** 10 | * Constructor for Vehicle 11 | */ 12 | constructor() { 13 | } 14 | 15 | /** 16 | * @return { String } 17 | */ 18 | getType() { 19 | throw new Error('Not implemented'); 20 | } 21 | } 22 | 23 | module.exports = Vehicle; 24 | 25 | // 3rd party modules 26 | const String = require('String'); 27 | const Number = require('Number'); 28 | 29 | /** 30 | * Class Car 31 | */ 32 | class Car extends Vehicle { 33 | /** 34 | * Constructor for Car 35 | */ 36 | constructor() { 37 | super(); 38 | this.model = undefined; 39 | this.make = undefined; 40 | this.year = undefined; 41 | } 42 | 43 | /** 44 | * @param { String } model='lada' TBD 45 | */ 46 | setModel(model='lada') { 47 | // TBD 48 | } 49 | 50 | /** 51 | * @param { String } make TBD 52 | */ 53 | setMake(make) { 54 | // TBD 55 | } 56 | 57 | /** 58 | * @param { Number } TBD 59 | */ 60 | setYear(param0) { 61 | // TBD 62 | } 63 | 64 | /** 65 | * @return { String } 66 | */ 67 | getModel() { 68 | return String; 69 | } 70 | 71 | /** 72 | * @return { String } 73 | */ 74 | getMake() { 75 | return String; 76 | } 77 | 78 | /** 79 | * @return { Number } 80 | */ 81 | getYear() { 82 | return Number; 83 | } 84 | } 85 | 86 | module.exports = Car; 87 | 88 | // 3rd party modules 89 | const String = require('String'); 90 | const String1 = require('String1'); 91 | const String_2 = require('String_2'); 92 | const String2 = require('String2'); 93 | 94 | /** 95 | * Class NamesInThings 96 | */ 97 | class NamesInThings { 98 | /** 99 | * Constructor for NamesInThings 100 | */ 101 | constructor() { 102 | this.field = undefined; 103 | this.field1 = undefined; 104 | this._some_private = undefined; 105 | this.field_2 = undefined; 106 | } 107 | 108 | /** 109 | */ 110 | member() { 111 | // TBD 112 | } 113 | 114 | /** 115 | * @return { String1 } 116 | * @private 117 | */ 118 | member2() { 119 | return String1; 120 | } 121 | 122 | /** 123 | */ 124 | member3() { 125 | // TBD 126 | } 127 | 128 | /** 129 | * @return { String2 } 130 | * @private 131 | */ 132 | member_s() { 133 | return String2; 134 | } 135 | } 136 | 137 | module.exports = NamesInThings; 138 | 139 | 140 | /** 141 | * Class Toyota 142 | */ 143 | class Toyota extends Car { 144 | /** 145 | * Constructor for Toyota 146 | */ 147 | constructor() { 148 | super(); 149 | } 150 | } 151 | 152 | module.exports = Toyota; 153 | 154 | 155 | /** 156 | * Class Honda 157 | */ 158 | class Honda extends Car { 159 | /** 160 | * Constructor for Honda 161 | */ 162 | constructor() { 163 | super(); 164 | } 165 | } 166 | 167 | module.exports = Honda; 168 | 169 | 170 | /** 171 | * Class Ford 172 | */ 173 | class Ford extends Car { 174 | /** 175 | * Constructor for Ford 176 | */ 177 | constructor() { 178 | super(); 179 | } 180 | } 181 | 182 | module.exports = Ford; 183 | 184 | 185 | /** 186 | * Class Hyundai 187 | */ 188 | class Hyundai extends Car { 189 | /** 190 | * Constructor for Hyundai 191 | */ 192 | constructor() { 193 | super(); 194 | } 195 | } 196 | 197 | module.exports = Hyundai; 198 | 199 | -------------------------------------------------------------------------------- /src/parser/UMLBlock.js: -------------------------------------------------------------------------------- 1 | const Namespace = require('./Namespace'); 2 | const Class = require('./Class'); 3 | const AbstractClass = require('./AbstractClass'); 4 | const InterfaceClass = require('./InterfaceClass'); 5 | const Connection = require('./Connection'); 6 | const Package = require('./Package'); 7 | const Extension = require('./Extension'); 8 | 9 | class UMLBlock { 10 | constructor(fileLines) { 11 | this.aNamespaces = []; // contains all defined namespaces 12 | this.aPackages = []; // contains all defined packages 13 | this.aClasses = []; // contains all defined classes 14 | this.aConnections = []; // contains all defined connections 15 | 16 | this.aItems = fileLines; 17 | 18 | this.populateGlobals(this); 19 | this.setupConnections(); 20 | } 21 | 22 | getClasses() { 23 | return this.aClasses; 24 | } 25 | 26 | getItems() { 27 | return this.aItems; 28 | } 29 | 30 | setupConnections() { 31 | for (let i = 0, { length } = this.aConnections; i < length; i += 1) { 32 | this.setupConnection(this.aConnections[i]); 33 | } 34 | } 35 | 36 | setupConnection(connection) { 37 | let cLeft = null; 38 | let cRight = null; 39 | for (let i = 0, { length } = this.aClasses; i < length; i += 1) { 40 | if (connection.leftObject.indexOf('.') !== -1) { 41 | if (connection.leftObject.indexOf('.') === 0) { 42 | if (this.aClasses[i].getNamespace() === null 43 | && this.aClasses[i].getName() === connection.leftObject.substring(1)) { 44 | cLeft = this.aClasses[i]; 45 | } 46 | } else if (this.aClasses[i].getFullName() === connection.leftObject) { 47 | cLeft = this.aClasses[i]; 48 | } 49 | } else if (this.aClasses[i].getName() === connection.leftObject 50 | && this.aClasses[i].getNamespace() === connection.getNamespace()) { 51 | cLeft = this.aClasses[i]; 52 | } 53 | 54 | if (connection.rightObject.indexOf('.') !== -1) { 55 | if (connection.rightObject.indexOf('.') === 0) { 56 | if (this.aClasses[i].getNamespace() === null 57 | && this.aClasses[i].getName() === connection.rightObject.substring(1)) { 58 | cRight = this.aClasses[i]; 59 | } 60 | } else if (this.aClasses[i].getFullName() === connection.rightObject) { 61 | cRight = this.aClasses[i]; 62 | } 63 | } else if (this.aClasses[i].getName() === connection.rightObject 64 | && this.aClasses[i].getNamespace() === connection.getNamespace()) { 65 | cRight = this.aClasses[i]; 66 | } 67 | } 68 | 69 | if (cLeft !== null && cRight !== null) { 70 | if (connection.getConnector() instanceof Extension) { 71 | if (connection.getConnector().isLeft()) { 72 | cRight.setExtends(cLeft); 73 | } else { 74 | cLeft.setExtends(cRight); 75 | } 76 | } 77 | } 78 | } 79 | 80 | populateGlobals(item) { 81 | const items = item.getItems(); 82 | 83 | for (let i = 0, { length } = items; i < length; i += 1) { 84 | if (items[i] instanceof Namespace) { 85 | this.aNamespaces.push(items[i]); 86 | this.populateGlobals(items[i]); 87 | } else if (items[i] instanceof Class || items[i] instanceof AbstractClass || items[i] instanceof InterfaceClass) { 88 | this.aClasses.push(items[i]); 89 | } else if (items[i] instanceof Package) { 90 | this.aPackages.push(items[i]); 91 | this.populateGlobals(items[i]); 92 | } else if (items[i] instanceof Connection) { 93 | this.aConnections.push(items[i]); 94 | } else { 95 | throw new Error('Unknown type'); 96 | } 97 | } 98 | } 99 | } 100 | module.exports = UMLBlock; 101 | -------------------------------------------------------------------------------- /src/parser/Class.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Field = require('./Field'); 3 | const Method = require('./Method'); 4 | 5 | class Class { 6 | constructor(className, members) { 7 | this.cExtends = null; 8 | this.members = members || []; 9 | this.members.forEach(member => member.setIsConstructor(this.isConstructor.bind(this))); 10 | this.className = className; 11 | this.nNamespace = null; 12 | } 13 | 14 | static splitArrays(acc, dep) { 15 | // List -> [List, Value] 16 | const parts = dep.split('<'); 17 | parts.forEach((part) => { 18 | acc.push(_.trimEnd(part, '>')); 19 | }); 20 | return acc; 21 | } 22 | 23 | _getDependencies() { 24 | const returnTypes = this.members.map(member => member.getReturnType()); 25 | const parameterTypes = this.members 26 | .reduce((acc, member) => [...acc, ...member.getParameters()], []) 27 | .map(params => params.getReturnType()); 28 | const ignoreModules = ['void', 'async']; 29 | const all = [...returnTypes, ...parameterTypes] 30 | .reduce(Class.splitArrays, []) 31 | .filter(type => ignoreModules.indexOf(type) === -1); 32 | 33 | return _.uniq(all); 34 | } 35 | 36 | static get langNativeModules() { 37 | return { 38 | ecmascript6: ['EventEmitter'], 39 | }; 40 | } 41 | 42 | getNativeModules() { 43 | // how to select language specific native modules.. 44 | const nativeModules = Class.langNativeModules.ecmascript6; 45 | const allDeps = this._getDependencies(); 46 | const isValid = dep => nativeModules.indexOf(dep) !== -1; 47 | return _.uniq(_.filter(allDeps, isValid)); 48 | } 49 | 50 | get3rdPartyModules() { 51 | // figure out 3rd party dependencies 52 | const native = this.getNativeModules(); 53 | const allDeps = this._getDependencies(); 54 | const isValid = dep => native.indexOf(dep) === -1; 55 | return _.filter(allDeps, isValid); 56 | } 57 | 58 | getAppModules() { 59 | const native = this.getNativeModules(); 60 | const extDep = this.get3rdPartyModules(); 61 | const exluded = [...native, ...extDep]; 62 | return _.without(this._getDependencies(), ...exluded); 63 | } 64 | 65 | setExtends(className) { 66 | this.cExtends = className; 67 | } 68 | 69 | getExtends() { 70 | return this.cExtends; 71 | } 72 | 73 | setNamespace(namespace) { 74 | this.nNamespace = namespace; 75 | } 76 | 77 | getNamespace() { 78 | return this.nNamespace; 79 | } 80 | 81 | isAbstract() { // eslint-disable-line class-methods-use-this 82 | return false; 83 | } 84 | 85 | isInterface() { // eslint-disable-line class-methods-use-this 86 | return false; 87 | } 88 | 89 | getName() { 90 | return this.className; 91 | } 92 | 93 | isConstructor(name) { 94 | const languageSpecific = { 95 | coffeescript: 'constructor', 96 | ecmascript5: 'constructor', 97 | ecmascript6: 'constructor', 98 | java: this.getName(), 99 | php: '__construct', 100 | python: '__init__', 101 | ruby: 'initialize', 102 | cpp: this.getName(), 103 | typescript: 'constructor', 104 | }; 105 | return Object.values(languageSpecific).indexOf(name) !== -1; 106 | } 107 | 108 | getConstructorArgs() { 109 | const methods = this.getMethods(); 110 | const cs = methods.find(method => this.isConstructor(method.getName())); 111 | if (cs) { 112 | return cs.getParameters(); 113 | } 114 | return []; 115 | } 116 | 117 | hasPublichMethods() { 118 | return !!this.getMethods().length; 119 | } 120 | 121 | getPublicMethods() { 122 | return _.filter(this.getMethods(), method => method.isPublic()); 123 | } 124 | 125 | 126 | hasMethods() { 127 | return !!this.getMethods().length; 128 | } 129 | 130 | getPrivateMethods() { 131 | return _.filter(this.getMethods(), method => method.isPrivate()); 132 | } 133 | 134 | hasPrivateMethods() { 135 | return !!this.getPrivateMethods().length; 136 | } 137 | 138 | /** 139 | * get methods 140 | * @returns {[Method>]} list of Method's 141 | * @private 142 | */ 143 | getMethods() { 144 | const aResult = this.members.filter(file => file instanceof Method); 145 | return aResult; 146 | } 147 | 148 | hasFields() { 149 | return !!this.getFields().length; 150 | } 151 | 152 | hasPrivateFields() { 153 | return !!this.getPrivateFields().length; 154 | } 155 | 156 | getPrivateFields() { 157 | return _.filter(this.getFields(), field => field.isPrivate()); 158 | } 159 | 160 | getFields() { 161 | const aResult = this.members.filter(file => (!(file instanceof Method) && (file instanceof Field))); 162 | return aResult; 163 | } 164 | 165 | getFullName() { 166 | if (this.getNamespace() !== null) { 167 | return `${this.getNamespace().getFullName()}.${this.getName()}`; 168 | } 169 | return this.getName(); 170 | } 171 | } 172 | module.exports = Class; 173 | -------------------------------------------------------------------------------- /test/data/car.cpp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file Vehicle.h 3 | */ 4 | 5 | #ifndef Vehicle_h 6 | #define Vehicle_h 7 | 8 | 9 | 10 | 11 | class Vehicle { 12 | private: 13 | protected: 14 | public: 15 | 16 | public: 17 | Vehicle() 18 | { 19 | // @todo 20 | } 21 | // Public methods 22 | 23 | /** 24 | * @return String 25 | */ 26 | String getType() { 27 | // @todo 28 | return String(); 29 | } 30 | 31 | 32 | // Protected methods 33 | protected: 34 | 35 | // Private methods 36 | private: 37 | } 38 | 39 | #endif // Vehicle_h 40 | 41 | /** 42 | * \file Car.h 43 | */ 44 | 45 | #ifndef Car_h 46 | #define Car_h 47 | 48 | #include "Vehicle.h" 49 | 50 | 51 | class Car: public Vehicle { 52 | private: 53 | String model; 54 | String make; 55 | Number year; 56 | protected: 57 | public: 58 | 59 | public: 60 | Car(): Vehicle() 61 | { 62 | // @todo 63 | } 64 | // Public methods 65 | 66 | /** 67 | * @param model TBD 68 | */ 69 | void setModel(String model='lada') { 70 | // @todo 71 | } 72 | 73 | /** 74 | * @param make TBD 75 | */ 76 | void setMake(String make) { 77 | // @todo 78 | } 79 | 80 | /** 81 | * @param TBD 82 | */ 83 | void setYear(Number param0) { 84 | // @todo 85 | } 86 | 87 | /** 88 | * @return String 89 | */ 90 | String getModel() { 91 | // @todo 92 | return String(); 93 | } 94 | 95 | /** 96 | * @return String 97 | */ 98 | String getMake() { 99 | // @todo 100 | return String(); 101 | } 102 | 103 | /** 104 | * @return Number 105 | */ 106 | Number getYear() { 107 | // @todo 108 | return Number(); 109 | } 110 | 111 | 112 | // Protected methods 113 | protected: 114 | 115 | // Private methods 116 | private: 117 | } 118 | 119 | #endif // Car_h 120 | 121 | /** 122 | * \file NamesInThings.h 123 | */ 124 | 125 | #ifndef NamesInThings_h 126 | #define NamesInThings_h 127 | 128 | 129 | 130 | 131 | class NamesInThings { 132 | private: 133 | String _some_private; 134 | String_2 field_2; 135 | protected: 136 | public: 137 | String field; 138 | String1 field1; 139 | 140 | public: 141 | NamesInThings() 142 | { 143 | // @todo 144 | } 145 | // Public methods 146 | 147 | /** 148 | */ 149 | void member() { 150 | // @todo 151 | } 152 | 153 | 154 | // Protected methods 155 | protected: 156 | /** 157 | */ 158 | void member3() { 159 | // @todo 160 | } 161 | 162 | 163 | // Private methods 164 | private: 165 | /** 166 | * @return String1 167 | */ 168 | String1 member2() { 169 | // @todo 170 | return String1(); 171 | } 172 | 173 | /** 174 | * @return String2 175 | */ 176 | String2 member_s() { 177 | // @todo 178 | return String2(); 179 | } 180 | 181 | } 182 | 183 | #endif // NamesInThings_h 184 | 185 | /** 186 | * \file Toyota.h 187 | */ 188 | 189 | #ifndef Toyota_h 190 | #define Toyota_h 191 | 192 | #include "Car.h" 193 | 194 | 195 | class Toyota: public Car { 196 | private: 197 | protected: 198 | public: 199 | 200 | public: 201 | Toyota(): Car() 202 | { 203 | // @todo 204 | } 205 | // Public methods 206 | 207 | 208 | // Protected methods 209 | protected: 210 | 211 | // Private methods 212 | private: 213 | } 214 | 215 | #endif // Toyota_h 216 | 217 | /** 218 | * \file Honda.h 219 | */ 220 | 221 | #ifndef Honda_h 222 | #define Honda_h 223 | 224 | #include "Car.h" 225 | 226 | 227 | class Honda: public Car { 228 | private: 229 | protected: 230 | public: 231 | 232 | public: 233 | Honda(): Car() 234 | { 235 | // @todo 236 | } 237 | // Public methods 238 | 239 | 240 | // Protected methods 241 | protected: 242 | 243 | // Private methods 244 | private: 245 | } 246 | 247 | #endif // Honda_h 248 | 249 | /** 250 | * \file Ford.h 251 | */ 252 | 253 | #ifndef Ford_h 254 | #define Ford_h 255 | 256 | #include "Car.h" 257 | 258 | 259 | class Ford: public Car { 260 | private: 261 | protected: 262 | public: 263 | 264 | public: 265 | Ford(): Car() 266 | { 267 | // @todo 268 | } 269 | // Public methods 270 | 271 | 272 | // Protected methods 273 | protected: 274 | 275 | // Private methods 276 | private: 277 | } 278 | 279 | #endif // Ford_h 280 | 281 | /** 282 | * \file Hyundai.h 283 | */ 284 | 285 | #ifndef Hyundai_h 286 | #define Hyundai_h 287 | 288 | #include "Car.h" 289 | 290 | 291 | class Hyundai: public Car { 292 | private: 293 | protected: 294 | public: 295 | 296 | public: 297 | Hyundai(): Car() 298 | { 299 | // @todo 300 | } 301 | // Public methods 302 | 303 | 304 | // Protected methods 305 | protected: 306 | 307 | // Private methods 308 | private: 309 | } 310 | 311 | #endif // Hyundai_h 312 | 313 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { Readable } = require('stream'); 2 | const { join } = require('path'); 3 | const { createReadStream, readFile } = require('fs'); 4 | // 3rd party modules 5 | const Promise = require('bluebird'); 6 | const Handlebars = require('handlebars'); 7 | const _ = require('lodash'); 8 | // application modules 9 | const parser = require('./parser'); 10 | const Output = require('./Output'); 11 | const dummyLogger = require('./logger'); 12 | 13 | const { SyntaxError } = parser; 14 | 15 | 16 | class PlantUmlToCode { 17 | constructor(stream, { logger = dummyLogger } = {}) { 18 | this._stream = stream; 19 | this.logger = logger; 20 | PlantUmlToCode._registerHandlebarsHelpers(); 21 | } 22 | 23 | static _registerHandlebarsHelpers() { 24 | // helper to avoid escape rendering 25 | // Usage 26 | // {{SafeString this getName}} where 'this' is an class instance and 'getName' are instance function 27 | // or 28 | // {{SafeString this}} where 'this' is an string 29 | const SafeString = (context, method) => new Handlebars.SafeString( 30 | _.isFunction(method) ? method.call(context) : context, 31 | ); 32 | Handlebars.registerHelper('SafeString', SafeString); 33 | 34 | // Workaround for an apparent bug in Handlebars: functions are not called with the parent scope 35 | // as context. 36 | // 37 | // Here the getFullName is found in the parent scope (Class), but it is called with the current 38 | // scope (Field) as context: 39 | // 40 | // {{#each getFields}} 41 | // {{../getFullName}} 42 | // {{/each}} 43 | // 44 | // The following helper works around it: 45 | // 46 | // {{#each getFields}} 47 | // {{#call ../this ../getFullName}} 48 | // {{/each}} 49 | Handlebars.registerHelper('call', (context, member) => member.call(context)); 50 | } 51 | 52 | static fromString(str) { 53 | if (!_.isString(str)) { 54 | throw new TypeError('str should be an String'); 55 | } 56 | const stream = new Readable(); 57 | stream._read = () => {}; // redundant? see update below 58 | stream.push(str); 59 | stream.push(null); 60 | return new PlantUmlToCode(stream); 61 | } 62 | 63 | static fromFile(file) { 64 | return new PlantUmlToCode(createReadStream(file)); 65 | } 66 | 67 | static async _readStream(stream) { 68 | const chunks = []; 69 | return new Promise((resolve, reject) => { 70 | stream.on('data', chunk => chunks.push(chunk)); 71 | stream.on('error', reject); 72 | stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); 73 | }); 74 | } 75 | 76 | async generate(lang = 'ecmascript6') { 77 | this.logger.silly('Reading puml data'); 78 | try { 79 | const str = await PlantUmlToCode._readStream(this._stream); 80 | const files = await this._toCode(str, lang); 81 | return new Output(files, { logger: this.logger }); 82 | } catch (error) { 83 | if (error instanceof SyntaxError) { 84 | const str = `line: ${error.location.start.line} column: ${error.location.start.column}: ${error}`; 85 | this.logger.error(str); 86 | throw new Error(str); 87 | } 88 | this.logger.error(error); 89 | throw error; 90 | } 91 | } 92 | 93 | static get templateFiles() { 94 | return _.reduce(PlantUmlToCode.languages, (acc, lang) => { 95 | acc[lang] = join(__dirname, 'templates', `${lang}.hbs`); 96 | return acc; 97 | }, {}); 98 | } 99 | 100 | async _readTemplate(lang) { 101 | const tmpl = PlantUmlToCode.templateFiles[lang]; 102 | this.logger.silly(`Read template: ${tmpl}`); 103 | let source = await Promise.fromCallback(cb => readFile(tmpl, cb)); 104 | source = source.toString(); 105 | return Handlebars.compile(source, { noEscape: true }); 106 | } 107 | 108 | static get languages() { 109 | return _.keys(PlantUmlToCode.extensions); 110 | } 111 | 112 | static get extensions() { 113 | return { 114 | coffeescript: 'coffee', 115 | csharp: 'cs', 116 | ecmascript5: 'js', 117 | ecmascript6: 'js', 118 | java: 'java', 119 | php: 'php', 120 | python: 'py', 121 | ruby: 'rb', 122 | typescript: 'ts', 123 | cpp: 'h', 124 | }; 125 | } 126 | 127 | static getExtension(lang) { 128 | return _.get(PlantUmlToCode.extensions, lang, 'js'); 129 | } 130 | 131 | /** 132 | * @param {string} pegjs rules 133 | * @param {string} lang Target language 134 | * @returns {string} class code as a string 135 | * @private 136 | */ 137 | async _toCode(data, lang) { 138 | const template = await this._readTemplate(lang); 139 | const aUMLBlocks = await parser(data); 140 | const files = {}; 141 | const extension = PlantUmlToCode.getExtension(lang); 142 | aUMLBlocks.forEach((project) => { 143 | project.getClasses().forEach((element) => { 144 | files[`${element.getFullName()}.${extension}`] = template(element); 145 | }); 146 | }); 147 | return files; 148 | } 149 | } 150 | 151 | module.exports = PlantUmlToCode; 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/puml2code.svg)](https://badge.fury.io/js/puml2code) 2 | [![CircleCI](https://circleci.com/gh/jupe/puml2code/tree/master.svg?style=svg)](https://circleci.com/gh/jupe/puml2code/tree/master) 3 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 4 | [![License badge](https://img.shields.io/badge/license-MIT-blue.svg)](https://img.shields.io) 5 | 6 | 7 | ## PlantUML code generator (puml2code) 8 | 9 | a command line utility that convert Plantuml -text file that represent class UML diagram to source code. 10 | puml parser based on [plantuml-code-generator](https://github.com/bafolts/plantuml-code-generator) but is rewritten with es6. 11 | 12 | ### Installation 13 | 14 | Global installation brings `puml2code` command to PATH 15 | ```bash 16 | $ npm i -g puml2code 17 | ``` 18 | 19 | Development installation 20 | ```bash 21 | $ git clone https://github.com/jupe/puml2code.git 22 | $ npm i 23 | $ bin/puml2code -h 24 | ``` 25 | 26 | Running tests 27 | ```bash 28 | $ npm test 29 | ``` 30 | 31 | 32 | ### Supported output languages 33 | * [CoffeeScript](test/data/car.coffeescript.coffee) (coffeescript) 34 | * [C#](test/data/car.csharp.cs) (csharp) 35 | * [C++](test/data/car.cpp.cpp) (cpp) 36 | * [ECMAScript5](test/data/car.ecmascript5.js) (ecmascript5) 37 | * [ECMAScript6](test/data/car.ecmascript6.js) (ecmascript6) [default] 38 | * [Java](test/data/car.java.java) (java) 39 | * [PHP](test/data/car.php.php) (php) 40 | * [python](test/data/car.python.ts) (python) 41 | * [Ruby](test/data/car.ruby.rb) (ruby) 42 | * [TypeScript](test/data/car.typescript.ts) (typescript) 43 | 44 | 45 | ### Supported features 46 | * output: file per class/console 47 | * template engine: [handlebars](http://handlebarsjs.com) 48 | * puml parser engine: [pegjs](http://pegjs.org) 49 | 50 | Features supported per output language: 51 | 52 | |Language|supported|docs|deps imports| 53 | |--------|---------|---------|-------| 54 | |Coffeescript|✓||| 55 | |C#|✓||| 56 | |C++|✓|✓|| 57 | |ECMAScript5|✓||| 58 | |ECMAScript6|✓|✓|✓| 59 | |Java|✓||| 60 | |PHP|✓||| 61 | |Python|✓|✓|| 62 | |Ruby|✓||| 63 | |TypeScript|✓||| 64 | 65 | Feature explanations: 66 | 67 | 68 | |feature|notes| 69 | |-------|----| 70 | |supported|at least source code can be generated| 71 | |docs|Documentation comments, eg python: docstring| 72 | |deps imports|Try to detect dependencies and import them top of source files| 73 | 74 | 75 | **NOTE:** 76 | 77 | parser and code templates are not perfect. There is planty of cases that is not yet covered or supported. Basic scenarious should work. 78 | 79 | ### Problems? 80 | 81 | * If `puml2code` causes error like: 82 | ``` 83 | Error: line: 21 column: 3: SyntaxError: Expected "'", "--", "..", "__", "abstract ", 84 | "class ", "hide empty members", "interface ", "namespace ", "note ", "skinparam", "title ", 85 | [ \t], [#], [+], [A-Za-z_], [\-], [\n], [\r\n], [^ ,\n\r\t(){}], or [}] but "{" found. 86 | ``` 87 | it's most probably because [PEG.js based grammar](src/parser/plantuml.pegjs) does not have support 88 | for plantuml format you have in input file. 89 | 90 | **What should I do?** 91 | 92 | Please [raise ticket](https://github.com/jupe/puml2code/issues/new?template=grammar.md) with example plantuml file that does not work 93 | 94 | * generated source code does not look like you expected 95 | 96 | **What should I do?** 97 | 98 | Please [raise ticket](https://github.com/jupe/puml2code/issues/new?template=output.md) with example plantuml file and generated source 99 | code with some description how it should look like. 100 | 101 | 102 | **NOTE** If you are able to create PR that solves your issue it would be even more wellcome. 103 | 104 | ### Usage 105 | 106 | ``` 107 | $ puml2code -h 108 | Usage: puml2code [options] 109 | 110 | Options: 111 | -V, --version output the version number 112 | -i, --input [file] input .puml file, or "stdin" 113 | -l, --lang [lang] Optional output source code language (default: "ecmascript6") 114 | -o, --out [path] Output path. When not given output is printed to console. 115 | -h, --help output usage information 116 | 117 | Supported languages: coffeescript, csharp, ecmascript5, ecmascript6, java, php, python, ruby, typescript 118 | 119 | Examples: 120 | $ puml2code -i input.puml -l ecmascript6 121 | $ puml2code -h 122 | Use DEBUG=puml2code env variable to get traces. Example: 123 | $ DEBUG=puml2code puml2code -i input.puml 124 | ``` 125 | 126 | e.g. 127 | ``` 128 | $ puml2code -i myfile.puml 129 | 130 | Scheduler.js: 131 | // native modules 132 | // 3rd party modules 133 | // application modules 134 | const Queue = require('./Queue'); 135 | const Resources = require('./Resources'); 136 | 137 | 138 | /** 139 | * Class Scheduler 140 | */ 141 | class Scheduler { 142 | /** 143 | * TBD 144 | */ 145 | constructor(queue, resources) { 146 | this._queue = query; 147 | this._resoures = resources; 148 | } 149 | 150 | /** 151 | * @param {Queue} queue TBD 152 | */ 153 | _test(queue) { 154 | // TBD 155 | } 156 | 157 | /** 158 | * @param {Queue} queue TBD 159 | */ 160 | __protected(queue) { 161 | // TBD 162 | } 163 | } 164 | ``` 165 | See more output examples [here](examples) 166 | 167 | ## Tool logic 168 | ![logi](logic.png) 169 | 170 | 171 | ### LICENSE: 172 | [MIT](LICENSE) -------------------------------------------------------------------------------- /src/parser/plantuml.pegjs: -------------------------------------------------------------------------------- 1 | plantumlfile 2 | = items:((noise newline { return null }) / (noise "@startuml" noise newline filelines:umllines noise "@enduml" noise { var UMLBlock = require("./UMLBlock"); return new UMLBlock(filelines) }))* { for (var i = 0; i < items.length; i++) { if (items[i] === null) { items.splice(i, 1); i--; } } return items } 3 | umllines 4 | = lines:(umlline*) { for (var i = 0; i < lines.length; i++) { if (lines[i]===null) { lines.splice(i, 1); i--; } } return lines; } 5 | umlline 6 | = propertyset newline { return null } 7 | / titleset newline { return null } 8 | / headerset newline { return null } 9 | / noise newline { return null } 10 | / commentline { return null } 11 | / noteline { return null } 12 | / hideline newline { return null } 13 | / skinparams newline { return null } 14 | / declaration:packagedeclaration newline { return declaration } 15 | / declaration:namespacedeclaration newline { return declaration } 16 | / declaration:classdeclaration newline { return declaration } 17 | / declaration:abstractclassdeclaration newline { return declaration } 18 | / declaration:interfacedeclaration newline { return declaration } 19 | / declaration:memberdeclaration newline { return declaration } 20 | / declaration:connectordeclaration newline { return declaration } 21 | hideline 22 | = noise "hide empty members" noise 23 | skinparams 24 | = noise "skinparam" noise [^\r\n]+ 25 | connectordeclaration 26 | = noise leftObject:objectname noise connectordescription? noise connector:connectortype noise connectordescription? noise rightObject:objectname noise ([:] [^\r\n]+)? { var Connection = require("./Connection"); return new Connection(leftObject, connector, rightObject) } 27 | connectordescription 28 | = noise ["]([\\]["]/[^"])*["] noise 29 | titleset 30 | = noise "title " noise [^\r\n]+ noise 31 | headerset 32 | = "header" (!"endheader" .)* "endheader" 33 | commentline 34 | = noise "'" [^\r\n]+ noise 35 | / noise ".." [^\r\n\.]+ ".." noise 36 | / noise "--" [^\r\n\-]+ "--" noise 37 | / noise "__" [^\r\n\_]+ "__" noise 38 | noteline 39 | = noise "note " noise [^\r\n]+ noise 40 | connectortype 41 | = item:extends { return item } 42 | / concatenates { var Composition = require("./Composition"); return new Composition() } 43 | / aggregates { var Aggregation = require("./Aggregation"); return new Aggregation() } 44 | / connectorsize { return null } 45 | extends 46 | = "<|" connectorsize { var Extension = require("./Extension"); return new Extension(true) } 47 | / connectorsize "|>" { var Extension = require("./Extension"); return new Extension(false) } 48 | connectorsize 49 | = ".." 50 | / [-]+ "up" [-]+ 51 | / [-]+ "down" [-]+ 52 | / [-]+ "left" [-]+ 53 | / [-]+ "right" [-]+ 54 | / "---" 55 | / "--" 56 | / [.] 57 | / [-] 58 | concatenates 59 | = "*" connectorsize 60 | / connectorsize [*] 61 | aggregates 62 | = "o" connectorsize 63 | / connectorsize [o] 64 | startblock 65 | = noise [{] noise 66 | endblock 67 | = noise [}] 68 | propertyset 69 | = "setpropname.*" 70 | packagedeclaration 71 | = "package " objectname startblock newline umllines endblock 72 | / "package " objectname newline umllines "end package" 73 | interfacedeclaration 74 | = noise "interface " noise classname:objectname noise startblock lines:umllines endblock { var InterfaceClass = require("./InterfaceClass"); return new InterfaceClass(classname, lines) } 75 | abstractclassdeclaration 76 | = noise "abstract " noise "class "? noise classname:objectname noise startblock lines:umllines endblock { var AbstractClass = require("./AbstractClass"); return new AbstractClass(classname, lines) } 77 | / noise "abstract " noise "class "? noise classname:objectname noise { var AbstractClass = require("./AbstractClass"); return new AbstractClass(classname) } 78 | / noise "abstract " noise "class "? noise classname:objectname noise newline noise lines:umllines "end class" { var AbstractClass = require("./AbstractClass"); return new AbstractClass(classname, lines) } 79 | noise 80 | = [ \t]* 81 | splitter 82 | = [:] 83 | newline 84 | = [\r\n] 85 | / [\n] 86 | classdeclaration 87 | = noise "class " noise classname:objectname noise startblock lines:umllines endblock { var Class = require("./Class"); return new Class(classname, lines) } 88 | / noise "class " noise classname:objectname noise "<<" noise [^>]+ noise ">>" noise { var Class = require("./Class"); return new Class(classname) } 89 | / noise "class " noise classname:objectname noise { var Class = require("./Class"); return new Class(classname) } 90 | / noise "class " noise classname:objectname noise newline noise lines:umllines "end class" { var Class = require("./Class"); return new Class(classname, lines) } 91 | color 92 | = [#][0-9a-fA-F]+ 93 | namespacedeclaration 94 | = noise "namespace " noise namespacename:objectname noise color? noise startblock lines:umllines endblock { var Namespace = require("./Namespace"); return new Namespace(namespacename, lines) } 95 | / noise "namespace " noise namespacename:objectname noise newline umllines "end namespace" { var Namespace = require("./Namespace"); return new Namespace(namespacename) } 96 | staticmemberdeclaration 97 | = "static " memberdeclaration 98 | memberdeclaration 99 | = declaration:methoddeclaration { return declaration } 100 | / declaration:fielddeclaration { return declaration } 101 | fielddeclaration 102 | = noise accessortype:accessortype noise abstract:abstract? returntype:returntype noise membername:membername noise { var Field = require("./Field"); return new Field(accessortype, returntype, membername, abstract) } 103 | / noise accessortype:accessortype noise abstract:abstract? membername:membername noise { var Field = require("./Field"); return new Field(accessortype, "void", membername, abstract) } 104 | / noise returntype:returntype noise abstract:abstract? membername:membername noise { var Field = require("./Field"); return new Field("+", returntype, membername, abstract) } 105 | methoddeclaration 106 | = noise field:fielddeclaration [(] parameters:methodparameters [)] noise { var Method = require("./Method"); return new Method(field.getAccessType(), field.getReturnType(), field.getName(), parameters); } 107 | methodparameters 108 | = items:methodparameter* { return items; } 109 | methodparameter 110 | = noise item:returntype membername:([ ] membername)? [=] defaultValue:(defaultvalue) [,]? { var Parameter = require("./Parameter"); return new Parameter(item, membername ? membername[1] : null, defaultValue); } 111 | / noise item:returntype membername:([ ] membername)? [,]? { var Parameter = require("./Parameter"); return new Parameter(item, membername ? membername[1] : null); } 112 | returntype 113 | = items:[^ ,\n\r\t(){}<>]+ template:([<] templateargs [>])? typeinfo:[*\[\]&]* { return items.join("") + (template ? template.join("") : "") + typeinfo.join(""); } 114 | templateargs 115 | = items:templatearg+ { return items; } 116 | templatearg 117 | = noise item:returntype noise [,]? { return item; } 118 | objectname 119 | = objectname:([A-Za-z_][A-Za-z0-9.]*) { return [objectname[0], objectname[1].join("")].join("") } 120 | membername 121 | = "operator" op:[+=*/<>!~^&|,\[\]]+ { return "operator" + op.join("") } 122 | / items:([A-Za-z_\*][A-Za-z0-9_]*) { return [items[0], items[1].join("")].join("")} 123 | defaultvalue 124 | = items:([{}\[\]A-Za-z0-9_\'\"]*) { return items.join("") } 125 | accessortype 126 | = publicaccessor 127 | / privateaccessor 128 | / protectedaccessor 129 | publicaccessor 130 | = [+] 131 | privateaccessor 132 | = [-] 133 | protectedaccessor 134 | = [#] 135 | abstract 136 | = abstract:("{abstract}") noise { return !!abstract; } --------------------------------------------------------------------------------