diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt index dbdfd42856..429107fca5 100644 --- a/Userland/Utilities/CMakeLists.txt +++ b/Userland/Utilities/CMakeLists.txt @@ -56,3 +56,4 @@ target_link_libraries(gzip LibCompress) target_link_libraries(gunzip LibCompress) target_link_libraries(CppParserTest LibCpp LibGUI) target_link_libraries(PreprocessorTest LibCpp LibGUI) +target_link_libraries(file LibGfx LibIPC) diff --git a/Userland/Utilities/file.cpp b/Userland/Utilities/file.cpp new file mode 100644 index 0000000000..342724665a --- /dev/null +++ b/Userland/Utilities/file.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021, Valtteri Koskivuori + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static Optional description_only(String description, [[maybe_unused]] const String& path) +{ + return description; +} + +// FIXME: Ideally Gfx::ImageDecoder could tell us the image type directly. +static Optional image_details(const String description, const String& path) +{ + auto file_or_error = MappedFile::map(path); + if (file_or_error.is_error()) + return {}; + + auto& mapped_file = *file_or_error.value(); + auto image_decoder = Gfx::ImageDecoder::create((const u8*)mapped_file.data(), mapped_file.size()); + + if (!image_decoder->is_valid()) + return {}; + + return String::formatted("{}, {} x {}", description, image_decoder->width(), image_decoder->height()); +} + +#define ENUMERATE_DESCRIPTION_CONTENTS(V) \ + V(pbm, "image/x-portable-bitmap", "PBM image data", image_details) \ + V(pgm, "image/x-portable-graymap", "PGM image data", image_details) \ + V(png, "image/png", "PNG image data", image_details) \ + V(ppm, "image/x-portable-pixmap", "PPM image data", image_details) \ + V(gif_87, "image/gif", "GIF image data", image_details) \ + V(gif_89, "image/gif", "GIF image data", image_details) \ + V(bmp, "image/bmp", "BMP image data", image_details) \ + V(jpeg, "image/jpeg", "JPEG image data", image_details) \ + V(jpeg_jfif, "image/jpeg", "JFIF image data", image_details) \ + V(jpeg_huh, "image/jpeg", "JPEG image data", image_details) \ + V(shell, "text/x-shellscript", "POSIX shell script text executable", description_only) \ + V(json, "application/json", "JSON data", description_only) \ + V(javascript, "application/javascript", "JavaScript source", description_only) \ + V(markdown, "text/markdown", "Markdown document", description_only) + +#define V(var_name, mime_type, description, details) \ + static const String var_name = description; +ENUMERATE_DESCRIPTION_CONTENTS(V) +#undef V + +static Optional get_description_from_mime_type(String& mime, String path) +{ +#define V(var_name, mime_type, description, details) \ + if (String(mime_type) == mime) \ + return details(String(description), path).value_or({}); + ENUMERATE_DESCRIPTION_CONTENTS(V); +#undef V + return {}; +} + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Vector paths; + bool flag_mime_only = false; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Determine type of files"); + args_parser.add_option(flag_mime_only, "Only print mime type", "mime-type", 'I'); + args_parser.add_positional_argument(paths, "Files to identify", "files", Core::ArgsParser::Required::Yes); + args_parser.parse(argc, argv); + + for (auto path : paths) { + auto file = Core::File::construct(path); + if (!file->open(Core::File::ReadOnly)) { + perror(path); + return 1; + } + auto bytes = file->read(25); + auto file_name_guess = Core::guess_mime_type_based_on_filename(path); + auto mime_type = Core::guess_mime_type_based_on_sniffed_bytes(bytes.bytes()).value_or(file_name_guess); + auto human_readable_description = get_description_from_mime_type(mime_type, String(path)).value_or(mime_type); + outln("{}: {}", path, flag_mime_only ? mime_type : human_readable_description); + } + + return 0; +}