Serenity Operating System
at master 149 lines 4.9 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, Maxwell Trussell <maxtrussell@gmail.com> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/Assertions.h> 9#include <AK/JsonArray.h> 10#include <AK/JsonObject.h> 11#include <AK/JsonValue.h> 12#include <AK/StringBuilder.h> 13#include <AK/StringView.h> 14#include <AK/Types.h> 15#include <AK/Vector.h> 16#include <LibCore/ArgsParser.h> 17#include <LibCore/File.h> 18#include <LibCore/System.h> 19#include <LibMain/Main.h> 20#include <unistd.h> 21 22static JsonValue query(JsonValue const& value, Vector<StringView>& key_parts, size_t key_index = 0); 23static void print(JsonValue const& value, int spaces_per_indent, int indent = 0, bool use_color = true); 24static void print_indent(int indent, int spaces_per_indent) 25{ 26 for (int i = 0; i < indent * spaces_per_indent; ++i) 27 out(" "); 28} 29 30ErrorOr<int> serenity_main(Main::Arguments arguments) 31{ 32 TRY(Core::System::pledge("stdio rpath")); 33 34 StringView path; 35 StringView dotted_key; 36 int spaces_in_indent = 4; 37 38 Core::ArgsParser args_parser; 39 args_parser.set_general_help("Pretty-print a JSON file with syntax-coloring and indentation."); 40 args_parser.add_option(dotted_key, "Dotted query key", "query", 'q', "foo.*.bar"); 41 args_parser.add_option(spaces_in_indent, "Indent size", "indent-size", 'i', "spaces_in_indent"); 42 args_parser.add_positional_argument(path, "Path to JSON file", "path", Core::ArgsParser::Required::No); 43 VERIFY(spaces_in_indent >= 0); 44 args_parser.parse(arguments); 45 46 auto file = TRY(Core::File::open_file_or_standard_stream(path, Core::File::OpenMode::Read)); 47 48 TRY(Core::System::pledge("stdio")); 49 50 auto file_contents = TRY(file->read_until_eof()); 51 auto json = TRY(JsonValue::from_string(file_contents)); 52 if (!dotted_key.is_empty()) { 53 auto key_parts = dotted_key.split_view('.'); 54 json = query(json, key_parts); 55 } 56 57 print(json, spaces_in_indent, 0, isatty(STDOUT_FILENO)); 58 outln(); 59 60 return 0; 61} 62 63void print(JsonValue const& value, int spaces_per_indent, int indent, bool use_color) 64{ 65 if (value.is_object()) { 66 size_t printed_members = 0; 67 auto& object = value.as_object(); 68 outln("{{"); 69 object.for_each_member([&](auto& member_name, auto& member_value) { 70 ++printed_members; 71 print_indent(indent + 1, spaces_per_indent); 72 if (use_color) 73 out("\"\033[33;1m{}\033[0m\": ", member_name); 74 else 75 out("\"{}\": ", member_name); 76 print(member_value, spaces_per_indent, indent + 1, use_color); 77 if (printed_members < static_cast<size_t>(object.size())) 78 out(","); 79 outln(); 80 }); 81 print_indent(indent, spaces_per_indent); 82 out("}}"); 83 return; 84 } 85 if (value.is_array()) { 86 size_t printed_entries = 0; 87 auto array = value.as_array(); 88 outln("["); 89 array.for_each([&](auto& entry_value) { 90 ++printed_entries; 91 print_indent(indent + 1, spaces_per_indent); 92 print(entry_value, spaces_per_indent, indent + 1, use_color); 93 if (printed_entries < static_cast<size_t>(array.size())) 94 out(","); 95 outln(); 96 }); 97 print_indent(indent, spaces_per_indent); 98 out("]"); 99 return; 100 } 101 if (use_color) { 102 if (value.is_string()) 103 out("\033[31;1m"); 104 else if (value.is_number()) 105 out("\033[35;1m"); 106 else if (value.is_bool()) 107 out("\033[32;1m"); 108 else if (value.is_null()) 109 out("\033[34;1m"); 110 } 111 if (value.is_string()) 112 out("\""); 113 out("{}", value.to_deprecated_string()); 114 if (value.is_string()) 115 out("\""); 116 if (use_color) 117 out("\033[0m"); 118} 119 120JsonValue query(JsonValue const& value, Vector<StringView>& key_parts, size_t key_index) 121{ 122 if (key_index == key_parts.size()) 123 return value; 124 auto key = key_parts[key_index++]; 125 126 if (key == "*"sv) { 127 Vector<JsonValue> matches; 128 if (value.is_object()) { 129 value.as_object().for_each_member([&](auto&, auto& member_value) { 130 matches.append(query(member_value, key_parts, key_index)); 131 }); 132 } else if (value.is_array()) { 133 value.as_array().for_each([&](auto& member) { 134 matches.append(query(member, key_parts, key_index)); 135 }); 136 } 137 return JsonValue(JsonArray(matches)); 138 } 139 140 JsonValue result {}; 141 if (value.is_object()) { 142 result = value.as_object().get(key).value_or({}); 143 } else if (value.is_array()) { 144 auto key_as_index = key.to_int(); 145 if (key_as_index.has_value()) 146 result = value.as_array().at(key_as_index.value()); 147 } 148 return query(result, key_parts, key_index); 149}