// file : tests/default-options/driver.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include <limits> #include <string> #include <vector> #include <iostream> #include <exception> #include <stdexcept> // invalid_argument #include <libbutl/path.hxx> #include <libbutl/path-io.hxx> #include <libbutl/utility.hxx> // eof() #include <libbutl/optional.hxx> #include <libbutl/fdstream.hxx> #include <libbutl/default-options.hxx> #undef NDEBUG #include <cassert> using namespace std; using namespace butl; // Usage: argv[0] [-f <file>] [-d <start-dir>] [-s <sys-dir>] [-h <home-dir>] // [-x <extra-dir>] [-a] [-e] [-t] <cmd-options> // // Parse default options files, merge them with the command line options, and // print the resulting options to STDOUT one per line. Note that the options // instance is a vector of arbitrary strings. // // -f <file> // Default options file name. Can be specified multiple times. // // -d <start-dir> // Directory to start the default options files search from. Can be // specified multiple times, in which case a common start (parent) // directory is deduced. // // -s <sys-dir> // System directory. // // -h <home-dir> // Home directory. // // -x <extra-dir> // Extra directory. // // -a // Default arguments are allowed in the options files. // // -e // Print the default options entries (rather than the merged options) to // STDOUT one per line in the following format: // // <file>,<space-separated-options>,<remote> // // -t // Trace the default options files search to STDERR. // // -m <num> // Maximum number of arguments globally (SIZE_MAX/2 by default). // // -l <num> // Maximum number of arguments in the options file (1024 by default). // int main (int argc, const char* argv[]) { using butl::optional; class scanner { public: scanner (const string& f, const string& option, size_t pos) : option_ (option), start_pos_ (pos) {load (path (f));} bool more () { return i_ < args_.size (); } string peek () { assert (more ()); return args_[i_]; } string next () { assert (more ()); return args_[i_++]; } size_t position () { return start_pos_ + i_; } private: void load (const path& f) { ifdstream is (f, fdopen_mode::in, ifdstream::badbit); for (string l; !eof (getline (is, l)); ) { if (option_ && *option_ == l) { assert (!eof (getline (is, l))); // If the path of the file being parsed is not simple and the path // of the file that needs to be loaded is relative, then complete // the latter using the former as a base. // path p (l); if (!f.simple () && p.relative ()) p = f.directory () / p; load (p); } else args_.push_back (move (l)); } } private: optional<string> option_; vector<string> args_; size_t i_ = 0; size_t start_pos_; }; enum class unknow_mode { stop, fail }; class unknown_argument: public std::exception { public: string argument; explicit unknown_argument (string a): argument (move (a)) {} }; class options: public vector<string> { public: bool parse (scanner& s, unknow_mode, unknow_mode m) { bool r (false); while (s.more ()) { string a (s.peek ()); if (a.compare (0, 2, "--") != 0) { switch (m) { case unknow_mode::stop: return r; case unknow_mode::fail: throw unknown_argument (move (a)); } } s.next (); if (a == "--no-default-options") no_default_options_ = true; push_back (move (a)); r = true; } return r; } void merge (const options& o) { insert (end (), o.begin (), o.end ()); } bool no_default_options () const noexcept { return no_default_options_; } private: bool no_default_options_ = false; }; // Parse and validate the arguments. // default_options_files fs; optional<dir_path> sys_dir; optional<dir_path> home_dir; optional<dir_path> extra_dir; bool args (false); vector<dir_path> dirs; options cmd_ops; vector<string> cmd_args; bool print_entries (false); bool trace (false); size_t arg_max (numeric_limits<size_t>::max () / 2); size_t arg_max_file (1024); auto num = [] (const string& s) -> size_t { assert (!s.empty ()); char* e (nullptr); errno = 0; // We must clear it according to POSIX. uint64_t r (strtoull (s.c_str (), &e, 10)); // Can't throw. assert (errno != ERANGE && e == s.c_str () + s.size () && r <= numeric_limits<size_t>::max ()); return static_cast<size_t> (r); }; for (int i (1); i != argc; ++i) { string a (argv[i]); if (a == "-f") { assert (++i != argc); fs.files.push_back (path (argv[i])); } else if (a == "-d") { assert (++i != argc); dirs.emplace_back (argv[i]); } else if (a == "-s") { assert (++i != argc); sys_dir = dir_path (argv[i]); } else if (a == "-h") { assert (++i != argc); home_dir = dir_path (argv[i]); } else if (a == "-x") { assert (++i != argc); extra_dir = dir_path (argv[i]); } else if (a == "-a") { args = true; } else if (a == "-e") { print_entries = true; } else if (a == "-t") { trace = true; } else if (a == "-m") { assert (++i != argc); arg_max = num (argv[i]); } else if (a == "-l") { assert (++i != argc); arg_max_file = num (argv[i]); } else if (a.compare (0, 2, "--") == 0) cmd_ops.push_back (move (a)); else cmd_args.push_back (move (a)); } // Deduce a common start directory. // fs.start = default_options_start (home_dir, dirs.begin (), dirs.end ()); // Load and print the default options. // default_options<options> def_ops; try { def_ops = load_default_options<options, scanner, unknow_mode> ( sys_dir, home_dir, extra_dir, fs, [trace] (const path& f, bool remote, bool overwrite) { if (trace) cerr << (overwrite ? "overwriting " : "loading ") << (remote ? "remote " : "local ") << f << endl; }, "--options-file", arg_max, arg_max_file, args); } catch (const unknown_argument& e) { cerr << "error: unexpected argument '" << e.argument << "'" << endl; return 1; } catch (const invalid_argument& e) { cerr << "error: unable to load default options files: " << e.what () << endl; return 1; } if (print_entries) { for (const default_options_entry<options>& e: def_ops) { cout << e.file << ','; for (const string& o: e.options) { if (&o != &e.options[0]) cout << ' '; cout << o; } if (args) { cout << "|"; for (const string& a: e.arguments) { if (&a != &e.arguments[0]) cout << ' '; cout << a; } } cout << (e.remote ? ",true" : ",false") << endl; } } // Merge the options and print the result. // if (!print_entries) { options ops (merge_default_options (def_ops, cmd_ops)); for (const string& o: ops) cout << o << endl; if (args) { vector<string> as (merge_default_arguments (def_ops, cmd_args)); for (const string& a: as) cout << a << endl; } } return 0; }