Skip to content

Commit

Permalink
Utilities/cat: Add -b to number non-blank lines
Browse files Browse the repository at this point in the history
  • Loading branch information
ebanner committed Aug 12, 2024
1 parent cf82101 commit 477134e
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 14 deletions.
7 changes: 6 additions & 1 deletion Base/usr/share/man/man1/cat.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ cat - concatenate files to stdout
## Synopsis

```**sh
$ cat [file...]
$ cat [options...] [files...]
```

## Description

This program passes contents of specified `files` to standard output, in the specified order. If no `file` is specified, or it is `-`, it defaults to standard input.

## Options

* `-n`, `--number`: Number all output lines
* `-b`, `--number-non-blank`: Number non-blank output lines

## Arguments

* `file`: Files to print
Expand Down
1 change: 1 addition & 0 deletions Tests/Utilities/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ set(TEST_SOURCES
TestSed.cpp
TestPatch.cpp
TestUniq.cpp
TestCat.cpp
)

foreach(source IN LISTS TEST_SOURCES)
Expand Down
45 changes: 45 additions & 0 deletions Tests/Utilities/TestCat.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <AK/ScopeGuard.h>
#include <AK/StringView.h>
#include <LibCore/Command.h>
#include <LibCore/File.h>
#include <LibTest/Macros.h>
#include <LibTest/TestCase.h>

static void run_cat(Vector<char const*>&& arguments, StringView standard_input, StringView expected_stdout)
{
MUST(arguments.try_insert(0, "cat"));
MUST(arguments.try_append(nullptr));
auto cat = MUST(Core::Command::create("cat"sv, arguments.data()));
MUST(cat->write(standard_input));
auto [stdout, stderr] = MUST(cat->read_all());
auto status = MUST(cat->status());
if (status != Core::Command::ProcessResult::DoneWithZeroExitCode) {
FAIL(ByteString::formatted("cat didn't exit cleanly: status: {}, stdout:{}, stderr: {}", static_cast<int>(status), StringView { stdout.bytes() }, StringView { stderr.bytes() }));
}

EXPECT_EQ(StringView { expected_stdout.bytes() }, StringView { stdout.bytes() });
}

TEST_CASE(show_lines)
{
run_cat({ "-n" }, "hello"sv, " 1\thello"sv);
run_cat({ "-n" }, "hello\nworld"sv, " 1\thello\n 2\tworld"sv);
run_cat({ "-n" }, "hello\n\nworld"sv, " 1\thello\n 2\t\n 3\tworld"sv);
run_cat({ "-n" }, "\nhello"sv, " 1\t\n 2\thello"sv);
run_cat({ "-n" }, "hello\n"sv, " 1\thello\n"sv);
run_cat({ "-n" }, "hello\n\n"sv, " 1\thello\n 2\t\n"sv);
}

TEST_CASE(show_only_non_blank_lines)
{
run_cat({ "-b" }, "hello"sv, " 1\thello"sv);
run_cat({ "-b" }, "hello\nworld"sv, " 1\thello\n 2\tworld"sv);
run_cat({ "-b" }, "hello\n\nworld"sv, " 1\thello\n\n 2\tworld"sv);
run_cat({ "-b" }, "\nhello"sv, "\n 1\thello"sv);
run_cat({ "-b" }, "hello\n"sv, " 1\thello\n"sv);
run_cat({ "-b" }, "hello\n\n"sv, " 1\thello\n\n"sv);
}
44 changes: 31 additions & 13 deletions Userland/Utilities/cat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,36 @@

struct LineTracker {
size_t line_count = 1;
bool display_line_number = true;
enum state : u8 {
LINE,
NEWLINES,
} state = NEWLINES;
};

static void output_buffer_with_line_numbers(LineTracker& line_tracker, ReadonlyBytes buffer_span)
static void output_buffer_with_line_numbers(LineTracker& line_tracker, ReadonlyBytes buffer_span, bool show_lines = true)
{
for (auto const curr_value : buffer_span) {
if (line_tracker.display_line_number) {
out("{: >6}\t", line_tracker.line_count);
line_tracker.line_count++;
line_tracker.display_line_number = false;
for (size_t i = 0; i < buffer_span.size(); i++) {
if (line_tracker.state == LineTracker::state::LINE) {
if (buffer_span[i] == '\n') {
out("{:c}", buffer_span[i]);
line_tracker.state = LineTracker::state::NEWLINES;
} else {
out("{:c}", buffer_span[i]);
}
} else if (line_tracker.state == LineTracker::state::NEWLINES) {
if (buffer_span[i] == '\n') {
if (show_lines) {
out("{: >6}\t", line_tracker.line_count);
line_tracker.line_count++;
}
out("{:c}", buffer_span[i]);
} else {
out("{: >6}\t", line_tracker.line_count);
line_tracker.line_count++;
out("{:c}", buffer_span[i]);
line_tracker.state = LineTracker::state::LINE;
}
}
if (curr_value == '\n')
line_tracker.display_line_number = true;
out("{:c}", curr_value);
}
}

Expand All @@ -36,11 +52,13 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)

Vector<StringView> paths;
bool show_lines = false;
bool show_only_blank_lines = false;

Core::ArgsParser args_parser;
args_parser.set_general_help("Concatenate files or pipes to stdout.");
args_parser.add_positional_argument(paths, "File path", "path", Core::ArgsParser::Required::No);
args_parser.add_option(show_lines, "Number all output lines", "number", 'n');
args_parser.add_option(show_only_blank_lines, "Number all non-blank output lines", "number-non-blank", 'b');
args_parser.parse(arguments);

if (paths.is_empty())
Expand All @@ -58,15 +76,15 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)

TRY(Core::System::pledge("stdio"));

// used only if we are using the -n option
// used only if we are using the -n or -b option
LineTracker line_tracker;

Array<u8, 32768> buffer;
for (auto const& file : files) {
while (!file->is_eof()) {
auto const buffer_span = TRY(file->read_some(buffer));
if (show_lines) {
output_buffer_with_line_numbers(line_tracker, buffer_span);
if (show_lines || show_only_blank_lines) {
output_buffer_with_line_numbers(line_tracker, buffer_span, show_lines);
} else {
out("{:s}", buffer_span);
}
Expand Down

0 comments on commit 477134e

Please sign in to comment.