Skip to content

Commit 0a89931

Browse files
Add new exercise grep (#752)
* Add exercise grep Co-authored-by: Derk-Jan Karrenbeld <derk-jan+github@karrenbeld.info>
1 parent 6254119 commit 0a89931

File tree

12 files changed

+699
-0
lines changed

12 files changed

+699
-0
lines changed

config.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,18 @@
14301430
"strings"
14311431
],
14321432
"deprecated": true
1433+
},
1434+
{
1435+
"slug": "grep",
1436+
"uuid": "78bcbae1-a0f2-460c-ba89-e51844fe9397",
1437+
"core": false,
1438+
"unlocked_by": "matrix",
1439+
"difficulty": 4,
1440+
"topics": [
1441+
"files",
1442+
"searching",
1443+
"text_formatting"
1444+
]
14331445
}
14341446
]
14351447
}

exercises/grep/.eslintrc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"root": true,
3+
"parser": "babel-eslint",
4+
"parserOptions": {
5+
"ecmaVersion": 7,
6+
"sourceType": "module"
7+
},
8+
"env": {
9+
"es6": true,
10+
"node": true,
11+
"jest": true
12+
},
13+
"extends": [
14+
"eslint:recommended",
15+
"plugin:import/errors",
16+
"plugin:import/warnings"
17+
],
18+
"rules": {
19+
"linebreak-style": "off",
20+
21+
"import/extensions": "off",
22+
"import/no-default-export": "off",
23+
"import/no-unresolved": "off",
24+
"import/prefer-default-export": "off"
25+
}
26+
}

