-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgcode-processor.ts
125 lines (105 loc) · 3.33 KB
/
gcode-processor.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import fs from 'fs';
import path from 'path';
import readline from 'readline';
import { EventEmitter } from 'stream';
import { GCode, GCommand } from '../gcode';
import { Pipe } from '../pipe';
import { OutputPipe } from './pipes/output.pipe';
/**
* GCode Processor provides complete gcode file processing feature,
* utilizing user-provided processing pipes.
*
* @export
* @class GCodeProcessor
*/
export class GCodeProcessor {
/**
* Stores user-supplied pipes.
*
* @type {Pipe[]}
*/
private pipeline: Pipe[] = [];
/**
* Connects provided pipe to the end of the processor's pipeline.
*
* @param {Pipe} pipe Fresh Pipe instance.
*/
addPipe(pipe: Pipe) {
this.pipeline.push(pipe);
}
/**
* Runs file-processing routine:
* 1. Warmups all the pipes
* 2. Feeds all the data, contained in the file under `inputFilePath`
* 3. Collects output data and overwrites original file
* 4. Cooldowns all the pipes
*
* @async
* @param {string} inputFilePath path to the input file. Assumes that file exists.
* @returns {Promise<void>}
*/
async run(inputFilePath: string): Promise<void> {
const inPath = path.resolve(inputFilePath);
const parsedInPath = path.parse(inPath);
const outFilePath = path.join(parsedInPath.dir, parsedInPath.name + '-out' + parsedInPath.ext);
const pipeline = this.pipeline.slice();
pipeline.push(new OutputPipe(outFilePath));
const allSupportedCommands = this.connectPipes(pipeline);
try {
pipeline[0].warmup();
const stream = this.readLines(inPath);
stream.on('line', (line) => {
let gcode = new GCode(line, true);
if (allSupportedCommands.has(gcode.command as GCommand)) gcode = new GCode(line);
pipeline[0].input(gcode);
});
await this.eventToPromise(stream, 'close');
} finally {
pipeline[0].cooldown();
}
fs.renameSync(outFilePath, inPath);
}
/**
* Converts input file stream into readline interface
*
* @private
* @param {fs.PathLike} path path to the file
* @returns {readline.Interface} readline interface with data from the input file
*/
private readLines(path: fs.PathLike): readline.Interface {
const fileStream = fs.createReadStream(path);
const input = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});
return input;
}
/**
* Connects pipes to the each other in the provided pipeline.
*
* @private
* @param {Pipe[]} pipeline
* @returns {Set<GCommand>} list of all commands supported by the pipes in the pipeline
*/
private connectPipes(pipeline: Pipe[]): Set<GCommand> {
const allSupportedCommands = new Set<GCommand>();
pipeline.forEach((pipe, index) => {
pipe.SUPPORTED_COMMANDS.forEach((command) => allSupportedCommands.add(command));
const prevPipe = pipeline[index - 1];
if (prevPipe) {
prevPipe.nextPipe = pipe;
}
});
return allSupportedCommands;
}
/**
* Returns a promise which resolves with payload whenever specified event has happened
*
* @param eventEmitter event emitter to listen on
* @param eventName event name to listen for
* @returns Promise
*/
private eventToPromise<T>(eventEmitter: EventEmitter, eventName: string) {
return new Promise<T>((resolve) => eventEmitter.once(eventName, resolve));
}
}