├── .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 |
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 |
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 |
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 |
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 |
43 |
44 | or by links:
45 |
46 |
47 |
48 | or by both:
49 |
50 |
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 |
55 |
56 | >**Note**: you can change even key field of data!
57 |
58 |
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 |
64 |
65 | 8. Now we can delete usless vertex by using command 'delete'
66 |
67 |
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 |
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 |
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 |
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 |
89 |
90 | Then type ```node tests/testsErrors.js```, it expects everything to throw errors.
91 |
92 |
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 |
--------------------------------------------------------------------------------