Serenity Operating System
at master 136 lines 3.7 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, Peter Elliott <pelliott@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/DeprecatedString.h> 9#include <AK/HashMap.h> 10#include <AK/QuickSort.h> 11#include <AK/Vector.h> 12#include <LibCore/ArgsParser.h> 13#include <LibCore/File.h> 14#include <LibCore/System.h> 15#include <LibMain/Main.h> 16#include <ctype.h> 17 18struct Line { 19 StringView key; 20 long int numeric_key; 21 DeprecatedString line; 22 bool numeric; 23 24 bool operator<(Line const& other) const 25 { 26 if (numeric) 27 return numeric_key < other.numeric_key; 28 29 return key < other.key; 30 } 31 32 bool operator==(Line const& other) const 33 { 34 if (numeric) 35 return numeric_key == other.numeric_key; 36 37 return key == other.key; 38 } 39 40private: 41}; 42 43template<> 44struct AK::Traits<Line> : public GenericTraits<Line> { 45 static unsigned hash(Line l) 46 { 47 if (l.numeric) 48 return l.numeric_key; 49 50 return l.key.hash(); 51 } 52}; 53 54struct Options { 55 size_t key_field { 0 }; 56 bool unique { false }; 57 bool numeric { false }; 58 bool reverse { false }; 59 StringView separator { "\0", 1 }; 60 Vector<DeprecatedString> files; 61}; 62 63static ErrorOr<void> load_file(Options options, StringView filename, Vector<Line>& lines, HashTable<Line>& seen) 64{ 65 auto file = TRY(Core::BufferedFile::create( 66 TRY(Core::File::open_file_or_standard_stream(filename, Core::File::OpenMode::Read)))); 67 68 // FIXME: Unlimited line length 69 auto buffer = TRY(ByteBuffer::create_uninitialized(4096)); 70 while (TRY(file->can_read_line())) { 71 DeprecatedString line = TRY(file->read_line(buffer)); 72 73 StringView key = line; 74 if (options.key_field != 0) { 75 auto split = (options.separator[0]) 76 ? line.split_view(options.separator[0]) 77 : line.split_view(isspace); 78 if (options.key_field - 1 >= split.size()) { 79 key = ""sv; 80 } else { 81 key = split[options.key_field - 1]; 82 } 83 } 84 85 Line l = { key, key.to_int().value_or(0), line, options.numeric }; 86 87 if (!options.unique || !seen.contains(l)) { 88 lines.append(l); 89 if (options.unique) 90 seen.set(l); 91 } 92 } 93 94 return {}; 95} 96 97ErrorOr<int> serenity_main([[maybe_unused]] Main::Arguments arguments) 98{ 99 TRY(Core::System::pledge("stdio rpath")); 100 101 Options options; 102 103 Core::ArgsParser args_parser; 104 args_parser.add_option(options.key_field, "The field to sort by", "key-field", 'k', "keydef"); 105 args_parser.add_option(options.unique, "Don't emit duplicate lines", "unique", 'u'); 106 args_parser.add_option(options.numeric, "treat the key field as a number", "numeric", 'n'); 107 args_parser.add_option(options.separator, "The separator to split fields by", "sep", 't', "char"); 108 args_parser.add_option(options.reverse, "Sort in reverse order", "reverse", 'r'); 109 args_parser.add_positional_argument(options.files, "Files to sort", "file", Core::ArgsParser::Required::No); 110 args_parser.parse(arguments); 111 112 Vector<Line> lines; 113 HashTable<Line> seen; 114 115 if (options.files.size() == 0) { 116 TRY(load_file(options, "-"sv, lines, seen)); 117 } else { 118 for (auto& file : options.files) { 119 TRY(load_file(options, file, lines, seen)); 120 } 121 } 122 123 quick_sort(lines); 124 125 auto print_lines = [](auto const& lines) { 126 for (auto& line : lines) 127 outln("{}", line.line); 128 }; 129 130 if (options.reverse) 131 print_lines(lines.in_reverse()); 132 else 133 print_lines(lines); 134 135 return 0; 136}