Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@
"practices": [],
"prerequisites": [],
"difficulty": 1
},
{
"slug": "grep",
"name": "Grep",
"uuid": "9b2dff70-335a-456b-9700-665d29704309",
"practices": [],
"prerequisites": [],
"difficulty": 4
}
]
},
Expand Down
27 changes: 27 additions & 0 deletions exercises/practice/grep/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Instructions

Search files for lines matching a search string and return all matching lines.

The Unix [`grep`][grep] command searches files for lines that match a regular expression.
Your task is to implement a simplified `grep` command, which supports searching for fixed strings.

The `grep` command takes three arguments:

1. The string to search for.
2. Zero or more flags for customizing the command's behavior.
3. One or more files to search in.

It then reads the contents of the specified files (in the order specified), finds the lines that contain the search string, and finally returns those lines in the order in which they were found.
When searching in multiple files, each matching line is prepended by the file name and a colon (':').

## Flags

The `grep` command supports the following flags:

- `-n` Prepend the line number and a colon (':') to each line in the output, placing the number after the filename (if present).
- `-l` Output only the names of the files that contain at least one matching line.
- `-i` Match using a case-insensitive comparison.
- `-v` Invert the program -- collect all lines that fail to match.
- `-x` Search only for lines where the search string matches the entire line.

[grep]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html
19 changes: 19 additions & 0 deletions exercises/practice/grep/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"quintuple-mallard"
],
"files": {
"solution": [
"grep.nu"
],
"test": [
"tests.nu"
],
"example": [
".meta/example.nu"
]
},
"blurb": "Search a file for lines matching a regular expression pattern. Return the line number and contents of each matching line.",
"source": "Conversation with Nate Foster.",
"source_url": "https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf"
}
60 changes: 60 additions & 0 deletions exercises/practice/grep/.meta/example.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
def make_options [flags: list<string>, files: list<string>] {
{
filename: (($files | length) != 1),
line_number: (-n in $flags),
insensitive: (-i in $flags),
names_only: (-l in $flags),
inverted: (-v in $flags),
fullline: (-x in $flags)
}
}

def grep-single [pattern: string, options: record, file: path] {
let contents = open --raw $file | split row "\n"
let contains = if $options.fullline {
{|pattern, line| $pattern == $line}
} else {
{|pattern, line| $pattern in $line}
}
let matches = $contents | enumerate | each {|line|
let idx = $line.index
mut line = $line.item
mut upcased = $line
mut pattern = $pattern
if $options.insensitive {
$upcased = $upcased | str upcase
$pattern = $pattern | str upcase
}

if $options.inverted xor (do $contains $pattern $upcased) {
if $options.line_number {
$line = $"($idx + 1):($line)"
}
if $options.filename {
$line = $"($file):($line)"
}
$line
}

}
if $options.names_only {
if ( $matches | length) > 0 {
[$file]
} else {
[]
}
} else {
$matches
}
}

export def main [pattern: string, flags: list<string>, files: list<string>] {
let options = make_options $flags $files


let results = $files | each {|file|
grep-single $pattern $options $file
}

$results | flatten | str join "\n"
}
85 changes: 85 additions & 0 deletions exercises/practice/grep/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[9049fdfd-53a7-4480-a390-375203837d09]
description = "Test grepping a single file -> One file, one match, no flags"

[76519cce-98e3-46cd-b287-aac31b1d77d6]
description = "Test grepping a single file -> One file, one match, print line numbers flag"

[af0b6d3c-e0e8-475e-a112-c0fc10a1eb30]
description = "Test grepping a single file -> One file, one match, case-insensitive flag"

[ff7af839-d1b8-4856-a53e-99283579b672]
description = "Test grepping a single file -> One file, one match, print file names flag"

[8625238a-720c-4a16-81f2-924ec8e222cb]
description = "Test grepping a single file -> One file, one match, match entire lines flag"

