aboutsummaryrefslogtreecommitdiff
path: root/libbutl
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2019-08-10 17:14:37 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2019-08-12 18:35:37 +0300
commitd20d2a641351b7f9e8c9bd9b841d8de4d824aa82 (patch)
tree8ac63c12a5c45f1e1a8159fc07d76575cb82a147 /libbutl
parent800bf1b9f67aae867ffe900a545444dfe8aa46c9 (diff)
Add default options loading and merging API
Diffstat (limited to 'libbutl')
-rw-r--r--libbutl/default-options.mxx115
-rw-r--r--libbutl/default-options.txx166
2 files changed, 281 insertions, 0 deletions
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 <utility> // 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 <libbutl/path.mxx>
+#include <libbutl/optional.mxx>
+#include <libbutl/small-vector.mxx>
+
+#include <libbutl/git.mxx>
+#include <libbutl/filesystem.mxx>
+#endif
+
+#include <libbutl/export.hxx>
+
+LIBBUTL_MODEXPORT namespace butl
+{
+ // Default options files helper implementation.
+ //
+ struct default_options_files
+ {
+ small_vector<path, 2> files;
+ optional<dir_path> start_dir;
+ };
+
+ template <typename O>
+ struct default_options_entry
+ {
+ path file;
+ O options;
+ bool remote;
+ };
+
+ template <typename O>
+ using default_options = small_vector<default_options_entry<O>, 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 <typename O, typename S, typename U>
+ default_options<O>
+ load_default_options (const optional<dir_path>& sys_dir,
+ const optional<dir_path>& 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 <typename O>
+ O
+ merge_default_options (const default_options<O>&, 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<O>&, 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 <typename O, typename F>
+ O
+ merge_default_options (const default_options<O>&, const O&, F&&);
+}
+
+#include <libbutl/default-options.txx>
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 <typename O, typename S, typename U>
+ void
+ load_default_options_files (const dir_path& d,
+ bool remote,
+ const small_vector<path, 2>& fs,
+ default_options<O>& 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<O> {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 <typename O, typename S, typename U>
+ bool
+ load_default_options_files (const dir_path& start_dir,
+ const optional<dir_path>& home_dir,
+ const small_vector<path, 2>& fs,
+ default_options<O>& r)
+ {
+ if (start_dir.root () || (home_dir && start_dir == *home_dir))
+ return false;
+
+ bool remote (load_default_options_files<O, S, U> (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<O, S, U> (d, remote, fs, r);
+
+ return remote;
+ }
+
+ template <typename O, typename S, typename U>
+ default_options<O>
+ load_default_options (const optional<dir_path>& sys_dir,
+ const optional<dir_path>& home_dir,
+ const default_options_files& ofs)
+ {
+ default_options<O> r;
+
+ if (sys_dir)
+ {
+ assert (sys_dir->absolute () && sys_dir->normalized ());
+
+ if (dir_exists (*sys_dir))
+ load_default_options_files<O, S, U> (*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<O, S, U> (d,
+ false /* remote */,
+ ofs.files,
+ r);
+ }
+
+ if (ofs.start_dir)
+ {
+ assert (ofs.start_dir->absolute () && ofs.start_dir->normalized ());
+
+ load_default_options_files<O, S, U> (*ofs.start_dir,
+ home_dir,
+ ofs.files,
+ r);
+ }
+
+ return r;
+ }
+
+ template <typename O, typename F>
+ O
+ merge_default_options (const default_options<O>& 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<O>& e: def_ops)
+ {
+ f (e, cmd_ops);
+ r.merge (e.options);
+ }
+
+ r.merge (cmd_ops);
+ return r;
+ }
+
+ template <typename O>
+ inline O
+ merge_default_options (const default_options<O>& def_ops, const O& cmd_ops)
+ {
+ return merge_default_options (
+ def_ops,
+ cmd_ops,
+ [] (const default_options_entry<O>&, const O&) {});
+ }
+}