[
  {
    "path": ".gitignore",
    "content": "out\nnode_modules\ndist"
  },
  {
    "path": ".npmignore",
    "content": "src\ntsconfig.json\nassets\n.vscodeignore"
  },
  {
    "path": ".vscodeignore",
    "content": ".vscode/**\n.vscode-test/**\nout/test/**\ntest/**\nsrc/**\n**/*.map\n.gitignore\ntsconfig.json\nvsc-extension-quickstart.md\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016-2018 Remo H. Jansen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<img src=\"/assets/logo.png\" width=\"150\" align=\"right\" />\n\n# TsUML\n\n:construction: WORK IN PROGRESS :construction:\n\nGenerate UML diagram for your TypeScript applications powered by https://yuml.me/\n\n![](/assets/cli-preview.gif)\n\n## Installation\n\n```sh\nnpm install -g tsuml\n```\n\n## Usage\n\n```\ntsuml --glob ./src/**/*.ts\n```\n\nThe diagram generated for the code under the [demo folder](https://github.com/remojansen/TsUML/tree/master/src/demo) looks as follows:\n\n![](/assets/uml_diagram.svg)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"tsuml\",\n  \"version\": \"0.0.1-alpha.8\",\n  \"description\": \"UML diagrams for TypeScript\",\n  \"main\": \"dist/lib/index.js\",\n  \"bin\": {\n    \"tsuml\": \"dist/bin/index.js\"\n  },\n  \"devDependencies\": {\n    \"@types/glob\": \"5.0.35\",\n    \"@types/lodash\": \"4.14.106\",\n    \"@types/node\": \"9.6.0\",\n    \"@types/opn\": \"5.1.0\",\n    \"@types/request\": \"2.47.0\",\n    \"@types/yargs\": \"11.0.0\"\n  },\n  \"dependencies\": {\n    \"chalk\": \"2.3.2\",\n    \"glob\": \"7.1.2\",\n    \"lodash\": \"4.17.5\",\n    \"opn\": \"5.3.0\",\n    \"request\": \"2.85.0\",\n    \"ts-simple-ast\": \"9.5.0\",\n    \"typescript\": \"2.7.2\",\n    \"yargs\": \"11.0.0\"\n  },\n  \"scripts\": {\n    \"test\": \"tsc -p tsconfig.json\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/remojansen/TsUML.git\"\n  },\n  \"keywords\": [\n    \"typescript\",\n    \"uml\",\n    \"diagram\",\n    \"generator\",\n    \"class\"\n  ],\n  \"author\": \"Remo H. Jansen\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/remojansen/TsUML/issues\"\n  },\n  \"homepage\": \"https://github.com/remojansen/TsUML#readme\"\n}\n"
  },
  {
    "path": "src/bin/index.ts",
    "content": "#! /usr/bin/env node\n\nimport chalk from \"chalk\";\nimport * as yargs from \"yargs\";\nimport { getUrl } from \"../core\";\n\n(async () => {\n\n    try {\n\n        if (yargs.argv.help) {\n            console.log(chalk.yellowBright(\"tsuml --glob ./src/**/*.ts\"));\n        }\n\n        const pattern = yargs.argv.glob;\n\n        if (!pattern) {\n            console.log(chalk.redBright(\"Missing --glob\"));\n        } else {\n            const url = await getUrl(\"./tsconfig.json\", pattern);\n            const opn = require(\"opn\");\n            opn(url);\n        }\n\n    } catch(e) {\n        console.log(chalk.redBright(e));\n    }\n\n})();\n"
  },
  {
    "path": "src/core/emitter.ts",
    "content": "import Ast, * as SimpleAST from \"ts-simple-ast\";\nimport * as ts from \"typescript\";\nimport { flatten, join } from \"lodash\";\nimport * as path from \"path\";\nimport { PropertyDetails, MethodDetails, HeritageClause } from \"./interfaces\";\nimport { templates }from \"./templates\";\nimport { download } from \"./io\";\n\nexport function emitSingleClass(name: string, properties: PropertyDetails[], methods: MethodDetails[]) {\n    return templates.class(name, properties, methods);\n}\n\nexport function emitSingleInterface(name: string, properties: PropertyDetails[], methods: MethodDetails[]) {\n    return templates.interface(name, properties, methods);\n}\n  \nexport function emitHeritageClauses(heritageClauses: HeritageClause[]) {\n    return heritageClauses.map((heritageClause) =>\n        templates.implementsOrExtends(heritageClause.clause, heritageClause.className)\n    );\n}\n"
  },
  {
    "path": "src/core/index.ts",
    "content": "import * as fs from \"fs\";\nimport chalk from \"chalk\";\nimport { flatten, join } from \"lodash\";\nimport { findFilesByGlob, download } from \"./io\";\nimport { getAst, parseClasses, parseInterfaces, parseHeritageClauses } from \"./parser\";\nimport { emitSingleClass, emitSingleInterface, emitHeritageClauses } from \"./emitter\";\n\nasync function getDsl(tsConfigPath: string, pattern: string) {\n\n  const sourceFilesPaths = await findFilesByGlob(pattern);\n\n  console.log(\n    chalk.yellowBright(\n      \"Matched files:\\n\" + sourceFilesPaths.reduce((p, c) => `${p}${c}\\n`, \"\")\n    )\n  );\n\n  const ast = getAst(tsConfigPath, sourceFilesPaths);\n  const files = ast.getSourceFiles();\n\n  // parser\n  const declarations = files.map(f => {\n    const classes = f.getClasses();\n    const interfaces = f.getInterfaces();\n    const path = f.getFilePath();\n    return {\n      fileName: path,\n      classes: classes.map(parseClasses),\n      heritageClauses: classes.map(parseHeritageClauses),\n      interfaces: interfaces.map(parseInterfaces)\n    };\n  });\n\n  // emitter\n  const entities = declarations.map(d => {\n    const classes = d.classes.map((c) => emitSingleClass(c.className, c.properties, c.methods));\n    const interfaces = d.interfaces.map((i) => emitSingleInterface(i.interfaceName, i.properties, i.methods));\n    const heritageClauses = d.heritageClauses.map(emitHeritageClauses);\n    return [...classes, ...interfaces, ...heritageClauses];\n  });\n\n  return join(flatten(entities), \",\");\n\n}\n\nexport async function getUrl(tsConfigPath: string, pattern: string) {\n  const dsl = await getDsl(tsConfigPath, pattern);\n  return await download(dsl);\n}\n"
  },
  {
    "path": "src/core/interfaces.ts",
    "content": "export interface MethodDetails {\n    name: string;\n}\n\nexport interface PropertyDetails {\n    name: string;\n}\n\nexport interface HeritageClause {\n    clause: string;\n    className: string;\n}\n"
  },
  {
    "path": "src/core/io.ts",
    "content": "import * as glob from \"glob\";\nimport * as request from \"request\";\nimport * as fs from \"fs\";\n\nexport async function findFilesByGlob(pattern: string) {\n    return new Promise<string[]>((res, rej) => {\n        glob(pattern, (err, files) => {\n            if (err) {\n                rej(err);\n            } else {\n                res(files);\n            }\n        });\n    });\n}\n\nexport async function download(dsl: string) {\n    return new Promise<string>((resolve, reject) => {\n        const url = \"https://yuml.me/diagram/plain/class/\";\n        const options = {\n            form: {\n                dsl_text: dsl\n            }\n        };\n        request.post(url, options, (err, res, body) => {\n            if (err) {\n                reject(err);\n            }\n            const svgFileName = body.replace(\".png\", \".svg\");\n            const diagramUrl = `${url}${svgFileName}`;\n            resolve(diagramUrl);\n        });\n    });\n};\n"
  },
  {
    "path": "src/core/parser.ts",
    "content": "import Ast, * as SimpleAST from \"ts-simple-ast\";\nimport * as ts from \"typescript\";\nimport { flatten, join } from \"lodash\";\nimport { PropertyDetails, MethodDetails, HeritageClause } from \"./interfaces\";\n\nexport function getAst(tsConfigPath: string, sourceFilesPaths?: string[]) {\n    const ast = new Ast({\n        tsConfigFilePath: tsConfigPath,\n        addFilesFromTsConfig: !Array.isArray(sourceFilesPaths)\n    });\n    if (sourceFilesPaths) {\n        ast.addExistingSourceFiles(sourceFilesPaths);\n    }\n    return ast;\n}\n\nexport function parseClasses(classDeclaration: SimpleAST.ClassDeclaration) {\n    \n    const className = classDeclaration.getSymbol()!.getName();\n    const propertyDeclarations = classDeclaration.getProperties();\n    const methodDeclarations = classDeclaration.getMethods();\n\n    const properties = propertyDeclarations.map(property => {\n        const sym = property.getSymbol();\n        if (sym) {\n            return {\n                name: sym.getName()\n            };\n        }\n    }).filter((p) => p !== undefined) as PropertyDetails[];\n\n    const methods = methodDeclarations.map(method => {\n        const sym = method.getSymbol();\n        if (sym) {\n            return {\n                name: sym.getName()\n            }\n        }\n    }).filter((p) => p !== undefined) as MethodDetails[];\n\n    return { className, properties, methods };\n}\n\nexport function parseInterfaces(interfaceDeclaration: SimpleAST.InterfaceDeclaration) {\n\n    const interfaceName = interfaceDeclaration.getSymbol()!.getName();\n    const propertyDeclarations = interfaceDeclaration.getProperties();\n    const methodDeclarations = interfaceDeclaration.getMethods();\n  \n    const properties = propertyDeclarations.map(property => {\n        const sym = property.getSymbol();\n        if (sym) {\n            return {\n                name: sym.getName()\n            }\n        }\n    }).filter((p) => p !== undefined) as PropertyDetails[];\n  \n    const methods = methodDeclarations.map(method => {\n        const sym = method.getSymbol();\n        if (sym) {\n            return {\n                name: sym.getName()\n            }\n        }\n    }).filter((p) => p !== undefined) as MethodDetails[];\n  \n    return { interfaceName, properties, methods };\n}\n\nexport function parseHeritageClauses(classDeclaration: SimpleAST.ClassDeclaration) {\n\n    const className = classDeclaration.getSymbol()!.getName();\n    const extended =  classDeclaration.getExtends();\n    const implemented =  classDeclaration.getImplements();\n    let heritageClauses: HeritageClause[] = [];\n\n    if (extended) {\n        const identifier = extended.getChildrenOfKind(ts.SyntaxKind.Identifier)[0];\n        if (identifier) {\n            const sym = identifier.getSymbol();\n            if (sym) {\n                heritageClauses.push(\n                    {\n                        clause: sym.getName(),\n                        className\n                    }\n                );\n            }\n        }\n    }\n\n    if (implemented) {\n        implemented.forEach(i => {\n            const identifier = i.getChildrenOfKind(ts.SyntaxKind.Identifier)[0];\n            if (identifier) {\n                const sym = identifier.getSymbol();\n                if (sym) {\n                    heritageClauses.push(\n                        {\n                            clause: sym.getName(),\n                            className\n                        }\n                    );\n                }\n            }\n        });\n    }\n\n    return heritageClauses;\n}\n\n"
  },
  {
    "path": "src/core/templates.ts",
    "content": "import { PropertyDetails, MethodDetails} from \"./interfaces\";\n\nexport const templates = {\n    composition: \"+->\",\n    implementsOrExtends: (abstraction: string, implementation: string) => {\n        return (\n        `${templates.plainClassOrInterface(abstraction)}` +\n        `^-${templates.plainClassOrInterface(implementation)}`\n        );\n    },\n    plainClassOrInterface: (name: string) => `[${name}]`,\n    colorClass: (name: string) => `[${name}{bg:skyblue}]`,\n    colorInterface: (name: string) => `[${name}{bg:palegreen}]`,\n    class: (name: string, props: PropertyDetails[], methods: MethodDetails[]) => {\n        const pTemplate = (property: PropertyDetails) => `${property.name};`;\n        const mTemplate = (method: MethodDetails) => `${method.name}();`;\n        return (\n        `${templates.colorClass(name)}` +\n        `[${name}|${props.map(pTemplate).join(\"\")}|${methods.map(mTemplate).join(\"\")}]`\n        );\n    },\n    interface: (\n        name: string,\n        props: PropertyDetails[],\n        methods: MethodDetails[]\n    ) => {\n        const pTemplate = (property: PropertyDetails) => `${property.name};`;\n        const mTemplate = (method: MethodDetails) => `${method.name}();`;\n        return (\n        `${templates.colorInterface(name)}` +\n        `[${name}|${props.map(pTemplate).join(\"\")}|${methods.map(mTemplate).join(\"\")}]`\n        );\n    }\n};\n"
  },
  {
    "path": "src/demo/interfaces.ts",
    "content": "export interface Weapon {\n    tryHit(fromDistance: number): boolean;\n}\n\nexport interface Named {\n    name: string;\n}\n"
  },
  {
    "path": "src/demo/katana.ts",
    "content": "import { Weapon, Named } from \"./interfaces\";\n\nexport class BaseWeapon {\n    damage = 25;\n}\n\nexport class Katana extends BaseWeapon implements Weapon, Named  {\n    name = \"Katana\";\n    public tryHit(fromDistance: number) {\n        return fromDistance <= 2;\n    }\n}\n"
  },
  {
    "path": "src/demo/main.ts",
    "content": "import { Ninja } from \"./ninja\";\nimport { Katana } from \"./katana\";\n\nconst ninja = new Ninja(new Katana());\n\nninja.fight(5);\n"
  },
  {
    "path": "src/demo/ninja.ts",
    "content": "import { Weapon } from \"./interfaces\";\n\nexport class Ninja {\n    private _weapon: Weapon;\n    public constructor(weapon: Weapon) {\n        this._weapon = weapon;\n    }\n    public fight(fromDistance: number) {\n        return this._weapon.tryHit(fromDistance);\n    }\n}\n"
  },
  {
    "path": "src/lib/index.ts",
    "content": "export { getUrl } from \"../core\";\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"dom\", \"es2015\"],\n    \"strict\": true,\n    \"outDir\": \"dist\"\n  }\n}\n"
  }
]