[2a6266b3-a60f-475c-a5f5-f5008a717d3e]
description = "Test grepping a single file -> One file, one match, multiple flags"

[842222da-32e8-4646-89df-0d38220f77a1]
description = "Test grepping a single file -> One file, several matches, no flags"

[4d84f45f-a1d8-4c2e-a00e-0b292233828c]
description = "Test grepping a single file -> One file, several matches, print line numbers flag"

[0a483b66-315b-45f5-bc85-3ce353a22539]
description = "Test grepping a single file -> One file, several matches, match entire lines flag"

[3d2ca86a-edd7-494c-8938-8eeed1c61cfa]
description = "Test grepping a single file -> One file, several matches, case-insensitive flag"

[1f52001f-f224-4521-9456-11120cad4432]
description = "Test grepping a single file -> One file, several matches, inverted flag"

[7a6ede7f-7dd5-4364-8bf8-0697c53a09fe]
description = "Test grepping a single file -> One file, no matches, various flags"

[3d3dfc23-8f2a-4e34-abd6-7b7d140291dc]
description = "Test grepping a single file -> One file, one match, file flag takes precedence over line flag"

[87b21b24-b788-4d6e-a68b-7afe9ca141fe]
description = "Test grepping a single file -> One file, several matches, inverted and match entire lines flags"

[ba496a23-6149-41c6-a027-28064ed533e5]
description = "Test grepping multiples files at once -> Multiple files, one match, no flags"

[4539bd36-6daa-4bc3-8e45-051f69f5aa95]
description = "Test grepping multiples files at once -> Multiple files, several matches, no flags"

[9fb4cc67-78e2-4761-8e6b-a4b57aba1938]
description = "Test grepping multiples files at once -> Multiple files, several matches, print line numbers flag"

[aeee1ef3-93c7-4cd5-af10-876f8c9ccc73]
description = "Test grepping multiples files at once -> Multiple files, one match, print file names flag"

[d69f3606-7d15-4ddf-89ae-01df198e6b6c]
description = "Test grepping multiples files at once -> Multiple files, several matches, case-insensitive flag"

[82ef739d-6701-4086-b911-007d1a3deb21]
description = "Test grepping multiples files at once -> Multiple files, several matches, inverted flag"

[77b2eb07-2921-4ea0-8971-7636b44f5d29]
description = "Test grepping multiples files at once -> Multiple files, one match, match entire lines flag"

[e53a2842-55bb-4078-9bb5-04ac38929989]
description = "Test grepping multiples files at once -> Multiple files, one match, multiple flags"

[9c4f7f9a-a555-4e32-bb06-4b8f8869b2cb]
description = "Test grepping multiples files at once -> Multiple files, no matches, various flags"

[ba5a540d-bffd-481b-bd0c-d9a30f225e01]
description = "Test grepping multiples files at once -> Multiple files, several matches, file flag takes precedence over line number flag"

[ff406330-2f0b-4b17-9ee4-4b71c31dd6d2]
description = "Test grepping multiples files at once -> Multiple files, several matches, inverted and match entire lines flags"
1 change: 1 addition & 0 deletions exercises/practice/grep/grep.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export def main [pattern: string, flags: list<string>, files: list<path>] { error make {msg: "Remove this line and implement grep"} }
138 changes: 138 additions & 0 deletions exercises/practice/grep/tests.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std/assert
use ./grep.nu
"Achilles sing, O Goddess! Peleus' son;
His wrath pernicious, who ten thousand woes
Caused to Achaia's host, sent many a soul
Illustrious into Ades premature,
And Heroes gave (so stood the will of Jove)
To dogs and to all ravening fowls a prey,
When fierce dispute had separated once
The noble Chief Achilles from the son
Of Atreus, Agamemnon, King of men." o> iliad.txt

