Haio 0.2.X
Homebrew Asset IO/CDN
Loading...
Searching...
No Matches
cli.cpp
Go to the documentation of this file.
1#include <haio_cli.hpp>
3
4#include <algorithm>
5
6#include <filesystem>
7#include <fstream>
8#include <iostream>
9
10namespace Haio::Cli {
11namespace {
12
13/**
14 * the options, off the same table the parser reads and the grammar documents.
15 *
16 * it used to be a third copy of the list, which is why it was missing -filter, -limit
17 * and -palete entirely and offered a --size the parser did not take.
18 */
19void printUsage() {
20 std::cerr << "usage:\n"
21 << " haio convert input.png [filters] output.ppm\n"
22 << " haio convert png:- [filters] ppm:-\n"
23 << "\nfilters:\n";
24
25 const auto spelled = [](const Lexer::Option& option) {
26 auto out = std::string(option.spellings[0]);
27 if (option.args == 0) return out;
28 out += option.optional ? " [" + std::string(option.takes) + "]" : " " + std::string(option.takes);
29 return out;
30 };
31
32 size_t width = 0;
33 for (const auto& option : Lexer::options) {
34 if (option.filter) width = std::max(width, spelled(option).size());
35 }
36
37 for (const auto& option : Lexer::options) {
38 if (!option.filter) continue;
39 const auto line = spelled(option);
40 std::cerr << " " << line << std::string(width - line.size() + 2, ' ') << option.help << '\n';
41 }
42
43 std::cerr << "\nevery option also takes its value after an =, and several answer to more than\n"
44 "one spelling; docs/convert-ebnf.md lists them all.\n";
45}
46
47void printError(const ParseError& error) {
48 if (!error) return;
49 std::cerr << "[error] " << error.message;
50 if (!error.token.empty()) std::cerr << ": " << error.token;
51 std::cerr << '\n';
52}
53
54std::vector<uint8_t> readStream(std::istream& in) {
55 return {std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>()};
56}
57
58std::vector<uint8_t> readFile(const std::filesystem::path& path) {
59 std::ifstream in(path, std::ios::binary);
60 if (!in) throw std::runtime_error("could not open input: " + path.string());
61 return readStream(in);
62}
63
64Blob readInputBlob(Command& command) {
65 Blob blob;
66 blob.path = command.inputPath;
67 blob.format = command.inputFormat;
68 blob.contentType = contentTypeFor(blob.format);
69 blob.data = command.inputPath == "-" ? readStream(std::cin) : readFile(command.inputPath);
70
71 const auto found = Detect(blob.data);
72 const bool explicitFormat = !command.inputFormatName.empty();
73
74 if (found && found.format != command.inputFormat) {
75 std::cerr << "warning: " << command.inputPath << " is " << formatName(found.format)
76 << ", not " << formatName(command.inputFormat);
77 if (explicitFormat) {
78 // the prefix was asked for on purpose, so it is honoured and only flagged
79 std::cerr << "; decoding as " << formatName(command.inputFormat) << " anyway\n";
80 } else {
81 std::cerr << "; decoding as " << formatName(found.format) << '\n';
82 command.inputFormat = found.format;
83 blob.format = found.format;
84 blob.color = found.color;
85 blob.contentType = contentTypeFor(found.format);
86 }
87 } else if (found) {
88 blob.color = found.color;
89 }
90
91 return blob;
92}
93
94void writeOutput(const Command& command, const std::vector<uint8_t>& data) {
95 if (command.outputIsStdout) {
96 std::cout.write(reinterpret_cast<const char*>(data.data()), static_cast<std::streamsize>(data.size()));
97 if (!std::cout) throw std::runtime_error("could not write output: stdout");
98 return;
99 }
100
101 std::ofstream out(command.outputPath, std::ios::binary);
102 if (!out) throw std::runtime_error("could not open output: " + command.outputPath);
103 out.write(reinterpret_cast<const char*>(data.data()), static_cast<std::streamsize>(data.size()));
104 if (!out) throw std::runtime_error("could not write output: " + command.outputPath);
105}
106
107}
108
109int runCli(int argc, char* argv[]) {
110 if (argc <= 2) {
111 printUsage();
112 return 1;
113 }
114
115 auto command = parseArgs(argc, argv);
116 if (command.error) {
117 printError(command.error);
118 return 1;
119 }
120
121 try {
122 if (command.hasGenerator) {
123 (void)buildPipeline(command);
124 }
125
126 auto input = readInputBlob(command);
127 const auto pipeline = buildPipeline(command);
128 const auto output = runPipeline(std::move(input), pipeline);
129 if (!output) {
130 std::cerr << "[error] " << output.error().message << '\n';
131 return 1;
132 }
133
134 writeOutput(command, output->data);
135 return 0;
136 } catch (const std::exception& err) {
137 std::cerr << "[error] " << err.what() << '\n';
138 return 1;
139 }
140}
141
142} // namespace Haio::Cli
constexpr Option options[]
Pipeline buildPipeline(const Command &command)
Command parseArgs(int argc, char *argv[])
Definition parser.cpp:544
int runCli(int argc, char *argv[])
Definition cli.cpp:109
Found Detect(Bytes data)
Result< Blob > runPipeline(Blob input, const Pipeline &pipeline, std::optional< std::chrono::steady_clock::time_point > deadline=std::nullopt)
Definition convert.cpp:15
constexpr std::string_view formatName(Format wanted)
std::string_view contentTypeFor(Format format)
Definition format.cpp:62