├── .gitignore ├── THEMES.md ├── src ├── documentation.txt ├── classes │ ├── Vertex.js │ └── Graph.js ├── tools.js └── commands.js ├── package.json ├── LICENSE ├── tests ├── testsCollection.js ├── tests.js └── testsErrors.js ├── .eslintrc.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /THEMES.md: -------------------------------------------------------------------------------- 1 | # Themes I used in this project: 2 | 1. Async/await 3 | 2. Promises 4 | 3. Serialization/deserialization 5 | 4. OOP basics 6 | 5. RegExp 7 | 6. Working with files 8 | 7. Console output 9 | 8. Working with arrays, objects, collections and classes 10 | 9. Unit-testing 11 | -------------------------------------------------------------------------------- /src/documentation.txt: -------------------------------------------------------------------------------- 1 | Commands: 2 | 3 | new - to create new graph 4 | add - to add new node to graph 5 | dlink - to create directed link between nodes 6 | link - to create indirected link between nodes (you can link multiple nodes at once) 7 | select - to select nodes by data or by links 8 | modify - to change vertex data 9 | join - to merge 2 graphs 10 | delete - to delete node 11 | unlink - to delete link between two nodes 12 | save - to save graph to the file 13 | import - to upload existing graph from file 14 | clear - to delete all nodes from graph 15 | show - to display all nodes 16 | exit - to quit program 17 | 18 | 19 | ooooo ma gaaadd 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphs", 3 | "version": "1.0.0", 4 | "description": "This program is created to work with graph data structures", 5 | "main": "commands.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/kreslavskiy/graphs.git" 15 | }, 16 | "keywords": [ 17 | "graph" 18 | ], 19 | "author": "Mykhailo Kreslavskyi", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/kreslavskiy/graphs/issues" 23 | }, 24 | "homepage": "https://github.com/kreslavskiy/graphs#readme" 25 | } 26 | -------------------------------------------------------------------------------- /src/classes/Vertex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { removeFromArray } = require('../tools.js'); 4 | 5 | class Vertex { 6 | constructor(graphName, type, keyField, data) { 7 | this.graphName = graphName; 8 | this.type = type; 9 | this.keyField = keyField; 10 | this.data = data; 11 | this.links = new Array(); 12 | } 13 | 14 | get linksKeys() { 15 | const keys = new Array(); 16 | for (const link of this.links) { 17 | keys.push(link.key); 18 | } 19 | return keys; 20 | } 21 | 22 | createLink(destination, linkName, keyField) { 23 | const key = destination.data[keyField]; 24 | const links = this.linksKeys; 25 | if (!links.includes(key)) this.links.push({ key, linkName }); 26 | } 27 | 28 | deleteLink(linkToDelete) { 29 | for (const link of this.links) { 30 | if (link.key === linkToDelete) removeFromArray(this.links, link); 31 | } 32 | } 33 | } 34 | 35 | module.exports = { Vertex }; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Михайло Креславський 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 | -------------------------------------------------------------------------------- /tests/testsCollection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Graph } = require('../src/classes/Graph.js'); 4 | 5 | const graph = new Graph('people', 'name'); 6 | 7 | const FUNCTION_TESTS = { 8 | addTest() { 9 | graph.add('name: Dima, age: 19', 'person'); 10 | graph.add('name: Kirill, age: 18', 'person'); 11 | }, 12 | 13 | linkTest() { 14 | graph.link('Dima', 'Kirill', 'friends'); 15 | const firstVertexLink = graph.vertices.get('Dima').links[0]; 16 | const secondVertexLink = graph.vertices.get('Kirill').links[0]; 17 | return [firstVertexLink, secondVertexLink]; 18 | }, 19 | 20 | selectTest() { 21 | const selected = graph.select('age: 18'); 22 | const selectedData = selected[0].data; 23 | return selectedData; 24 | }, 25 | 26 | modifyVertexTest() { 27 | graph.modifyVertex('Dima', 'age: 13, job: none'); 28 | }, 29 | 30 | getLinkedTest() { 31 | const linked = graph.getLinked('Dima'); 32 | const linkedData = linked[0].data; 33 | return linkedData; 34 | }, 35 | 36 | deleteLinksTest() { 37 | graph.deleteLinks('Dima', 'Kirill'); 38 | return graph.vertices.get('Dima').links.length; 39 | }, 40 | 41 | deleteVertexTest() { 42 | graph.deleteVertex('Dima'); 43 | }, 44 | 45 | deleteGraphTest() { 46 | graph.deleteGraph('people'); 47 | }, 48 | }; 49 | 50 | module.exports = { FUNCTION_TESTS, graph }; 51 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert').strict; 4 | const { alert } = require('../src/tools.js'); 5 | const { FUNCTION_TESTS, graph } = require('./testsCollection.js'); 6 | 7 | const DATA_TESTS = [ 8 | [ 9 | FUNCTION_TESTS.addTest(), 10 | graph.vertices.get('Dima').data.age, 11 | 19, 12 | 'Function add() does not work properly', 13 | ], 14 | 15 | [ 16 | FUNCTION_TESTS.linkTest(), 17 | graph.vertices.get('Dima').links[0].linkName, 18 | 'friends', 19 | 'Function link() does not work properly', 20 | ], 21 | 22 | [ 23 | FUNCTION_TESTS.selectTest(), 24 | FUNCTION_TESTS.selectTest().name, 25 | 'Kirill', 26 | 'Function select() does not work properly', 27 | ], 28 | 29 | [ 30 | FUNCTION_TESTS.modifyVertexTest(), 31 | graph.vertices.get('Dima').data.age, 32 | 13, 33 | 'Function modifyVertex() does not work properly', 34 | ], 35 | 36 | [ 37 | FUNCTION_TESTS.getLinkedTest(), 38 | FUNCTION_TESTS.getLinkedTest().name, 39 | 'Kirill', 40 | 'Function getLinked() does not work properly', 41 | ], 42 | 43 | [ 44 | FUNCTION_TESTS.deleteLinksTest(), 45 | FUNCTION_TESTS.deleteLinksTest(), 46 | 0, 47 | 'Function deleteLinks() does not work properly', 48 | ], 49 | 50 | [ 51 | FUNCTION_TESTS.deleteVertexTest(), 52 | graph.vertices.get('Dima'), 53 | undefined, 54 | 'Function deleteVertex() does not work properly', 55 | ], 56 | 57 | [ 58 | FUNCTION_TESTS.deleteGraphTest(), 59 | graph.vertices.size, 60 | 0, 61 | 'Function deleteGraph() does not work properly', 62 | ], 63 | ]; 64 | 65 | //All tests are expected to run successfully 66 | 67 | for (const test of DATA_TESTS) { 68 | try { 69 | const [functionTest, entered, expected, message] = test; 70 | functionTest; 71 | assert.strictEqual(entered, expected, message); 72 | alert('green', 'Success!'); 73 | } catch (err) { 74 | alert('red', err.message); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/testsErrors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert').strict; 4 | const { alert } = require('../src/tools.js'); 5 | const { FUNCTION_TESTS, graph } = require('./testsCollection.js'); 6 | 7 | const DATA_TESTS = [ 8 | [ 9 | FUNCTION_TESTS.addTest(), 10 | typeof graph.vertices.get('Dima').data.age, 11 | 'string', 12 | 'Function add() does not work properly', 13 | ], 14 | 15 | [ 16 | FUNCTION_TESTS.linkTest(), 17 | typeof graph.vertices.get('Dima').links[0].linkName, 18 | undefined, 19 | 'Function link() does not work properly', 20 | ], 21 | 22 | [ 23 | FUNCTION_TESTS.selectTest(), 24 | typeof FUNCTION_TESTS.selectTest(), 25 | undefined, 26 | 'Function select() does not work properly', 27 | ], 28 | 29 | [ 30 | FUNCTION_TESTS.modifyVertexTest(), 31 | graph.vertices.get('Dima').data.age, 32 | '13', 33 | 'Function modifyVertex() does not work properly', 34 | ], 35 | 36 | [ 37 | FUNCTION_TESTS.getLinkedTest(), 38 | FUNCTION_TESTS.getLinkedTest().name, 39 | undefined, 40 | 'Function getLinked() does not work properly', 41 | ], 42 | 43 | [ 44 | FUNCTION_TESTS.deleteLinksTest(), 45 | FUNCTION_TESTS.deleteLinksTest(), 46 | 1, 47 | 'Function deleteLinks() does not work properly', 48 | ], 49 | 50 | [ 51 | FUNCTION_TESTS.deleteVertexTest(), 52 | graph.vertices.get('Dima'), 53 | 'Dima', 54 | 'Function deleteVertex() does not work properly', 55 | ], 56 | 57 | [ 58 | FUNCTION_TESTS.deleteGraphTest(), 59 | graph.vertices.size, 60 | 1, 61 | 'Function deleteGraph() does not work properly', 62 | ], 63 | ]; 64 | 65 | //All tests are expected to fail 66 | 67 | for (const test of DATA_TESTS) { 68 | try { 69 | const [functionTest, entered, expected, message] = test; 70 | functionTest; 71 | assert.strictEqual(entered, expected, message); 72 | alert('green', 'Success!'); 73 | } catch (err) { 74 | alert('red', err.message); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/tools.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vm = require('vm'); 4 | 5 | const COLORS = { 6 | red: '\x1b[31m', 7 | yell: '\x1b[33m', 8 | green: '\x1b[32m', 9 | }; 10 | 11 | const alert = (color, message) => { 12 | console.log(COLORS[color], message, '\x1b[0m'); 13 | }; 14 | 15 | const deserialize = (src) => 16 | vm.createScript('({' + src + '})').runInThisContext(); 17 | 18 | const removeFromArray = (array, value) => { 19 | if (array.includes(value)) { 20 | const index = array.indexOf(value); 21 | array.splice(index, 1); 22 | } 23 | }; 24 | 25 | const specifyType = (value) => { 26 | const reg = new RegExp('^[0-9]+$'); 27 | if (reg.test(value)) return Number(value); 28 | return '\'' + value + '\''; 29 | }; 30 | 31 | const checkInput = (line) => { 32 | const commas = (line.match(/,/g) || []).length; 33 | const colons = (line.match(/:/g) || []).length; 34 | if (colons - commas !== 1) { 35 | alert('red', 'Bad input'); 36 | return false; 37 | } else if (line.match(/['"]/g)) { 38 | alert('red', 'Please enter without quotes'); 39 | return false; 40 | } 41 | return true; 42 | }; 43 | 44 | const normalizeInput = (line) => { 45 | const normalized = line.trim().replaceAll(',', '').split(' '); 46 | return normalized; 47 | }; 48 | 49 | const addQuotes = (line) => { 50 | const result = []; 51 | const entries = line.replaceAll(' ', '').split(','); 52 | for (const entry of entries) { 53 | const data = entry.split(':'); 54 | data[1] = specifyType(data[1]); 55 | result.push(data.join(':')); 56 | } 57 | return result.join(','); 58 | }; 59 | 60 | const displayVertices = (vertices) => { 61 | for (const vertex of vertices) { 62 | const objectified = new Object(vertex); 63 | const { graphName, keyField, ...output } = objectified; 64 | console.log(vertex.data[keyField], '=>', output); 65 | } 66 | }; 67 | 68 | module.exports = { 69 | deserialize, 70 | removeFromArray, 71 | alert, 72 | checkInput, 73 | addQuotes, 74 | normalizeInput, 75 | displayVertices 76 | }; 77 | -------------------------------------------------------------------------------- /src/commands.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const readline = require('readline'); 4 | const fs = require('fs'); 5 | const { Graph } = require('./classes/Graph.js'); 6 | const { alert, displayVertices } = require('./tools.js'); 7 | 8 | console.log('Type "help" to see all commands!'); 9 | 10 | const rl = readline.createInterface({ 11 | input: process.stdin, 12 | output: process.stdout, 13 | prompt: '> ', 14 | }); 15 | 16 | rl.prompt(); 17 | 18 | const question = (str) => new Promise((answer) => rl.question(str, answer)); 19 | 20 | let graph = new Graph(); 21 | 22 | const commands = { 23 | help() { 24 | const message = fs.readFileSync('src/documentation.txt', 'utf-8'); 25 | console.log(message); 26 | }, 27 | 28 | async new() { 29 | const name = await question('Enter graph name: '); 30 | const field = await question('Enter key field: '); 31 | graph = new Graph(name, field, null); 32 | return graph; 33 | }, 34 | 35 | async add() { 36 | const type = await question('Enter type of vertex: '); 37 | const input = await question('Enter data: '); 38 | graph.add(input, type); 39 | }, 40 | 41 | async dlink() { 42 | const linkFrom = await question('From: '); 43 | const linkTo = await question('To: '); 44 | const type = await question('Enter link name: '); 45 | graph.link(linkFrom, linkTo, type, true); 46 | }, 47 | 48 | async link() { 49 | const linkFrom = await question('From: '); 50 | const linkTo = await question('To: '); 51 | const name = await question('Enter link name: '); 52 | graph.link(linkFrom, linkTo, name); 53 | }, 54 | 55 | async select() { 56 | const query = await question('Enter data: '); 57 | const links = await question('Enter links: '); 58 | const selected = graph.select(query); 59 | const link = graph.getLinked(links); 60 | if (![...selected, ...link].length) return alert('yell', 'Nothing found'); 61 | if (selected.length && link.length) { 62 | const res = selected.filter((value) => link.includes(value)); 63 | displayVertices(res, graph.keyField); 64 | } else displayVertices([...selected, ...link], graph.keyField); 65 | }, 66 | 67 | async modify() { 68 | const vertex = await question('Vertex you want to modify: '); 69 | const data = await question('Data you want to modify: '); 70 | graph.modifyVertex(vertex, data); 71 | }, 72 | 73 | async delete() { 74 | const vertexToDelete = await question('Enter vertex you want to delete: '); 75 | graph.deleteVertex(vertexToDelete); 76 | }, 77 | 78 | async unlink() { 79 | const deleteFrom = await question('Vertex you want to delete links from: '); 80 | const deleted = await question('Links you want to delete: '); 81 | graph.deleteLinks(deleteFrom, deleted); 82 | }, 83 | 84 | async save() { 85 | const name = await question('Enter file name: '); 86 | graph.saveToFile(name); 87 | }, 88 | 89 | async import() { 90 | const fileName = await question('Enter file name: '); 91 | graph.setGraph(fileName); 92 | }, 93 | 94 | async join() { 95 | const fileName = await question('Enter file name: '); 96 | graph.mergeTwoGraphs(fileName); 97 | }, 98 | 99 | async clear() { 100 | const graphName = await question('Enter name of graph you want to clear: '); 101 | graph.deleteGraph(graphName); 102 | }, 103 | 104 | show() { 105 | if (!graph) return alert('red', 'You have not created graph yet'); 106 | console.log('Graph name:', graph.graphName); 107 | if (!graph.vertices.size) 108 | return alert('red', 'There is no vertices in graph'); 109 | const vertices = graph.vertices.values(); 110 | displayVertices(vertices, graph.keyField); 111 | }, 112 | 113 | async exit() { 114 | if (!graph.directory) { 115 | const toSave = await question( 116 | 'Seems like you have unsaved changes. Wanna save?(y/n) ' 117 | ); 118 | if (toSave === 'y') await commands.save(); 119 | } 120 | rl.close(); 121 | }, 122 | }; 123 | 124 | rl.on('line', async (line) => { 125 | try { 126 | line = line.trim(); 127 | const command = commands[line]; 128 | if (command) await command(); 129 | else alert('red', 'Unknown command'); 130 | rl.prompt(); 131 | } catch (err) { 132 | alert('red', 'Uncought error'); 133 | process.exit(); 134 | } 135 | }).on('close', () => process.exit(0)); 136 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": 2022 10 | }, 11 | "rules": { 12 | "no-unused-vars": ["error", { "varsIgnorePattern": "graphName" }], 13 | "indent": ["error", 2], 14 | "linebreak-style": ["error", "unix"], 15 | "quotes": [ 16 | "error", 17 | "single", 18 | { 19 | "allowTemplateLiterals": true 20 | } 21 | ], 22 | "semi": ["error", "always"], 23 | "no-loop-func": ["error"], 24 | "block-spacing": ["error", "always"], 25 | "camelcase": ["error"], 26 | "eqeqeq": ["error", "always"], 27 | "strict": ["error", "global"], 28 | "brace-style": [ 29 | "error", 30 | "1tbs", 31 | { 32 | "allowSingleLine": true 33 | } 34 | ], 35 | "comma-style": ["error", "last"], 36 | "comma-spacing": [ 37 | "error", 38 | { 39 | "before": false, 40 | "after": true 41 | } 42 | ], 43 | "eol-last": ["error"], 44 | "func-call-spacing": ["error", "never"], 45 | "key-spacing": [ 46 | "error", 47 | { 48 | "beforeColon": false, 49 | "afterColon": true, 50 | "mode": "minimum" 51 | } 52 | ], 53 | "keyword-spacing": [ 54 | "error", 55 | { 56 | "before": true, 57 | "after": true, 58 | "overrides": { 59 | "function": { 60 | "after": false 61 | } 62 | } 63 | } 64 | ], 65 | "max-len": [ 66 | "error", 67 | { 68 | "code": 80, 69 | "ignoreUrls": true 70 | } 71 | ], 72 | "max-nested-callbacks": [ 73 | "error", 74 | { 75 | "max": 7 76 | } 77 | ], 78 | "new-cap": [ 79 | "error", 80 | { 81 | "newIsCap": true, 82 | "capIsNew": false, 83 | "properties": true 84 | } 85 | ], 86 | "new-parens": ["error"], 87 | "no-lonely-if": ["error"], 88 | "no-trailing-spaces": ["error"], 89 | "no-unneeded-ternary": ["error"], 90 | "no-whitespace-before-property": ["error"], 91 | "object-curly-spacing": ["error", "always"], 92 | "operator-assignment": ["error", "always"], 93 | "operator-linebreak": ["error", "after"], 94 | "semi-spacing": [ 95 | "error", 96 | { 97 | "before": false, 98 | "after": true 99 | } 100 | ], 101 | "space-before-blocks": ["error", "always"], 102 | "space-before-function-paren": [ 103 | "error", 104 | { 105 | "anonymous": "never", 106 | "named": "never", 107 | "asyncArrow": "always" 108 | } 109 | ], 110 | "space-in-parens": ["error", "never"], 111 | "space-infix-ops": ["error"], 112 | "space-unary-ops": [ 113 | "error", 114 | { 115 | "words": true, 116 | "nonwords": false, 117 | "overrides": { 118 | "typeof": false 119 | } 120 | } 121 | ], 122 | "no-unreachable": ["error"], 123 | "no-global-assign": ["error"], 124 | "no-self-compare": ["error"], 125 | "no-unmodified-loop-condition": ["error"], 126 | "no-constant-condition": [ 127 | "error", 128 | { 129 | "checkLoops": false 130 | } 131 | ], 132 | "no-console": ["off"], 133 | "no-useless-concat": ["error"], 134 | "no-useless-escape": ["error"], 135 | "no-shadow-restricted-names": ["error"], 136 | "no-use-before-define": [ 137 | "error", 138 | { 139 | "functions": false 140 | } 141 | ], 142 | "arrow-parens": ["error", "always"], 143 | "arrow-body-style": ["error", "as-needed"], 144 | "arrow-spacing": ["error"], 145 | "no-confusing-arrow": [ 146 | "error", 147 | { 148 | "allowParens": true 149 | } 150 | ], 151 | "no-useless-computed-key": ["error"], 152 | "no-useless-rename": ["error"], 153 | "no-var": ["error"], 154 | "object-shorthand": ["error", "always"], 155 | "prefer-arrow-callback": ["error"], 156 | "prefer-const": ["error"], 157 | "prefer-numeric-literals": ["error"], 158 | "prefer-rest-params": ["error"], 159 | "prefer-spread": ["error"], 160 | "rest-spread-spacing": ["error", "never"], 161 | "template-curly-spacing": ["error", "never"] 162 | } 163 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graphs 2 | This program is created to work with graph data structures. 3 | 4 | You can look which [themes](THEMES.md) I used in this project. 5 | 6 | No libraries used. 7 | 8 | ## Installation 9 | 1. Clone the repository: 10 | ```bash 11 | git clone https://github.com/kreslavskiy/graphs 12 | ``` 13 | 2. Run the program: 14 | ```bash 15 | node src/commands.js 16 | ``` 17 | ## Usage 18 | Type 'help' to see what which command does. 19 | 20 | 1. To create a new graph just type 'new', then enter name of the graph and key field 21 | 22 | Снимок экрана 2022-06-26 в 00 13 08 23 | 24 | 2. To add a new vertex to your graph use command 'add', then you'll have to enter type of the vertex, which you choose by yourself, and data that the vertex will contain. 25 | 26 | >**Warning**: don’t use quotes, otherwise vertex won't be added to the graph. 27 | 28 | Снимок экрана 2022-06-26 в 00 17 12 29 | 30 | 3. Use 'link' or 'dlink' commands to create a relation between vertices. 'link' creates undirected link and 'dlink' is for directed links. Also you'll have to enter name of the link for both commands. 31 | 32 | >**Note**: you can link multiple vertices at once, it'll work by many-to-many principle 33 | 34 | Снимок экрана 2022-06-26 в 00 22 12 35 | 36 | 4. Now, when you have a graph of 2 vertices, you may want to see it. Use command 'show' for it! 37 | 38 | Снимок экрана 2022-06-26 в 00 24 27 39 | 40 | 5. If you created many vertices and you want to see concrete ones, you can use command 'select', and select vertices by data: 41 | 42 | Снимок экрана 2022-06-27 в 00 05 23 43 | 44 | or by links: 45 | 46 | Снимок экрана 2022-06-27 в 00 06 09 47 | 48 | or by both: 49 | 50 | Снимок экрана 2022-06-27 в 00 06 39 51 | 52 | 6. To modify vertex data, you should use command 'modify', choose the vertex you want to change and enter new data. All new data will overwrite on existing one. 53 | 54 | Снимок экрана 2022-06-26 в 00 28 24 55 | 56 | >**Note**: you can change even key field of data! 57 | 58 | Снимок экрана 2022-06-26 в 00 31 37 59 | 60 | 7. If you want to delete relation between vertices, use 'unlink' command. 61 | >**Note**: it works by one-to-many principle 62 | 63 | Снимок экрана 2022-06-26 в 00 34 28 64 | 65 | 8. Now we can delete usless vertex by using command 'delete' 66 | 67 | Снимок экрана 2022-06-26 в 00 35 45 68 | 69 | 9. You also can save your graph, type 'save', enter file name and it'll be saved in .json format. 70 | 10. After saving, you can also open your graph in my program. Use command 'import' and enter file name. 71 | 72 | Снимок экрана 2022-06-27 в 00 08 02 73 | 74 | 11. If you created a graph and you want to concatinate it with another one, you can use command 'join', it'll upload data from entered file and unite these 2 graphs. 75 | >**Warning**: if graphs have different key fields, they won't merge 76 | Снимок экрана 2022-06-27 в 00 09 42 77 | 78 | 12. To delete all vertices at once use command 'clear', but you need to enter the name of graph to chack if you are sure. 79 | 80 | Снимок экрана 2022-06-26 в 00 41 26 81 | 82 | 13. To quit program use command 'exit'. IF you have unsaved data, it'll ask you if you want to save it. Type 'y' if you do and 'n' if don’t. 83 | 84 | ## Testing 85 | 86 | To test the program, type ```node tests/tests.js```, it expects everething to execute successfully. 87 | 88 | Снимок экрана 2022-06-26 в 00 47 42 89 | 90 | Then type ```node tests/testsErrors.js```, it expects everything to throw errors. 91 | 92 | Снимок экрана 2022-06-26 в 00 48 10 93 | 94 | -------------------------------------------------------------------------------- /src/classes/Graph.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const { Vertex } = require('../classes/Vertex.js'); 5 | const { 6 | checkInput, 7 | addQuotes, 8 | deserialize, 9 | normalizeInput, 10 | alert, 11 | } = require('../tools.js'); 12 | 13 | class Graph { 14 | constructor(graphName, keyField, directory) { 15 | this.graphName = graphName; 16 | this.keyField = keyField; 17 | this.directory = directory; 18 | this.vertices = new Map(); 19 | } 20 | 21 | add(input, vertexType) { 22 | if (!checkInput(input)) return; 23 | const inputNormalized = addQuotes(input); 24 | const data = deserialize(inputNormalized); 25 | const vertex = new Vertex(this.graphName, vertexType, this.keyField, data); 26 | if (Object.prototype.hasOwnProperty.call(data, this.keyField)) { 27 | const key = data[this.keyField]; 28 | if (!this.vertices.has(key)) { 29 | this.vertices.set(key, vertex); 30 | } 31 | } else alert('red', 'Vertex must contain key field'); 32 | return vertex; 33 | } 34 | 35 | link(source, destination, name, directed = false) { 36 | const sources = normalizeInput(source); 37 | const destinations = normalizeInput(destination); 38 | const vertices = this.vertices; 39 | const key = this.keyField; 40 | for (const vertex of sources) { 41 | const from = vertices.get(vertex); 42 | for (const link of destinations) { 43 | const target = vertices.get(link); 44 | if (from && target && !from.links.includes(link)) { 45 | from.createLink(target, name, key); 46 | if (!directed && !target.links.includes(vertex)) 47 | target.createLink(from, name, key); 48 | } else alert('red', 'One of these vertex does not exist'); 49 | } 50 | } 51 | } 52 | 53 | select(query) { 54 | const result = new Array(); 55 | if (query) { 56 | if (!checkInput(query)) return; 57 | const normalized = addQuotes(query); 58 | const input = deserialize(normalized); 59 | for (const vertex of this.vertices.values()) { 60 | const { data } = vertex; 61 | if (data) { 62 | for (const field in input) { 63 | if (data[field] === input[field]) result.push(vertex); 64 | } 65 | } 66 | } 67 | } 68 | return result; 69 | } 70 | 71 | getLinked(links) { 72 | const result = new Set(); 73 | links = links.replaceAll(' ', '').split(','); 74 | for (const vertex of this.vertices.values()) { 75 | const vertexLinks = vertex.linksKeys; 76 | for (const link of links) { 77 | if (vertexLinks.includes(link)) result.add(vertex); 78 | } 79 | } 80 | return Array.from(result); 81 | } 82 | 83 | async saveToFile(fileName) { 84 | const file = `${fileName}.json`; 85 | this.directory = file; 86 | const vertices = Object.fromEntries(this.vertices); 87 | let data = JSON.stringify(vertices); 88 | if (fs.existsSync(file)) { 89 | const oldData = JSON.parse(fs.readFileSync(file, 'utf-8')); 90 | data = JSON.stringify(Object.assign(oldData, vertices)); 91 | fs.truncate(file, (err) => { 92 | if (err) throw err; 93 | }); 94 | } 95 | await fs.promises.appendFile(file, data); 96 | } 97 | 98 | #vertexify(data) { 99 | const vertices = new Array(); 100 | for (const [key, value] of data) { 101 | const { graphName, type, keyField, data, links } = value; 102 | const vertex = new Vertex(graphName, type, keyField, data); 103 | vertex.links = links; 104 | vertices.push([key, vertex]); 105 | } 106 | return new Map(vertices); 107 | } 108 | 109 | #getVerticesFromFile(fileName) { 110 | const file = `${fileName}.json`; 111 | if (fs.existsSync(file)) { 112 | const content = fs.readFileSync(file, 'utf-8'); 113 | const parsed = Object.entries(JSON.parse(content)); 114 | const data = this.#vertexify(parsed); 115 | return data; 116 | } else return alert('red', 'This file does not exist'); 117 | } 118 | 119 | setGraph(fileName) { 120 | const vertices = this.#getVerticesFromFile(fileName); 121 | const [vertex] = vertices.values(); 122 | this.graphName = vertex.graphName; 123 | this.keyField = vertex.keyField; 124 | this.directory = fileName; 125 | this.vertices = vertices; 126 | return this.vertices; 127 | } 128 | 129 | mergeTwoGraphs(fileName) { 130 | const verticesFromFile = this.#getVerticesFromFile(fileName); 131 | const [ vertex ] = this.vertices.values(); 132 | const [ vertexFromFile ] = verticesFromFile.values(); 133 | if (vertex.keyField === vertexFromFile.keyField) { 134 | this.vertices = new Map([...this.vertices, ...verticesFromFile]); 135 | } else { 136 | alert('red', 'Unable to merge. These graphs have different key fields'); 137 | } 138 | } 139 | 140 | deleteGraph(name) { 141 | if (name === this.graphName) this.vertices.clear(); 142 | } 143 | 144 | deleteVertex(name) { 145 | const vertices = this.vertices; 146 | const vertexToDelete = vertices.get(name); 147 | const deletedKey = vertexToDelete.data[this.keyField]; 148 | const deleted = vertices.delete(name); 149 | if (deleted) { 150 | for (const vertex of vertices.values()) { 151 | vertex.deleteLink(deletedKey); 152 | } 153 | } 154 | } 155 | 156 | deleteLinks(deleteFrom, deleteWhat) { 157 | const linksToDelete = normalizeInput(deleteWhat); 158 | const vertex = this.vertices.get(deleteFrom); 159 | for (const link of linksToDelete) { 160 | vertex.deleteLink(link); 161 | } 162 | } 163 | 164 | #renameKey(oldName, newName, data) { 165 | this.vertices.set(newName, data); 166 | this.vertices.delete(oldName); 167 | for (const vertex of this.vertices.values()) { 168 | for (const link of vertex.links) { 169 | if (link.key === oldName) { 170 | link.key = newName; 171 | } 172 | } 173 | } 174 | } 175 | 176 | modifyVertex(link, newData) { 177 | if (!checkInput(newData)) return; 178 | const modificator = deserialize(addQuotes(newData)); 179 | const vertex = this.vertices.get(link); 180 | const keyField = this.keyField; 181 | 182 | if (this.vertices.has(modificator[keyField])) 183 | return alert('red', 'Vertex with this key field is already exists'); 184 | 185 | for (const [key, value] of Object.entries(modificator)) { 186 | if (Object.prototype.hasOwnProperty.call(vertex.data, key)) { 187 | if (vertex.data[key] !== modificator[key]) 188 | vertex.data[key] = modificator[key]; 189 | } else vertex.data[key] = value; 190 | } 191 | if (link !== vertex.data[keyField]) { 192 | this.#renameKey(link, vertex.data[keyField], vertex); 193 | } 194 | } 195 | } 196 | 197 | module.exports = { Graph }; 198 | --------------------------------------------------------------------------------