From d20d2a641351b7f9e8c9bd9b841d8de4d824aa82 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sat, 10 Aug 2019 17:14:37 +0300 Subject: Add default options loading and merging API --- libbutl/default-options.mxx | 115 ++++++++++++++++++++++++++++++ libbutl/default-options.txx | 166 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 libbutl/default-options.mxx create mode 100644 libbutl/default-options.txx (limited to 'libbutl') diff --git a/libbutl/default-options.mxx b/libbutl/default-options.mxx new file mode 100644 index 0000000..01d32c1 --- /dev/null +++ b/libbutl/default-options.mxx @@ -0,0 +1,115 @@ +// file : libbutl/default-options.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules_ts +#pragma once +#endif + +#ifndef __cpp_lib_modules_ts +#include // move() +#endif + +// Other includes. + +#ifdef __cpp_modules_ts +export module butl.default_options; +#ifdef __cpp_lib_modules_ts +import std.core; +#endif +import butl.path; +import butl.optional; +import butl.small_vector; + +import butl.git; +import butl.filesystem; +#else +#include +#include +#include + +#include +#include +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // Default options files helper implementation. + // + struct default_options_files + { + small_vector files; + optional start_dir; + }; + + template + struct default_options_entry + { + path file; + O options; + bool remote; + }; + + template + using default_options = small_vector, 4>; + + // Search for and load (using scanner S and parsing in the U::fail mode for + // both options and arguments) the specified list of options files in the + // specified directories returning a vector of option class instances (O). + // Throw std::system_error on the underlying OS error and pass through + // exceptions thrown by the options scanner/parser. + // + // Search order: + // + // - sys_dir + // - home_dir + // - start_dir and outer until home_dir or root (both excluding) + // + // Except for sys_dir, the options files are looked for in the .build2/ and + // .build2/local/ subdirectories of each directory. For sys_dir they are + // looked for in the directory itself (e.g., /etc/build2/). + // + // Note that all the directories should be absolute and normalized. + // + // The presence of the .git filesystem entry causes the options files in + // this directory and any of its subdirectories to be considered remote + // (note that in the current implementation this is the case even for files + // from the .build2/local/ subdirectory since the mere location is not a + // sufficient ground to definititevly conclude that the file is not remote; + // to be sure we would need to query the VCS or some such). + // + template + default_options + load_default_options (const optional& sys_dir, + const optional& home_dir, + const default_options_files&); + + // Merge the default options and the command line options. + // + // Note that this is the default implementation and in some cases you may + // want to provide an options class-specific version that verifies/sanitizes + // the default options (e.g., you may not want to allow certain options to + // be specified in the default options files) or warns/prompts about + // potentially dangerous options if they came from the remote options files. + // + template + O + merge_default_options (const default_options&, const O& cmd_ops); + + // As above but pass each default option to the specified function prior to + // merging. The function signature is: + // + // void (const default_options_entry&, const O& cmd_ops) + // + // This version can be used to verify the default options. For example, you + // may want to disallow certain options from being specified in the default + // options files. + // + template + O + merge_default_options (const default_options&, const O&, F&&); +} + +#include diff --git a/libbutl/default-options.txx b/libbutl/default-options.txx new file mode 100644 index 0000000..42dd585 --- /dev/null +++ b/libbutl/default-options.txx @@ -0,0 +1,166 @@ +// file : libbutl/default-options.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. +{ + // Search for and parse the options files in the specified directory and + // its local/ subdirectory, if exists, and append the options to the + // resulting list. + // + // Note that we check for the local/ subdirectory even if we don't think it + // belongs to the remote directory; the user may move things around or it + // could be a VCS we don't yet recognize and there doesn't seem to be any + // harm in doing so. + // + // Also note that if the directory is remote, then for now the options files + // in both the directory itself and its local/ subdirectory are considered + // remote (see load_default_options() for details). + // + template + void + load_default_options_files (const dir_path& d, + bool remote, + const small_vector& fs, + default_options& r) + { + auto load = [&fs, &r] (const dir_path& d, bool remote) + { + using namespace std; + + for (const path& f: fs) + { + path p (d / f); + + if (file_exists (p)) // Follows symlinks. + { + S s (p.string ()); + + // @@ Note that the potentially thrown exceptions (unknown option, + // unexpected argument, etc) will not contain any location + // information. Intercepting exception handling to add the file + // attribution feels too hairy for now. Maybe we should support + // this in CLI. + // + O o; + o.parse (s, U::fail, U::fail); + + r.push_back (default_options_entry {move (p), move (o), remote}); + } + } + }; + + load (d, remote); + + dir_path ld (d / dir_path ("local")); + + if (dir_exists (ld)) + load (ld, remote); + } + + // Search for and parse the options files in the specified and outer + // directories until root/home directory (excluding) and append the options + // to the resulting list. Return true if the directory is "remote" (i.e., + // belongs to a VCS repository). + // + template + bool + load_default_options_files (const dir_path& start_dir, + const optional& home_dir, + const small_vector& fs, + default_options& r) + { + if (start_dir.root () || (home_dir && start_dir == *home_dir)) + return false; + + bool remote (load_default_options_files (start_dir.directory (), + home_dir, + fs, + r) || + git_repository (start_dir)); + + dir_path d (start_dir / dir_path (".build2")); + + if (dir_exists (d)) + load_default_options_files (d, remote, fs, r); + + return remote; + } + + template + default_options + load_default_options (const optional& sys_dir, + const optional& home_dir, + const default_options_files& ofs) + { + default_options r; + + if (sys_dir) + { + assert (sys_dir->absolute () && sys_dir->normalized ()); + + if (dir_exists (*sys_dir)) + load_default_options_files (*sys_dir, + false /* remote */, + ofs.files, + r); + } + + if (home_dir) + { + assert (home_dir->absolute () && home_dir->normalized ()); + + dir_path d (*home_dir / dir_path (".build2")); + + if (dir_exists (d)) + load_default_options_files (d, + false /* remote */, + ofs.files, + r); + } + + if (ofs.start_dir) + { + assert (ofs.start_dir->absolute () && ofs.start_dir->normalized ()); + + load_default_options_files (*ofs.start_dir, + home_dir, + ofs.files, + r); + } + + return r; + } + + template + O + merge_default_options (const default_options& def_ops, + const O& cmd_ops, + F&& f) + { + // Optimize for the common case. + // + if (def_ops.empty ()) + return cmd_ops; + + O r; + for (const default_options_entry& e: def_ops) + { + f (e, cmd_ops); + r.merge (e.options); + } + + r.merge (cmd_ops); + return r; + } + + template + inline O + merge_default_options (const default_options& def_ops, const O& cmd_ops) + { + return merge_default_options ( + def_ops, + cmd_ops, + [] (const default_options_entry&, const O&) {}); + } +} -- cgit v1.1