From 6232d13f5321b87ddf12a1aa36b4545da45f173d Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Wed, 17 Nov 2021 22:34:57 +0100 Subject: Travel3: Simple image and video display site Reads the images and videos from filesystem and builds a site in memroy. --- src/args.cc | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 src/args.cc (limited to 'src/args.cc') diff --git a/src/args.cc b/src/args.cc new file mode 100644 index 0000000..243b284 --- /dev/null +++ b/src/args.cc @@ -0,0 +1,291 @@ +#include "common.hh" + +#include "args.hh" + +#include +#include +#include + +namespace { + +class OptionImpl : public Option { +public: + OptionImpl(char short_name, std::string long_name, std::string description, + bool require_arg, std::string arg_description) + : short_name_(short_name), + long_name_(std::move(long_name)), + description_(std::move(description)), + require_arg_(require_arg), + arg_description_(std::move(arg_description)) { + } + + bool is_set() const override { return set_; } + + std::string const& arg() const override { return arg_; } + + char short_name() const { return short_name_; } + + std::string const& long_name() const { return long_name_; } + + std::string const& description() const { return description_; } + + bool require_arg() const { return require_arg_; } + + std::string const& arg_description() const { return arg_description_; } + + void reset() { + set_ = false; + arg_.clear(); + } + + void set() { + set_ = true; + } + + void set_arg(std::string arg) { + arg_ = std::move(arg); + } + +private: + char const short_name_; + std::string const long_name_; + std::string const description_; + bool const require_arg_; + std::string const arg_description_; + bool set_ = false; + std::string arg_; +}; + +class ArgsImpl : public Args { +public: + ArgsImpl() = default; + + Option const* add_option(char short_name, std::string long_name, + std::string description) override { + prepare_option(short_name, long_name); + options_.push_back(std::make_unique(short_name, + std::move(long_name), + std::move(description), + false, std::string())); + return options_.back().get(); + } + + Option const* add_option_with_arg(char short_name, std::string long_name, + std::string description, + std::string arg_description) override { + prepare_option(short_name, long_name); + options_.push_back(std::make_unique(short_name, + std::move(long_name), + std::move(description), + true, arg_description)); + return options_.back().get(); + } + + bool run(int argc, char** argv, std::string_view prgname, std::ostream& err, + std::vector* out) override { + for (int a = 1; a < argc; ++a) { + if (argv[a][0] == '-') { + if (argv[a][1] == '-') { + if (argv[a][2] != '\0') { + // A long name with optional "=" argument + size_t len = 2; + while (argv[a][len] != '=' && argv[a][len]) + ++len; + std::string name(argv[a] + 2, len - 2); + auto it = long_names_.find(name); + if (it == long_names_.end()) { + err << prgname << ": unrecognized option '--" + << name << "'" << std::endl; + return false; + } + auto* opt = options_[it->second].get(); + opt->set(); + if (argv[a][len]) { + if (opt->require_arg()) { + opt->set_arg(std::string(argv[a] + len + 1)); + } else { + err << prgname << ": option '--" + << name << "' doesn't allow an argument" << std::endl; + return false; + } + } else { + if (opt->require_arg()) { + if (a + 1 >= argc) { + err << prgname << ": option '--" + << name << "' requires an argument" << std::endl; + return false; + } else { + opt->set_arg(argv[++a]); + } + } + } + continue; + } else { + // "--", all following values are arguments + for (++a; a < argc; ++a) + out->push_back(argv[a]); + break; + } + } else if (argv[a][1] != '\0') { + // One or more short names + for (auto* name = argv[a] + 1; *name; ++name) { + auto it = short_names_.find(*name); + if (it == short_names_.end()) { + err << prgname << ": invalid option -- '" + << *name << "'" << std::endl; + return false; + } + auto* opt = options_[it->second].get(); + opt->set(); + if (opt->require_arg()) { + if (a + 1 >= argc) { + err << prgname << ": option requires an argument" + << " -- '" << *name << "'" << std::endl; + return false; + } else { + opt->set_arg(argv[++a]); + } + } + } + continue; + } else { + // single "-", treat as argument + } + } + + out->push_back(argv[a]); + } + return true; + } + + void print_descriptions(std::ostream& out, + uint32_t column_width) const override { + uint32_t max_left = 0; + for (auto const& option : options_) { + uint32_t left = 0; + if (option->short_name() != '\0') { + if (!option->long_name().empty()) { + left = 6 + option->long_name().size(); // -S, --long + } else { + left = 2; // -S + } + } else if (!option->long_name().empty()) { + left = 2 + option->long_name().size(); // --long + } + if (option->require_arg()) + left += 1 + option->arg_description().size(); // (=| )ARG + if (left > 0) + left += 2; // Need at least two spaces between option and desc + // Prefix with two spaces (either infront of option or desc) + left += 2; + + if (left > max_left) + max_left = left; + } + + uint32_t const avail_right = + max_left > column_width ? 0 : column_width - max_left; + + if (avail_right < 20) { + // Fallback mode, description on its own row. + for (auto const& option : options_) { + print_option(out, *option); + out << '\n' << option->description() << '\n'; + } + return; + } + + // Check if all descriptions fit, justify to the right on a 80 col width + bool all_desc_fit = true; + uint32_t max_right = 0; + for (auto const& option : options_) { + uint32_t right = option->description().size(); + if (right > avail_right) { + all_desc_fit = false; + break; + } + if (right > max_right) + max_right = right; + } + + if (all_desc_fit) + max_left = std::max(80u, column_width) - max_right; + + for (auto const& option : options_) { + out << " "; + uint32_t left = 2 + print_option(out, *option); + std::fill_n(std::ostreambuf_iterator(out), max_left - left, ' '); + + if (option->description().size() <= avail_right) { + out << option->description() << '\n'; + continue; + } + + // Wrap description + size_t last = 0; + bool first = true; + while (true) { + if (first) { + first = false; + } else { + std::fill_n(std::ostreambuf_iterator(out), max_left, ' '); + } + + size_t end = last + avail_right; + if (end >= option->description().size()) { + out << option->description().substr(last) << '\n'; + break; + } + size_t space = option->description().rfind(' ', end); + if (space == std::string::npos || space < last) { + space = end; + } + out << option->description().substr(last, space - last) << '\n'; + last = space < end ? space + 1 : end; + } + } + } + +private: + void prepare_option(char short_name, std::string const& long_name) { + if (short_name != '\0') + short_names_.emplace(short_name, options_.size()); + if (!long_name.empty()) { + assert(long_name.find('=') == std::string::npos); + long_names_.emplace(long_name, options_.size()); + } + } + + size_t print_option(std::ostream& out, const OptionImpl& option) const { + bool only_short = false; + size_t ret = 0; + if (option.short_name() != '\0') { + out << '-' << option.short_name(); + if (!option.long_name().empty()) { + out << ", --" << option.long_name(); + ret = 6 + option.long_name().size(); + } else { + ret = 2; + only_short = true; + } + } else if (!option.long_name().empty()) { + out << "--" << option.long_name(); + ret = 2 + option.long_name().size(); + } + if (option.require_arg()) { + out << (only_short ? ' ' : '=') << option.arg_description(); + ret += 1 + option.arg_description().size(); + } + return ret; + } + + std::vector> options_; + std::unordered_map short_names_; + std::unordered_map long_names_; +}; + +} // namespace + +std::unique_ptr Args::create() { + return std::make_unique(); +} -- cgit v1.2.3-70-g09d2