exercises/grep/README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Grep
2+
3+
Search a file for lines matching a regular expression pattern. Return the line
4+
number and contents of each matching line.
5+
6+
The Unix [`grep`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html) command can be used to search for lines in one or more files
7+
that match a user-provided search query (known as the *pattern*).
8+
9+
The `grep` command takes three arguments:
10+
11+
1. The pattern used to match lines in a file.
12+
2. Zero or more flags to customize the matching behavior.
13+
3. One or more files in which to search for matching lines.
14+
15+
Your task is to implement the `grep` function, which should read the contents
16+
of the specified files, find the lines that match the specified pattern
17+
and then output those lines as a single string. Note that the lines should
18+
be output in the order in which they were found, with the first matching line
19+
in the first file being output first.
20+
21+
As an example, suppose there is a file named "input.txt" with the following contents:
22+
23+
```text
24+
hello
25+
world
26+
hello again
27+
```
28+
29+
If we were to call `grep "hello" input.txt`, the returned string should be:
30+
31+
```text
32+
hello
33+
hello again
34+
```
35+
36+
### Flags
37+
38+
As said earlier, the `grep` command should also support the following flags:
39+
40+
- `-n` Print the line numbers of each matching line.
41+
- `-l` Print only the names of files that contain at least one matching line.
42+
- `-i` Match line using a case-insensitive comparison.
43+
- `-v` Invert the program -- collect all lines that fail to match the pattern.
44+
- `-x` Only match entire lines, instead of lines that contain a match.
45+
46+
If we run `grep -n "hello" input.txt`, the `-n` flag will require the matching
47+
lines to be prefixed with its line number:
48+
49+
```text
50+
1:hello
51+
3:hello again
52+
```
53+
54+
And if we run `grep -i "HELLO" input.txt`, we'll do a case-insensitive match,
55+
and the output will be:
56+
57+
```text
58+
hello
59+
hello again
60+
```
61+
62+
The `grep` command should support multiple flags at once.
63+
64+
For example, running `grep -l -v "hello" file1.txt file2.txt` should
65+
print the names of files that do not contain the string "hello".
66+
67+
## Setup
68+
69+
Go through the setup instructions for Javascript to install the necessary
70+
dependencies:
71+
72+
[https://exercism.io/tracks/javascript/installation](https://exercism.io/tracks/javascript/installation)
73+
74+
## Requirements
75+
76+
Install assignment dependencies:
77+
78+
```bash
79+
$ npm install
80+
```
81+
82+
## Making the test suite pass
83+
84+
Execute the tests with:
85+
86+
```bash
87+
$ npm test
88+
```
89+
90+
In the test suites all tests but the first have been skipped.
91+
92+
Once you get a test passing, you can enable the next one by changing `xtest` to
93+
`test`.
94+
95+
## Source
96+
97+
Conversation with Nate Foster. [http://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf](http://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf)
98+
99+
## Submitting Incomplete Solutions
100+
101+
It's possible to submit an incomplete solution so you can see how others have
102+
completed the exercise.

exercises/grep/babel.config.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = {
2+
presets: [
3+
[
4+
'@babel/env',
5+
{
6+
targets: {
7+
node: 'current',
8+
},
9+
useBuiltIns: false,
10+
},
11+
12+
],
13+
],
14+
};

exercises/grep/data/iliad.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Achilles sing, O Goddess! Peleus' son;
2+
His wrath pernicious, who ten thousand woes
3+
Caused to Achaia's host, sent many a soul
4+
Illustrious into Ades premature,
5+
And Heroes gave (so stood the will of Jove)
6+
To dogs and to all ravening fowls a prey,
7+
When fierce dispute had separated once
8+
The noble Chief Achilles from the son
9+
Of Atreus, Agamemnon, King of men.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
I do entreat your grace to pardon me.
2+
I know not by what power I am made bold,
3+
Nor how it may concern my modesty,
4+
In such a presence here to plead my thoughts;
5+
But I beseech your grace that I may know
6+
The worst that may befall me in this case,
7+
If I refuse to wed Demetrius.

exercises/grep/data/paradise-lost.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Of Mans First Disobedience, and the Fruit
2+
Of that Forbidden Tree, whose mortal tast
3+
Brought Death into the World, and all our woe,
4+
With loss of Eden, till one greater Man
5+
Restore us, and regain the blissful Seat,
6+
Sing Heav'nly Muse, that on the secret top
7+
Of Oreb, or of Sinai, didst inspire
8+
That Shepherd, who first taught the chosen Seed

exercises/grep/example.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env node
2+
const fs = require("fs");
3+
4+
// Helpers
5+
const availables_options = [
6+
"n", // add line numbers
7+
"l", // print file names where pattern is found
8+
"i", // ignore case
9+
"v", // reverse files results
10+
"x" // match entire line
11+
];
12+
13+
const does_line_matche_pattern = (line, pattern) => {
14+
let left = line;
15+
let right = pattern;
16+
17+
if (is_option_set("i")) {
18+
left = line.toLowerCase();
19+
right = pattern.toLowerCase();
20+
}
21+
22+
if (is_option_set("x")) {
23+
return left === right;
24+
}
25+
26+
return left.match(right) !== null;
27+
};
28+
29+
const getConfigFromArgs = () => {
30+
const config = {
31+
pattern: "",
32+
options: [],
33+
files: []
34+
};
35+
36+
let has_pattern_been_found = false;
37+
38+
process.argv.slice(2).forEach(val => {
39+
if (has_pattern_been_found) {
40+
config.files.push(val);
41+
} else if (val.indexOf("-") !== -1) {
42+
const option = val.replace("-", "");
43+
44+
if (!availables_options.includes(option)) {
45+
throw new Error(`Unknown option ${option}`);
46+
}
47+
48+
config.options.push(option);
49+
} else {
50+
has_pattern_been_found = true;
51+
config.pattern = val;
52+
}
53+
});
54+
55+
return config;
56+
};
57+
58+
const config = getConfigFromArgs();
59+
const is_option_set = option => config.options.includes(option);
60+
61+
// Actual script
62+
config.files.forEach(file => {
63+
const data = fs.readFileSync(file, { encoding: "utf-8" });
64+
65+
if (is_option_set("l")) {
66+
data.split("\n").find(line => {
67+
const does_line_match_pattern = does_line_matche_pattern(line, config.pattern);
68+
69+
return is_option_set("v") ? !does_line_match_pattern : does_line_match_pattern;
70+
}) && console.log(file);
71+
} else {
72+
data.split("\n").forEach((line, index) => {
73+
let result = "";
74+
let should_output_line = does_line_matche_pattern(line, config.pattern);
75+
76+
if (is_option_set("v")) {
77+
should_output_line = !should_output_line;
78+
}
79+
80+
if (should_output_line) {
81+
if (config.files.length > 1) {
82+
result += `${file}:`;
83+
}
84+
85+
if (is_option_set("n")) {
86+
result += `${index + 1}:`;
87+
}
88+
89+
result += line;
90+
91+
console.log(result);
92+
}
93+
});
94+
}
95+
});

exercises/grep/grep.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env node
2+
3+
// The above line is a shebang. On Unix-like operating systems, or environments, this will allow the script to be
4+
// run by node, and thus turn this JavaScript file into an executable. If you don't have a Unix-like operating
5+
// system or environment, for example Windows without WSL, you can use
6+
//
7+
// node grep.js args
8+
//
9+
// Instead of "./grep.js args".
10+
//
11+
// Read more about shebangs here: https://en.wikipedia.org/wiki/Shebang_(Unix)
12+
//
13+
// This is only a SKELETON file for the 'Grep' exercise. It's been provided as a
14+
// convenience to get you started writing code faster.
15+
//

0 commit comments

Comments
 (0)