"I do entreat your grace to pardon me.
I know not by what power I am made bold,
Nor how it may concern my modesty,
In such a presence here to plead my thoughts;
But I beseech your grace that I may know
The worst that may befall me in this case,
If I refuse to wed Demetrius." o> midsummer-night.txt
"Of Mans First Disobedience, and the Fruit
Of that Forbidden Tree, whose mortal tast
Brought Death into the World, and all our woe,
With loss of Eden, till one greater Man
Restore us, and regain the blissful Seat,
Sing Heav'nly Muse, that on the secret top
Of Oreb, or of Sinai, didst inspire
That Shepherd, who first taught the chosen Seed" o> paradise-lost.txt
# Prevent conflicts with the system grep command when run on Linux
let tmp_path = $env.PATH
$env.PATH = []
# One file, one match, no flags
assert equal (grep Agamemnon [] [iliad.txt]) "Of Atreus, Agamemnon, King of men."
# One file, one match, print line numbers flag
assert equal (grep Forbidden [-n] [paradise-lost.txt]) "2:Of that Forbidden Tree, whose mortal tast"
# One file, one match, case-insensitive flag
assert equal (grep FORBIDDEN [-i] [paradise-lost.txt]) "Of that Forbidden Tree, whose mortal tast"
# One file, one match, print file names flag
assert equal (grep Forbidden [-l] [paradise-lost.txt]) "paradise-lost.txt"
# One file, one match, match entire lines flag
assert equal (grep "With loss of Eden, till one greater Man" [-x] [paradise-lost.txt]) "With loss of Eden, till one greater Man"
# One file, one match, multiple flags
assert equal (grep "OF ATREUS, Agamemnon, KIng of MEN." [-n, -i, -x] [iliad.txt]) "9:Of Atreus, Agamemnon, King of men."
# One file, several matches, no flags
assert equal (grep may [] [midsummer-night.txt]) "Nor how it may concern my modesty,
But I beseech your grace that I may know
The worst that may befall me in this case,"
# One file, several matches, print line numbers flag
assert equal (grep may [-n] [midsummer-night.txt]) "3:Nor how it may concern my modesty,
5:But I beseech your grace that I may know
6:The worst that may befall me in this case,"
# One file, several matches, match entire lines flag
assert equal (grep may [-x] [midsummer-night.txt]) ""
# One file, several matches, case-insensitive flag
assert equal (grep ACHILLES [-i] [iliad.txt]) "Achilles sing, O Goddess! Peleus' son;
The noble Chief Achilles from the son"
# One file, several matches, inverted flag
assert equal (grep Of [-v] [paradise-lost.txt]) "Brought Death into the World, and all our woe,
With loss of Eden, till one greater Man
Restore us, and regain the blissful Seat,
Sing Heav'nly Muse, that on the secret top
That Shepherd, who first taught the chosen Seed"
# One file, no matches, various flags
assert equal (grep Gandalf [-n, -l, -x, -i] [iliad.txt]) ""
# One file, one match, file flag takes precedence over line flag
assert equal (grep ten [-n, -l] [iliad.txt]) "iliad.txt"
# One file, several matches, inverted and match entire lines flags
assert equal (grep "Illustrious into Ades premature," [-x, -v] [iliad.txt]) "Achilles sing, O Goddess! Peleus' son;
His wrath pernicious, who ten thousand woes
Caused to Achaia's host, sent many a soul
And Heroes gave (so stood the will of Jove)
To dogs and to all ravening fowls a prey,
When fierce dispute had separated once
The noble Chief Achilles from the son
Of Atreus, Agamemnon, King of men."
# Multiple files, one match, no flags
assert equal (grep Agamemnon [] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) "iliad.txt:Of Atreus, Agamemnon, King of men."
# Multiple files, several matches, no flags
assert equal (grep may [] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) "midsummer-night.txt:Nor how it may concern my modesty,
midsummer-night.txt:But I beseech your grace that I may know
midsummer-night.txt:The worst that may befall me in this case,"
# Multiple files, several matches, print line numbers flag
assert equal (grep that [-n] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) "midsummer-night.txt:5:But I beseech your grace that I may know
midsummer-night.txt:6:The worst that may befall me in this case,
paradise-lost.txt:2:Of that Forbidden Tree, whose mortal tast
paradise-lost.txt:6:Sing Heav'nly Muse, that on the secret top"
# Multiple files, one match, print file names flag
assert equal (grep who [-l] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) "iliad.txt
paradise-lost.txt"
# Multiple files, several matches, case-insensitive flag
assert equal (grep TO [-i] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) "iliad.txt:Caused to Achaia's host, sent many a soul
iliad.txt:Illustrious into Ades premature,
iliad.txt:And Heroes gave (so stood the will of Jove)
iliad.txt:To dogs and to all ravening fowls a prey,
midsummer-night.txt:I do entreat your grace to pardon me.
midsummer-night.txt:In such a presence here to plead my thoughts;
midsummer-night.txt:If I refuse to wed Demetrius.
paradise-lost.txt:Brought Death into the World, and all our woe,
paradise-lost.txt:Restore us, and regain the blissful Seat,
paradise-lost.txt:Sing Heav'nly Muse, that on the secret top"
# Multiple files, several matches, inverted flag
assert equal (grep a [-v] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) "iliad.txt:Achilles sing, O Goddess! Peleus' son;
iliad.txt:The noble Chief Achilles from the son
midsummer-night.txt:If I refuse to wed Demetrius."
# Multiple files, one match, match entire lines flag
assert equal (grep "But I beseech your grace that I may know" [-x] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) "midsummer-night.txt:But I beseech your grace that I may know"
# Multiple files, one match, multiple flags
assert equal (grep "WITH LOSS OF EDEN, TILL ONE GREATER MAN" [-n, -i, -x] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) "paradise-lost.txt:4:With loss of Eden, till one greater Man"
# Multiple files, no matches, various flags
assert equal (grep Frodo [-n, -l, -x, -i] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) ""
# Multiple files, several matches, file flag takes precedence over line number flag
assert equal (grep who [-n, -l] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) "iliad.txt
paradise-lost.txt"
# Multiple files, several matches, inverted and match entire lines flags
assert equal (grep "Illustrious into Ades premature," [-x, -v] [iliad.txt, midsummer-night.txt, paradise-lost.txt]) "iliad.txt:Achilles sing, O Goddess! Peleus' son;
iliad.txt:His wrath pernicious, who ten thousand woes
iliad.txt:Caused to Achaia's host, sent many a soul
iliad.txt:And Heroes gave (so stood the will of Jove)
iliad.txt:To dogs and to all ravening fowls a prey,
iliad.txt:When fierce dispute had separated once
iliad.txt:The noble Chief Achilles from the son
iliad.txt:Of Atreus, Agamemnon, King of men.
midsummer-night.txt:I do entreat your grace to pardon me.
midsummer-night.txt:I know not by what power I am made bold,
midsummer-night.txt:Nor how it may concern my modesty,
midsummer-night.txt:In such a presence here to plead my thoughts;
midsummer-night.txt:But I beseech your grace that I may know
midsummer-night.txt:The worst that may befall me in this case,
midsummer-night.txt:If I refuse to wed Demetrius.
paradise-lost.txt:Of Mans First Disobedience, and the Fruit
paradise-lost.txt:Of that Forbidden Tree, whose mortal tast
paradise-lost.txt:Brought Death into the World, and all our woe,
paradise-lost.txt:With loss of Eden, till one greater Man
paradise-lost.txt:Restore us, and regain the blissful Seat,
paradise-lost.txt:Sing Heav'nly Muse, that on the secret top
paradise-lost.txt:Of Oreb, or of Sinai, didst inspire
paradise-lost.txt:That Shepherd, who first taught the chosen Seed"
# Restore path
$env.PATH = $tmp_path