mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 08:54:58 +00:00
Utilities: Add strings
This commit is contained in:
parent
e3112a3d2e
commit
8677dbfc7f
3 changed files with 180 additions and 1 deletions
140
Userland/Utilities/strings.cpp
Normal file
140
Userland/Utilities/strings.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/Stream.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibMain/Main.h>
|
||||
#include <unistd.h>
|
||||
|
||||
enum class StringOffsetFormat {
|
||||
None = 0,
|
||||
Decimal,
|
||||
Octal,
|
||||
Hexadecimal
|
||||
};
|
||||
|
||||
// NOTE: This is similar to how the cat utility works in the sense of aggregating
|
||||
// data in 32K buffer.
|
||||
static constexpr size_t buffer_read_size = 32768;
|
||||
|
||||
static bool should_print_characters(Vector<u8> const& characters)
|
||||
{
|
||||
for (u8 ch : characters) {
|
||||
if (is_ascii_printable(ch) && !is_ascii_space(ch))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void print_characters(Vector<u8> const& characters, StringOffsetFormat string_offset_format, size_t string_offset_position)
|
||||
{
|
||||
switch (string_offset_format) {
|
||||
case StringOffsetFormat::Decimal:
|
||||
out("{:>7d} ", string_offset_position);
|
||||
break;
|
||||
case StringOffsetFormat::Octal:
|
||||
out("{:>7o} ", string_offset_position);
|
||||
break;
|
||||
case StringOffsetFormat::Hexadecimal:
|
||||
out("{:>7x} ", string_offset_position);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
outln("{:s}", characters.span());
|
||||
}
|
||||
|
||||
static int process_characters_in_span(Vector<u8>& characters, ReadonlyBytes span)
|
||||
{
|
||||
int processed_characters = 0;
|
||||
for (u8 ch : span) {
|
||||
++processed_characters;
|
||||
if (is_ascii_printable(ch) || ch == '\t')
|
||||
characters.append(ch);
|
||||
else
|
||||
break;
|
||||
}
|
||||
return processed_characters;
|
||||
}
|
||||
|
||||
static ErrorOr<void> process_strings_in_file(StringView path, bool show_paths, StringOffsetFormat string_offset_format, size_t minimum_string_length)
|
||||
{
|
||||
Array<u8, buffer_read_size> buffer;
|
||||
Vector<u8> output_characters;
|
||||
auto file = TRY(Core::Stream::File::open_file_or_standard_stream(path, Core::Stream::OpenMode::Read));
|
||||
size_t processed_characters = 0;
|
||||
size_t string_offset_position = 0;
|
||||
bool did_show_path = false;
|
||||
while (!file->is_eof()) {
|
||||
auto buffer_span = TRY(file->read(buffer));
|
||||
while (!buffer_span.is_empty()) {
|
||||
string_offset_position += processed_characters;
|
||||
processed_characters = process_characters_in_span(output_characters, buffer_span);
|
||||
if (show_paths && !did_show_path) {
|
||||
outln("path {}:", path);
|
||||
did_show_path = true;
|
||||
}
|
||||
if (output_characters.size() >= minimum_string_length && should_print_characters(output_characters)) {
|
||||
print_characters(output_characters, string_offset_format, string_offset_position);
|
||||
}
|
||||
buffer_span = buffer_span.slice(processed_characters);
|
||||
output_characters.clear();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||
{
|
||||
TRY(Core::System::pledge("stdio rpath"));
|
||||
|
||||
Vector<StringView> paths;
|
||||
size_t minimum_string_length = 4;
|
||||
bool show_paths = false;
|
||||
|
||||
StringOffsetFormat string_offset_format { StringOffsetFormat::None };
|
||||
|
||||
Core::ArgsParser args_parser;
|
||||
args_parser.add_option(minimum_string_length, "Specify the minimum string length.", nullptr, 'n', "number");
|
||||
args_parser.add_option(show_paths, "Display the path for each matched file.", nullptr, 'p');
|
||||
args_parser.add_option({ Core::ArgsParser::OptionArgumentMode::Required,
|
||||
"Write offset relative to start of each file in (d)ec, (o)ct, or he(x) format.",
|
||||
nullptr,
|
||||
't',
|
||||
"format",
|
||||
[&string_offset_format](char const* s) {
|
||||
StringView value = { s, strlen(s) };
|
||||
if (value == "d") {
|
||||
string_offset_format = StringOffsetFormat::Decimal;
|
||||
} else if (value == "o") {
|
||||
string_offset_format = StringOffsetFormat::Octal;
|
||||
} else if (value == "x") {
|
||||
string_offset_format = StringOffsetFormat::Hexadecimal;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} });
|
||||
args_parser.set_general_help("Write the sequences of printable characters in files or pipes to stdout.");
|
||||
args_parser.add_positional_argument(paths, "File path", "path", Core::ArgsParser::Required::No);
|
||||
args_parser.parse(arguments);
|
||||
|
||||
if (minimum_string_length < 1) {
|
||||
warnln("Invalid minimum string length {}", minimum_string_length);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (paths.is_empty())
|
||||
paths.append("-"sv);
|
||||
|
||||
for (auto const& path : paths)
|
||||
TRY(process_strings_in_file(path, show_paths, string_offset_format, minimum_string_length));
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue