Skip to content

Commit d56c806

Browse files
Challenge 21 - Create your own sed (#32)
* Completed step 1 with tests for challenge 21 - Write your own sed * Completed step 2 - support for range of lines using `-n` * completed step 3 - added support for pattern with `-n` option * Completed step 4 - added support for `G` option - double spacing a file * added feature for stripping trailing blank lines * Updated whole sed. Added functionality for `-i` option 1. Using a custom parser to parse the command line args 2. Added control checks for valid and invalid args 3. Added support for in place option * Added jsdoc. Updated tests * Added link to challenge 21 in global README * Added readme, fixed test
1 parent 2de1b05 commit d56c806

File tree

6 files changed

+664
-1
lines changed

6 files changed

+664
-1
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Checkout my [Notion](https://mohitjain.notion.site/Coding-Challenges-af9b8197a43
3939
18. [Write Your Own Spotify Client](https://github.com/jainmohit2001/spotify-client)
4040
19. [Write Your Own Discord Bot](src/19/)
4141
20. [Write Your Own LinkedIn Carousel Generator](https://github.com/jainmohit2001/carousel-gen)
42+
21. [Write Your Own Sed](src/21/)
4243

4344
## Installation
4445

jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module.exports = {
88
'^.+\\.(ts|tsx)$': 'ts-jest'
99
},
1010
verbose: true,
11-
coverageDirectory: './coverage',
11+
coverageDirectory: '<rootDir>/coverage',
1212
collectCoverage: true,
1313
detectOpenHandles: true,
1414
silent: false

src/21/README.md

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Challenge 1 - Write Your Own Sed
2+
3+
This challenge corresponds to the 21<sup>st</sup> part of the Coding Challenges series by John Crickett https://codingchallenges.fyi/challenges/challenge-sed.
4+
5+
## Description
6+
7+
The Sed tool is written in `sed.ts`. The tool is used to perform basic text transformations on an input stream (a file or input from stdin).
8+
9+
## Usage
10+
11+
You can use `ts-node` to run the tool as follows:
12+
13+
```bash
14+
# Substitute <this> for <that> everywhere <this> appears in the file <filename>
15+
npx ts-node sed.ts s/<this>/<that>/g <filename>
16+
17+
# Print lines 2 to 4 from file <filename>
18+
npx ts-node sed.ts -n "2,4p" <filename>
19+
20+
# Output only lines containing a specific pattern <pattern> from file <filename>
21+
npx ts-node sed.ts -n /pattern/p <filename>
22+
23+
# Add another line after each line, i.e. double spacing a file.
24+
npx ts-node sed.ts G <filename>
25+
26+
# Edit in-place: Substitute <this> for <that> everywhere <this> appears in the file <filename>
27+
npx ts-node sed.ts -i 's/<this>/<that>/g' <filename>
28+
```
29+
30+
The following options are supported:
31+
32+
- Character replacement
33+
- Range of lines selection
34+
- Output only lines containing a specific pattern
35+
- Double spacing a file using option
36+
- Strip trailing blank lines from a file
37+
- Edit in-place
38+
39+
To use the tool in stdin mode, use the following command:
40+
41+
```bash
42+
cat filename | npx ts-node sed.ts [option]
43+
```
44+
45+
## Run tests
46+
47+
To run the tests for the Sed tool, go to the root directory of this repository and run the following command:
48+
49+
```bash
50+
npm test src/21/
51+
```

src/21/__tests__/sed.test.ts

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import { spawn } from 'child_process';
2+
import path from 'path';
3+
import fs from 'fs';
4+
import os from 'os';
5+
import { randomBytes } from 'crypto';
6+
7+
const PATH_TO_SED_JS = './build/21/sed.js';
8+
const filename = path.join(__dirname, 'test.txt');
9+
10+
describe('Testing invalid args', () => {
11+
const args = [
12+
[PATH_TO_SED_JS],
13+
[PATH_TO_SED_JS, 'invalid-pattern', 'invalid-file.txt']
14+
];
15+
16+
args.forEach((arg) => {
17+
it('should print usage for args: ' + args.toString(), (done) => {
18+
const sed = spawn('node', arg);
19+
let output = '';
20+
21+
sed.stdout.on('data', (data) => {
22+
output += data.toString();
23+
});
24+
25+
sed.on('close', () => {
26+
expect(output).toContain('Usage');
27+
done();
28+
});
29+
});
30+
});
31+
32+
it('should print invalid file with valid pattern', (done) => {
33+
const sed = spawn('node', [
34+
PATH_TO_SED_JS,
35+
's/this/that/',
36+
'invalid-file.txt'
37+
]);
38+
39+
let output = '';
40+
sed.stderr.on('data', (data) => {
41+
output += data.toString();
42+
});
43+
44+
sed.on('close', () => {
45+
expect(output).toContain('Invalid file');
46+
done();
47+
});
48+
});
49+
50+
it('should print "Invalid pattern or range"', (done) => {
51+
const sed = spawn('node', [
52+
PATH_TO_SED_JS,
53+
'-n',
54+
'2,4',
55+
'invalid-file.txt'
56+
]);
57+
let output = '';
58+
59+
sed.stderr.on('data', (data) => {
60+
output += data.toString();
61+
});
62+
63+
sed.on('close', () => {
64+
expect(output).toContain('Invalid pattern or range');
65+
done();
66+
});
67+
});
68+
69+
it('should print invalid file with valid range', (done) => {
70+
const sed = spawn('node', [
71+
PATH_TO_SED_JS,
72+
'-n',
73+
'2,4p',
74+
'invalid-file.txt'
75+
]);
76+
let output = '';
77+
78+
sed.stderr.on('data', (data) => {
79+
output += data.toString();
80+
});
81+
82+
sed.on('close', () => {
83+
expect(output).toContain('Invalid file');
84+
done();
85+
});
86+
});
87+
});
88+
89+
describe('Testing valid args', () => {
90+
it('should replace all occurrences', (done) => {
91+
const pattern = 's/a/b/';
92+
const content = fs.readFileSync(filename).toString();
93+
const expectedOutput = content.replaceAll('a', 'b');
94+
95+
const sed = spawn('node', [PATH_TO_SED_JS, pattern, filename]);
96+
let output = '';
97+
98+
sed.stdout.on('data', (data) => {
99+
output += data.toString();
100+
});
101+
102+
sed.on('close', () => {
103+
expect(output).toBe(expectedOutput);
104+
done();
105+
});
106+
});
107+
108+
it('should print only lines mentioned', (done) => {
109+
const [start, end] = [2, 4];
110+
const range = `${start},${end}p`;
111+
const content = fs.readFileSync(filename).toString();
112+
const expectedOutput = content
113+
.split(/\r\n|\n/)
114+
.slice(start, end + 1)
115+
.join('\r\n');
116+
117+
const sed = spawn('node', [PATH_TO_SED_JS, '-n', range, filename]);
118+
let output = '';
119+
120+
sed.stdout.on('data', (data) => {
121+
output += data.toString();
122+
});
123+
124+
sed.on('close', () => {
125+
expect(output).toBe(expectedOutput);
126+
done();
127+
});
128+
});
129+
130+
it('should print line containing the specific pattern only', (done) => {
131+
const pattern = 'roads';
132+
const content = fs.readFileSync(filename).toString();
133+
const expectedOutput: string[] = [];
134+
content.split(/\r\n|\n/).forEach((line) => {
135+
if (line.indexOf(pattern) >= 0) {
136+
expectedOutput.push(line);
137+
}
138+
});
139+
const sed = spawn('node', [
140+
PATH_TO_SED_JS,
141+
'-n',
142+
`/${pattern}/p`,
143+
filename
144+
]);
145+
146+
let output = '';
147+
148+
sed.stdout.on('data', (data) => {
149+
output += data.toString();
150+
});
151+
152+
sed.on('close', () => {
153+
expect(output).toBe(expectedOutput.join('\r\n'));
154+
done();
155+
});
156+
});
157+
158+
it('should double space the file correctly', (done) => {
159+
const content = fs.readFileSync(filename).toString();
160+
const expectedOutput = content.replaceAll(/\r\n|\n/g, '\r\n\r\n');
161+
const sed = spawn('node', [PATH_TO_SED_JS, 'G', filename]);
162+
163+
let output = '';
164+
165+
sed.stdout.on('data', (data) => {
166+
output += data.toString();
167+
});
168+
169+
sed.on('close', () => {
170+
expect(output).toBe(expectedOutput);
171+
done();
172+
});
173+
});
174+
175+
it('should remove trailing blank lines from a file', (done) => {
176+
const content = 'content\r\nsome more content ';
177+
const tmpfile = path.join(os.tmpdir(), 'temp-file.txt');
178+
fs.writeFileSync(tmpfile, content + '\r\n\r\n\r\n');
179+
const sed = spawn('node', [PATH_TO_SED_JS, '/^$/d', tmpfile]);
180+
181+
let output = '';
182+
183+
sed.stdout.on('data', (data) => {
184+
output += data.toString();
185+
});
186+
187+
sed.on('close', () => {
188+
expect(output).toBe(content.trimEnd());
189+
done();
190+
});
191+
});
192+
193+
it('should support -i option', (done) => {
194+
// First create a random test file from the `test.txt`
195+
const tempTextFile = path.join(__dirname, randomBytes(8).toString('hex'));
196+
fs.writeFileSync(tempTextFile, fs.readFileSync(filename));
197+
198+
// Prepare args
199+
const stringToReplace = 'Life';
200+
const stringToReplaceWith = 'Code';
201+
const characterReplacementInput = `s/${stringToReplace}/${stringToReplaceWith}/g`;
202+
203+
const initialContent = fs.readFileSync(tempTextFile).toString();
204+
const expectedContent = initialContent.replaceAll(
205+
stringToReplace,
206+
stringToReplaceWith
207+
);
208+
209+
const sed = spawn('node', [
210+
PATH_TO_SED_JS,
211+
'-i',
212+
characterReplacementInput,
213+
tempTextFile
214+
]);
215+
216+
sed.on('close', () => {
217+
const finalContent = fs.readFileSync(tempTextFile).toString();
218+
// Unlink random test file
219+
fs.unlinkSync(tempTextFile);
220+
221+
expect(finalContent).toBe(expectedContent);
222+
done();
223+
});
224+
});
225+
});

src/21/__tests__/test.txt

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"Life isn’t about getting and having, it’s about giving and being.",
2+
"Whatever the mind of man can conceive and believe, it can achieve.",
3+
"Strive not to be a success, but rather to be of value.",
4+
"Two roads diverged in a wood, and I—I took the one less traveled by, And that has made all the difference.",
5+
"I attribute my success to this: I never gave or took any excuse.",
6+
"You miss 100% of the shots you don’t take.",
7+
"I’ve missed more than 9000 shots in my career. I’ve lost almost 300 games. 26 times I’ve been trusted to take the game winning shot and missed. I’ve failed over and over and over again in my life. And that is why I succeed.",
8+
"The most difficult thing is the decision to act, the rest is merely tenacity.",
9+
"Every strike brings me closer to the next home run.",
10+
"Definiteness of purpose is the starting point of all achievement."

0 commit comments

Comments
 (0)