2021-07-14 07:51:04 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2021, the SerenityOS developers.
|
2022-09-20 10:56:33 +00:00
|
|
|
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
2021-07-14 07:51:04 +00:00
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <LibCore/ArgsParser.h>
|
2023-02-09 02:02:46 +00:00
|
|
|
#include <LibCore/File.h>
|
2021-11-27 19:50:00 +00:00
|
|
|
#include <LibCore/System.h>
|
2023-03-21 15:35:30 +00:00
|
|
|
#include <LibFileSystem/FileSystem.h>
|
2021-11-27 19:50:00 +00:00
|
|
|
#include <LibMain/Main.h>
|
2021-07-14 07:51:04 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <strings.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#define COL1_COLOR "\x1B[32m{}\x1B[0m"
|
|
|
|
#define COL2_COLOR "\x1B[34m{}\x1B[0m"
|
|
|
|
#define COL3_COLOR "\x1B[31m{}\x1B[0m"
|
|
|
|
|
2021-11-27 19:50:00 +00:00
|
|
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
2021-07-14 07:51:04 +00:00
|
|
|
{
|
2022-04-03 23:16:06 +00:00
|
|
|
TRY(Core::System::pledge("stdio rpath"));
|
2021-07-14 07:51:04 +00:00
|
|
|
|
2022-12-04 18:02:33 +00:00
|
|
|
DeprecatedString file1_path;
|
|
|
|
DeprecatedString file2_path;
|
2021-07-14 07:51:04 +00:00
|
|
|
bool suppress_col1 { false };
|
|
|
|
bool suppress_col2 { false };
|
|
|
|
bool suppress_col3 { false };
|
|
|
|
bool case_insensitive { false };
|
|
|
|
bool color { false };
|
|
|
|
bool no_color { false };
|
|
|
|
bool print_total { false };
|
|
|
|
|
|
|
|
Core::ArgsParser args_parser;
|
|
|
|
args_parser.set_general_help("Compare two sorted files line by line");
|
|
|
|
args_parser.add_option(suppress_col1, "Suppress column 1 (lines unique to file1)", nullptr, '1');
|
|
|
|
args_parser.add_option(suppress_col2, "Suppress column 2 (lines unique to file2)", nullptr, '2');
|
|
|
|
args_parser.add_option(suppress_col3, "Suppress column 3 (lines common to both files)", nullptr, '3');
|
|
|
|
args_parser.add_option(case_insensitive, "Use case-insensitive comparison of lines", nullptr, 'i');
|
|
|
|
args_parser.add_option(color, "Always print colored output", "color", 'c');
|
|
|
|
args_parser.add_option(no_color, "Do not print colored output", "no-color", 0);
|
|
|
|
args_parser.add_option(print_total, "Print a summary", "total", 't');
|
|
|
|
args_parser.add_positional_argument(file1_path, "First file to compare", "file1");
|
|
|
|
args_parser.add_positional_argument(file2_path, "Second file to compare", "file2");
|
2021-11-27 19:50:00 +00:00
|
|
|
args_parser.parse(arguments);
|
2021-07-14 07:51:04 +00:00
|
|
|
|
|
|
|
if (color && no_color) {
|
|
|
|
warnln("Cannot specify 'color' and 'no-color' together");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2022-09-20 10:56:33 +00:00
|
|
|
bool print_color = TRY(Core::System::isatty(STDOUT_FILENO));
|
2021-07-14 07:51:04 +00:00
|
|
|
if (color)
|
|
|
|
print_color = true;
|
|
|
|
else if (no_color)
|
|
|
|
print_color = false;
|
|
|
|
|
|
|
|
if (file1_path == "-" && file2_path == "-") {
|
|
|
|
warnln("File1 and file2 cannot both be the standard input");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2022-12-04 18:02:33 +00:00
|
|
|
auto open_file = [](DeprecatedString const& path, auto& file, int file_number) {
|
2023-02-09 02:02:46 +00:00
|
|
|
auto file_or_error = Core::File::open_file_or_standard_stream(path, Core::File::OpenMode::Read);
|
2022-09-20 10:56:33 +00:00
|
|
|
if (file_or_error.is_error()) {
|
|
|
|
warnln("Failed to open file{} '{}': {}", file_number, path, file_or_error.error());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-03-21 15:35:30 +00:00
|
|
|
if (path != "-" && FileSystem::is_directory(path)) {
|
2022-09-20 10:56:33 +00:00
|
|
|
warnln("Failed to open file{} '{}': is a directory", file_number, path);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-05-03 22:45:18 +00:00
|
|
|
auto buffered_file_or_error = Core::InputBufferedFile::create(file_or_error.release_value());
|
2022-09-20 10:56:33 +00:00
|
|
|
if (buffered_file_or_error.is_error()) {
|
|
|
|
warnln("Failed to create buffer for file{} '{}': {}", file_number, path, buffered_file_or_error.error());
|
|
|
|
return false;
|
2021-07-14 07:51:04 +00:00
|
|
|
}
|
2022-09-20 10:56:33 +00:00
|
|
|
|
|
|
|
file = buffered_file_or_error.release_value();
|
2021-07-14 07:51:04 +00:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2023-05-03 22:45:18 +00:00
|
|
|
OwnPtr<Core::InputBufferedFile> file1;
|
|
|
|
OwnPtr<Core::InputBufferedFile> file2;
|
2022-09-20 10:56:33 +00:00
|
|
|
if (!(open_file(file1_path, file1, 1) && open_file(file2_path, file2, 2)))
|
2021-07-14 07:51:04 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
char tab { '\t' };
|
|
|
|
size_t tab_count { 0 };
|
2022-12-04 18:02:33 +00:00
|
|
|
DeprecatedString col1_fmt;
|
|
|
|
DeprecatedString col2_fmt;
|
|
|
|
DeprecatedString col3_fmt;
|
2021-07-14 07:51:04 +00:00
|
|
|
if (!suppress_col1)
|
2022-12-04 18:02:33 +00:00
|
|
|
col1_fmt = DeprecatedString::formatted("{}{}", DeprecatedString::repeated(tab, tab_count++), print_color ? COL1_COLOR : "{}");
|
2021-07-14 07:51:04 +00:00
|
|
|
if (!suppress_col2)
|
2022-12-04 18:02:33 +00:00
|
|
|
col2_fmt = DeprecatedString::formatted("{}{}", DeprecatedString::repeated(tab, tab_count++), print_color ? COL2_COLOR : "{}");
|
2021-07-14 07:51:04 +00:00
|
|
|
if (!suppress_col3)
|
2022-12-04 18:02:33 +00:00
|
|
|
col3_fmt = DeprecatedString::formatted("{}{}", DeprecatedString::repeated(tab, tab_count++), print_color ? COL3_COLOR : "{}");
|
2021-07-14 07:51:04 +00:00
|
|
|
|
2022-12-04 18:02:33 +00:00
|
|
|
auto cmp = [&](DeprecatedString const& str1, DeprecatedString const& str2) {
|
2021-07-14 07:51:04 +00:00
|
|
|
if (case_insensitive)
|
|
|
|
return strcasecmp(str1.characters(), str2.characters());
|
2022-09-20 10:56:33 +00:00
|
|
|
return strcmp(str1.characters(), str2.characters());
|
2021-07-14 07:51:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
bool read_file1 { true };
|
|
|
|
bool read_file2 { true };
|
|
|
|
int col1_count { 0 };
|
|
|
|
int col2_count { 0 };
|
|
|
|
int col3_count { 0 };
|
2022-12-04 18:02:33 +00:00
|
|
|
DeprecatedString file1_line;
|
|
|
|
DeprecatedString file2_line;
|
2022-09-20 10:56:33 +00:00
|
|
|
Array<u8, PAGE_SIZE> buffer;
|
|
|
|
|
|
|
|
auto should_continue_comparing_files = [&]() {
|
|
|
|
if (read_file1) {
|
|
|
|
auto can_read_file1_line = file1->can_read_line();
|
|
|
|
if (can_read_file1_line.is_error() || !can_read_file1_line.value())
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (read_file2) {
|
|
|
|
auto can_read_file2_line = file2->can_read_line();
|
|
|
|
if (can_read_file2_line.is_error() || !can_read_file2_line.value())
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
2021-07-14 07:51:04 +00:00
|
|
|
|
2022-09-20 10:56:33 +00:00
|
|
|
while (should_continue_comparing_files()) {
|
2021-07-14 07:51:04 +00:00
|
|
|
if (read_file1)
|
2022-09-20 10:56:33 +00:00
|
|
|
file1_line = TRY(file1->read_line(buffer));
|
2021-07-14 07:51:04 +00:00
|
|
|
if (read_file2)
|
2022-09-20 10:56:33 +00:00
|
|
|
file2_line = TRY(file2->read_line(buffer));
|
2021-07-14 07:51:04 +00:00
|
|
|
|
|
|
|
int cmp_result = cmp(file1_line, file2_line);
|
|
|
|
|
2022-09-20 10:56:33 +00:00
|
|
|
if (cmp_result == 0) {
|
2021-07-14 07:51:04 +00:00
|
|
|
++col3_count;
|
|
|
|
read_file1 = read_file2 = true;
|
|
|
|
if (!suppress_col3)
|
|
|
|
outln(col3_fmt, file1_line);
|
|
|
|
} else if (cmp_result < 0) {
|
|
|
|
++col1_count;
|
|
|
|
read_file1 = true;
|
|
|
|
read_file2 = false;
|
|
|
|
if (!suppress_col1)
|
|
|
|
outln(col1_fmt, file1_line);
|
|
|
|
} else {
|
|
|
|
++col2_count;
|
|
|
|
read_file1 = false;
|
|
|
|
read_file2 = true;
|
|
|
|
if (!suppress_col2)
|
|
|
|
outln(col2_fmt, file2_line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-13 15:15:53 +00:00
|
|
|
// If the most recent line read was not a match, then the last line read from one of the files has not yet been output.
|
|
|
|
// So let's output it!
|
|
|
|
if (!read_file1 && !suppress_col1) {
|
|
|
|
++col1_count;
|
|
|
|
outln(col1_fmt, file1_line);
|
|
|
|
} else if (!read_file2 && !suppress_col2) {
|
|
|
|
++col2_count;
|
|
|
|
outln(col2_fmt, file2_line);
|
|
|
|
}
|
|
|
|
|
2022-12-04 18:02:33 +00:00
|
|
|
auto process_remaining = [&](DeprecatedString const& fmt, auto& file, int& count, bool print) {
|
2022-09-20 10:56:33 +00:00
|
|
|
while (true) {
|
|
|
|
auto can_read_result = file->can_read_line();
|
|
|
|
if (can_read_result.is_error() || !can_read_result.value())
|
|
|
|
break;
|
|
|
|
++count;
|
|
|
|
auto line = file->read_line(buffer);
|
|
|
|
if (line.is_error())
|
|
|
|
break;
|
|
|
|
if (print)
|
|
|
|
outln(fmt, line.value());
|
|
|
|
}
|
|
|
|
};
|
2021-07-14 07:51:04 +00:00
|
|
|
process_remaining(col1_fmt, file1, col1_count, !suppress_col1);
|
|
|
|
process_remaining(col2_fmt, file2, col2_count, !suppress_col2);
|
|
|
|
|
|
|
|
if (print_total)
|
2022-07-11 17:32:29 +00:00
|
|
|
outln(print_color ? COL1_COLOR "\t" COL2_COLOR "\t" COL3_COLOR "\ttotal"sv : "{}\t{}\t{}\ttotal"sv, col1_count, col2_count, col3_count);
|
2021-07-14 07:51:04 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|