/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2013-2016 Danny Robson */ #include "cmdopt.hpp" #include "cast.hpp" #include "debug.hpp" #include #include #include using namespace cruft::cmdopt; using namespace cruft::cmdopt::option; /////////////////////////////////////////////////////////////////////////////// base::~base () { ; } //----------------------------------------------------------------------------- void base::execute (void) { throw invalid_null (); } //----------------------------------------------------------------------------- void base::execute (const char *restrict value) { throw invalid_value (value); } //----------------------------------------------------------------------------- void base::start (void) { m_seen = false; } //----------------------------------------------------------------------------- void base::finish (void) { if (m_required && !m_seen) throw invalid_required (); } //----------------------------------------------------------------------------- bool base::required (void) const { return m_required; } //----------------------------------------------------------------------------- bool base::required (bool _required) { return m_required = _required; } //----------------------------------------------------------------------------- bool base::seen (void) const { return m_seen; } //----------------------------------------------------------------------------- bool base::seen (bool _seen) { return m_seen = _seen; } /////////////////////////////////////////////////////////////////////////////// void null::execute (void) { seen (true); } //----------------------------------------------------------------------------- void null::execute (const char *restrict) { seen (true); } //----------------------------------------------------------------------------- const std::string& null::example (void) const { static const std::string EXAMPLE; return EXAMPLE; } /////////////////////////////////////////////////////////////////////////////// present::present (bool &_data): m_data (_data) { ; } //----------------------------------------------------------------------------- void present::execute (void) { seen (true); } //----------------------------------------------------------------------------- const std::string& present::example (void) const { static const std::string EXAMPLE; return EXAMPLE; } //----------------------------------------------------------------------------- void present::finish (void) { m_data = seen (); base::finish (); } //----------------------------------------------------------------------------- namespace cruft::cmdopt::option { template class value; template class value; template class value; } /////////////////////////////////////////////////////////////////////////////// template count::count (T &_data): value (_data) { ; } //------------------------------------------------------------------------- template void count::execute (void) { ++this->data (); this->seen (true); } //----------------------------------------------------------------------------- namespace cruft::cmdopt::option { template class count; } /////////////////////////////////////////////////////////////////////////////// static size_t suffix_to_multiplier (char c) { switch (c) { case 'e': case 'E': return cruft::pow (1024UL, 6u); case 'p': case 'P': return cruft::pow (1024UL, 5u); case 't': case 'T': return cruft::pow (1024UL, 4u); case 'g': case 'G': return cruft::pow (1024UL, 3u); case 'm': case 'M': return cruft::pow (1024UL, 2u); case 'k': case 'K': return cruft::pow (1024UL, 1u); default: const char str[2] = { c, '\0' }; throw std::invalid_argument (str); } } //----------------------------------------------------------------------------- void bytes::execute (const char *restrict str) { const char *tail; const char *last = str + strlen (str); unsigned long val = std::strtoul (const_cast (str), const_cast (&tail), 10); CHECK_LE (tail, last); if (tail == str) { throw invalid_value (str); } else if (tail == last) { data (val); } else if (tail + 1 == last) { try { data (val * suffix_to_multiplier (*tail)); } catch (const std::invalid_argument&) { throw invalid_value (str); } } else throw invalid_value (str); } /////////////////////////////////////////////////////////////////////////////// int parser::scan (int argc, char const *const *argv) { CHECK_GT (argc, 0); CHECK (argv); for (auto &j: m_options) j.handler->start (); // start iterating after our program's name int i = 1; while (i < argc) { auto arg = argv[i]; auto len = strlen (arg); // bail if there's no potential for an option if (len < 2 || arg[0] != '-') break; // stop processing named options on '--' if (len == 2 && arg[1] == '-') { ++i; break; } // parse longopt auto inc = arg[1] == '-' ? parse_long (i, argc, argv) : parse_short (i, argc, argv); CHECK_GT (inc, 0); i += inc; } // process the positional arguments for (size_t cursor = 0; i < argc && cursor < m_positional.size (); ++i, ++cursor) m_positional[cursor].get ().execute (argv[i]); // ensure we've processed all the arguments if (i != argc) throw unhandled_argument (i); // allow arguments to check if they've been successfully handled for (auto &j: m_options) j.handler->finish (); return i; } //----------------------------------------------------------------------------- int parser::parse_long (int pos, int argc, const char *const *argv) { CHECK_LT (pos, argc); CHECK_GE (pos, 0); CHECK_GE (argc, 0); CHECK (argv); CHECK_EQ (argv[pos][0], '-'); CHECK_EQ (argv[pos][1], '-'); // break first atom into components and extract the key const char *start = argv[pos] + 2; const char *eq = strchr (start, '='); const char *last = start + strlen (start); std::string key { start, eq ? eq : last }; if (key == "help") print_help (argc, argv); auto &handler = m_long.at (key).get (); // maybe grab a value from the next atom and dispatch if (!eq) { // check the next atom for the value if (pos + 1 < argc) if (argv[pos + 1][0] != '-') { handler.execute (argv[pos+1]); return 2; } handler.execute (); } else { handler.execute (eq+1); } return 1; } //----------------------------------------------------------------------------- int parser::parse_short (int pos, int argc, const char *const *argv) { CHECK_LT (pos, argc); CHECK_GE (pos, 0); CHECK_GE (argc, 0); CHECK (argv); CHECK_EQ (argv[pos][0], '-'); CHECK_NEQ (argv[pos][1], '-'); // we have a run of no-value keys auto len = strlen (argv[pos]); if (len > 2 || pos + 1 == argc || argv[pos+1][0] == '-') { for (size_t i = 1; i < len; ++i) { auto letter = argv[pos][i]; if (letter == 'h') print_help (argc, argv); m_short.at(letter).get ().execute (); } return 1; } // we have a value following auto letter = argv[pos][1]; m_short.at (letter).get ().execute (argv[pos + 1]); return 2; } /////////////////////////////////////////////////////////////////////////////// void parser::print_help (const int argc, const char *const *argv) const { (void)argc; CHECK_EQ (m_short.size (), m_options.size ()); CHECK_EQ (m_long.size (), m_options.size ()); if (m_options.empty ()) exit (0); // find the longest long form argument so we can set field alignment auto largestwidth = std::max_element ( m_long.begin (), m_long.end (), [] (const auto &a, const auto &b) { auto const &[a_key,a_obj] = a; auto const &[b_key,b_obj] = b; return a_key.size () < b_key.size (); }); auto longwidth = largestwidth->first.size (); // find the longest example text auto largestexample = std::max_element ( m_options.cbegin (), m_options.cend (), [] (const auto &a, const auto &b) { return a.handler->example ().size () > b.handler->example ().size (); }); auto longexample = largestexample->handler->example ().size (); // field width requires an alignment. we don't care about preserving // state as we're about to bail anyway std::cout << std::left; // print all the option info std::cout << "usage: " << argv[0] << '\n'; for (auto &opt: m_options) { auto s = std::find_if ( std::cbegin (m_short), std::cend (m_short), [&] (auto j) { return &j.second.get () == opt.handler.get (); } ); auto l = std::find_if ( std::cbegin (m_long), std::cend (m_long), [&] (auto j) { return &j.second.get () == opt.handler.get (); } ); std::cout << '\t'; if (s != std::cend (m_short)) std::cout << '-' << s->first << '\t'; else std::cout << '\t'; std::cout << std::setw (cruft::cast::lossless (longwidth)); if (l != std::cend (m_long)) std::cout << l->first << '\t'; else std::cout << ' ' << '\t'; std::cout << std::setw (cruft::cast::lossless (longexample)) << opt.handler->example () << '\t' << std::setw (0) << opt.description << '\n'; } exit (0); } /////////////////////////////////////////////////////////////////////////////// invalid_key::invalid_key (std::string _key): m_key (std::move (_key)) { ; } //----------------------------------------------------------------------------- const char* invalid_key::what (void) const noexcept { return m_key.c_str (); } /////////////////////////////////////////////////////////////////////////////// invalid_value::invalid_value (std::string _value): m_value (std::move (_value)) { ; } //----------------------------------------------------------------------------- const char* invalid_value::what (void) const noexcept { return m_value.c_str (); } /////////////////////////////////////////////////////////////////////////////// const char* invalid_null::what (void) const noexcept { static const char WHAT[] = "unexpected null option was encountered"; return WHAT; } /////////////////////////////////////////////////////////////////////////////// const char* invalid_required::what (void) const noexcept { static const char WHAT[] = "required option not seen"; return WHAT; } /////////////////////////////////////////////////////////////////////////////// unhandled_argument::unhandled_argument (int _index): m_index (_index) { ; } //----------------------------------------------------------------------------- int unhandled_argument::index (void) const noexcept { return m_index; } //----------------------------------------------------------------------------- const char* unhandled_argument::what (void) const noexcept { static const char WHAT[] = "unhandled argument"; return WHAT; }