aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--COPYRIGHT2
-rw-r--r--LICENSE5
-rw-r--r--build/root.build7
-rw-r--r--libbutl/b.cxx85
-rw-r--r--libbutl/b.hxx (renamed from libbutl/b.mxx)75
-rw-r--r--libbutl/b.ixx31
-rw-r--r--libbutl/backtrace.cxx30
-rw-r--r--libbutl/backtrace.hxx (renamed from libbutl/backtrace.mxx)19
-rw-r--r--libbutl/base64.cxx102
-rw-r--r--libbutl/base64.hxx (renamed from libbutl/base64.mxx)39
-rw-r--r--libbutl/buildfile54
-rw-r--r--libbutl/builtin-options.cxx422
-rw-r--r--libbutl/builtin-options.hxx79
-rw-r--r--libbutl/builtin-options.ixx3
-rw-r--r--libbutl/builtin.cli5
-rw-r--r--libbutl/builtin.cxx382
-rw-r--r--libbutl/builtin.hxx (renamed from libbutl/builtin.mxx)61
-rw-r--r--libbutl/builtin.ixx16
-rw-r--r--libbutl/char-scanner.hxx (renamed from libbutl/char-scanner.mxx)29
-rw-r--r--libbutl/char-scanner.ixx6
-rw-r--r--libbutl/char-scanner.txx5
-rw-r--r--libbutl/command.cxx50
-rw-r--r--libbutl/command.hxx (renamed from libbutl/command.mxx)23
-rw-r--r--libbutl/const-ptr.hxx (renamed from libbutl/const-ptr.mxx)21
-rw-r--r--libbutl/curl.cxx173
-rw-r--r--libbutl/curl.hxx (renamed from libbutl/curl.mxx)121
-rw-r--r--libbutl/curl.ixx79
-rw-r--r--libbutl/curl.txx10
-rw-r--r--libbutl/default-options.hxx (renamed from libbutl/default-options.mxx)37
-rw-r--r--libbutl/default-options.ixx2
-rw-r--r--libbutl/default-options.txx10
-rw-r--r--libbutl/diagnostics.cxx89
-rw-r--r--libbutl/diagnostics.hxx (renamed from libbutl/diagnostics.mxx)138
-rw-r--r--libbutl/export.hxx8
-rw-r--r--libbutl/fdstream.cxx228
-rw-r--r--libbutl/fdstream.hxx (renamed from libbutl/fdstream.mxx)118
-rw-r--r--libbutl/fdstream.ixx4
-rw-r--r--libbutl/filesystem.cxx927
-rw-r--r--libbutl/filesystem.hxx (renamed from libbutl/filesystem.mxx)196
-rw-r--r--libbutl/filesystem.ixx45
-rw-r--r--libbutl/ft/lang.hxx9
-rw-r--r--libbutl/git.cxx44
-rw-r--r--libbutl/git.hxx (renamed from libbutl/git.mxx)28
-rw-r--r--libbutl/host-os-release.cxx323
-rw-r--r--libbutl/host-os-release.hxx86
-rw-r--r--libbutl/json/event.hxx27
-rw-r--r--libbutl/json/parser.cxx645
-rw-r--r--libbutl/json/parser.hxx705
-rw-r--r--libbutl/json/parser.ixx552
-rw-r--r--libbutl/json/pdjson.c1044
-rw-r--r--libbutl/json/pdjson.h147
-rw-r--r--libbutl/json/serializer.cxx671
-rw-r--r--libbutl/json/serializer.hxx413
-rw-r--r--libbutl/json/serializer.ixx247
-rw-r--r--libbutl/lz4-stream.cxx2
-rw-r--r--libbutl/lz4-stream.hxx6
-rw-r--r--libbutl/lz4.c12
-rw-r--r--libbutl/lz4.cxx2
-rw-r--r--libbutl/lz4.hxx4
-rw-r--r--libbutl/lz4frame.c4
-rw-r--r--libbutl/manifest-parser.cxx238
-rw-r--r--libbutl/manifest-parser.hxx (renamed from libbutl/manifest-parser.mxx)38
-rw-r--r--libbutl/manifest-rewriter.cxx44
-rw-r--r--libbutl/manifest-rewriter.hxx (renamed from libbutl/manifest-rewriter.mxx)28
-rw-r--r--libbutl/manifest-serializer.cxx153
-rw-r--r--libbutl/manifest-serializer.hxx (renamed from libbutl/manifest-serializer.mxx)43
-rw-r--r--libbutl/manifest-types.hxx (renamed from libbutl/manifest-types.mxx)22
-rw-r--r--libbutl/mingw-condition_variable.hxx275
-rw-r--r--libbutl/mingw-invoke.hxx109
-rw-r--r--libbutl/mingw-mutex.hxx210
-rw-r--r--libbutl/mingw-shared_mutex.hxx124
-rw-r--r--libbutl/mingw-thread.hxx330
-rw-r--r--libbutl/move-only-function.hxx177
-rw-r--r--libbutl/multi-index.hxx (renamed from libbutl/multi-index.mxx)21
-rw-r--r--libbutl/openssl.cxx27
-rw-r--r--libbutl/openssl.hxx (renamed from libbutl/openssl.mxx)69
-rw-r--r--libbutl/openssl.ixx14
-rw-r--r--libbutl/openssl.txx68
-rw-r--r--libbutl/optional.hxx (renamed from libbutl/optional.mxx)185
-rw-r--r--libbutl/optional.ixx5
-rw-r--r--libbutl/pager.cxx40
-rw-r--r--libbutl/pager.hxx (renamed from libbutl/pager.mxx)26
-rw-r--r--libbutl/path-io.hxx (renamed from libbutl/path-io.mxx)24
-rw-r--r--libbutl/path-map.hxx (renamed from libbutl/path-map.mxx)35
-rw-r--r--libbutl/path-pattern.cxx33
-rw-r--r--libbutl/path-pattern.hxx (renamed from libbutl/path-pattern.mxx)27
-rw-r--r--libbutl/path-pattern.ixx26
-rw-r--r--libbutl/path.cxx33
-rw-r--r--libbutl/path.hxx (renamed from libbutl/path.mxx)71
-rw-r--r--libbutl/path.ixx93
-rw-r--r--libbutl/path.txx2
-rw-r--r--libbutl/prefix-map.hxx (renamed from libbutl/prefix-map.mxx)52
-rw-r--r--libbutl/prefix-map.txx56
-rw-r--r--libbutl/process-details.hxx32
-rw-r--r--libbutl/process-io.cxx29
-rw-r--r--libbutl/process-io.hxx (renamed from libbutl/process-io.mxx)23
-rw-r--r--libbutl/process-run.cxx29
-rw-r--r--libbutl/process-run.txx83
-rw-r--r--libbutl/process.cxx107
-rw-r--r--libbutl/process.hxx (renamed from libbutl/process.mxx)201
-rw-r--r--libbutl/process.ixx233
-rw-r--r--libbutl/project-name.cxx30
-rw-r--r--libbutl/project-name.hxx (renamed from libbutl/project-name.mxx)23
-rw-r--r--libbutl/prompt.cxx30
-rw-r--r--libbutl/prompt.hxx (renamed from libbutl/prompt.mxx)23
-rw-r--r--libbutl/regex.cxx31
-rw-r--r--libbutl/regex.hxx (renamed from libbutl/regex.mxx)29
-rw-r--r--libbutl/regex.ixx4
-rw-r--r--libbutl/regex.txx5
-rw-r--r--libbutl/semantic-version.cxx97
-rw-r--r--libbutl/semantic-version.hxx (renamed from libbutl/semantic-version.mxx)93
-rw-r--r--libbutl/semantic-version.ixx64
-rw-r--r--libbutl/sendmail.cxx27
-rw-r--r--libbutl/sendmail.hxx (renamed from libbutl/sendmail.mxx)31
-rw-r--r--libbutl/sendmail.ixx5
-rw-r--r--libbutl/sha1.c10
-rw-r--r--libbutl/sha1.cxx24
-rw-r--r--libbutl/sha1.hxx (renamed from libbutl/sha1.mxx)19
-rw-r--r--libbutl/sha256.cxx34
-rw-r--r--libbutl/sha256.hxx (renamed from libbutl/sha256.mxx)19
-rw-r--r--libbutl/small-allocator.hxx (renamed from libbutl/small-allocator.mxx)18
-rw-r--r--libbutl/small-forward-list.hxx (renamed from libbutl/small-forward-list.mxx)29
-rw-r--r--libbutl/small-list.hxx (renamed from libbutl/small-list.mxx)31
-rw-r--r--libbutl/small-vector-odb.hxx2
-rw-r--r--libbutl/small-vector.hxx (renamed from libbutl/small-vector.mxx)50
-rw-r--r--libbutl/standard-version.cxx33
-rw-r--r--libbutl/standard-version.hxx (renamed from libbutl/standard-version.mxx)25
-rw-r--r--libbutl/string-parser.cxx30
-rw-r--r--libbutl/string-parser.hxx (renamed from libbutl/string-parser.mxx)19
-rw-r--r--libbutl/string-table.hxx (renamed from libbutl/string-table.mxx)26
-rw-r--r--libbutl/string-table.txx3
-rw-r--r--libbutl/tab-parser.cxx31
-rw-r--r--libbutl/tab-parser.hxx (renamed from libbutl/tab-parser.mxx)20
-rw-r--r--libbutl/target-triplet.cxx33
-rw-r--r--libbutl/target-triplet.hxx (renamed from libbutl/target-triplet.mxx)26
-rw-r--r--libbutl/timestamp.cxx39
-rw-r--r--libbutl/timestamp.hxx (renamed from libbutl/timestamp.mxx)32
-rw-r--r--libbutl/unicode.cxx23
-rw-r--r--libbutl/unicode.hxx (renamed from libbutl/unicode.mxx)20
-rw-r--r--libbutl/url.hxx (renamed from libbutl/url.mxx)41
-rw-r--r--libbutl/url.ixx2
-rw-r--r--libbutl/url.txx7
-rw-r--r--libbutl/utf8.hxx (renamed from libbutl/utf8.mxx)22
-rw-r--r--libbutl/utf8.ixx2
-rw-r--r--libbutl/utility.cxx67
-rw-r--r--libbutl/utility.hxx (renamed from libbutl/utility.mxx)76
-rw-r--r--libbutl/utility.ixx78
-rw-r--r--libbutl/uuid-linux.cxx2
-rw-r--r--libbutl/uuid-openbsd.cxx80
-rw-r--r--libbutl/uuid.cxx23
-rw-r--r--libbutl/uuid.hxx20
-rw-r--r--libbutl/uuid.ixx4
-rw-r--r--libbutl/vector-view.hxx (renamed from libbutl/vector-view.mxx)19
-rw-r--r--libbutl/win32-utility.cxx9
-rw-r--r--libbutl/win32-utility.hxx4
-rw-r--r--manifest10
-rw-r--r--tests/b-info/driver.cxx42
-rw-r--r--tests/backtrace/driver.cxx24
-rw-r--r--tests/base64/driver.cxx86
-rw-r--r--tests/build/root.build7
-rw-r--r--tests/builtin/driver.cxx36
-rw-r--r--tests/builtin/find.testscript276
-rw-r--r--tests/command/driver.cxx32
-rw-r--r--tests/cpfile/driver.cxx23
-rw-r--r--tests/curl/driver.cxx56
-rw-r--r--tests/curl/testscript20
-rw-r--r--tests/default-options/driver.cxx35
-rw-r--r--tests/dir-iterator/driver.cxx69
-rw-r--r--tests/dir-iterator/testscript30
-rw-r--r--tests/entry-time/driver.cxx26
-rw-r--r--tests/fdstream/driver.cxx122
-rw-r--r--tests/host-os-release/buildfile6
-rw-r--r--tests/host-os-release/driver.cxx58
-rw-r--r--tests/host-os-release/testscript223
-rw-r--r--tests/link/driver.cxx43
-rw-r--r--tests/lz4/driver.cxx7
-rw-r--r--tests/manifest-parser/driver.cxx89
-rw-r--r--tests/manifest-rewriter/driver.cxx48
-rw-r--r--tests/manifest-roundtrip/buildfile3
-rw-r--r--tests/manifest-roundtrip/driver.cxx69
-rw-r--r--tests/manifest-roundtrip/manifest32
-rw-r--r--tests/manifest-roundtrip/testscript118
-rw-r--r--tests/manifest-serializer/driver.cxx84
-rw-r--r--tests/move-only-function/buildfile6
-rw-r--r--tests/move-only-function/driver.cxx149
-rw-r--r--tests/mventry/driver.cxx23
-rw-r--r--tests/mventry/testscript10
-rw-r--r--tests/next-word/buildfile6
-rw-r--r--tests/next-word/driver.cxx46
-rw-r--r--tests/openssl/driver.cxx58
-rw-r--r--tests/optional/driver.cxx20
-rw-r--r--tests/pager/driver.cxx21
-rw-r--r--tests/path-entry/driver.cxx36
-rw-r--r--tests/path-entry/testscript10
-rw-r--r--tests/path/driver.cxx20
-rw-r--r--tests/prefix-map/driver.cxx21
-rw-r--r--tests/process-run/driver.cxx31
-rw-r--r--tests/process-term/driver.cxx23
-rw-r--r--tests/process/driver.cxx34
-rw-r--r--tests/progress/driver.cxx29
-rw-r--r--tests/project-name/driver.cxx25
-rw-r--r--tests/regex/driver.cxx21
-rw-r--r--tests/semantic-version/driver.cxx50
-rw-r--r--tests/sendmail/driver.cxx34
-rw-r--r--tests/sha1/driver.cxx29
-rw-r--r--tests/sha256/driver.cxx29
-rw-r--r--tests/small-forward-list/driver.cxx21
-rw-r--r--tests/small-list/driver.cxx17
-rw-r--r--tests/small-vector/driver.cxx21
-rw-r--r--tests/standard-version/driver.cxx31
-rw-r--r--tests/strcase/driver.cxx16
-rw-r--r--tests/string-parser/driver.cxx24
-rw-r--r--tests/tab-parser/driver.cxx20
-rw-r--r--tests/target-triplet/driver.cxx25
-rw-r--r--tests/timestamp/driver.cxx23
-rw-r--r--tests/url/driver.cxx24
-rw-r--r--tests/utf8/driver.cxx23
-rw-r--r--tests/uuid/driver.cxx4
-rw-r--r--tests/wildcard/driver.cxx86
-rw-r--r--tests/wildcard/testscript6
221 files changed, 12386 insertions, 4052 deletions
diff --git a/.gitignore b/.gitignore
index c3de2e7..5046596 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,10 +5,16 @@
*.d
*.t
*.i
+*.i.*
*.ii
+*.ii.*
*.o
*.obj
+*.gcm
+*.pcm
+*.ifc
*.so
+*.dylib
*.dll
*.a
*.lib
diff --git a/COPYRIGHT b/COPYRIGHT
index 49efbbe..6bd48c9 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1 +1 @@
-Copyright (c) 2014-2021 the build2 authors (see the AUTHORS file).
+Copyright (c) 2014-2024 the build2 authors (see the AUTHORS file).
diff --git a/LICENSE b/LICENSE
index 27bab99..f22e52f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -4,9 +4,14 @@ libbutl/sha1.c:
libbutl/{sha256c.c, strptime.c, timelocal.[hc]}:
libbutl/{xxhash.[hc], lz4*.[hc]}:
+libbutl/mingw-*.hxx:
2-clause BSD License; see the file headers for details.
+libbutl/json/pdjson.[hc]:
+
+UNLICENSE (dedicated to the public domain).
+
The rest:
MIT License
diff --git a/build/root.build b/build/root.build
index e867421..17e42b1 100644
--- a/build/root.build
+++ b/build/root.build
@@ -5,7 +5,7 @@ cxx.std = latest
using cxx
-hxx{*}: extension = hxx # We also have .mxx; see libbutl/buildfile.
+hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
@@ -22,7 +22,12 @@ elif ($cxx.id == 'gcc')
#
cxx.coptions += -Wno-maybe-uninitialized -Wno-free-nonheap-object \
-Wno-stringop-overread
+
+ if ($cxx.version.major >= 13)
+ cxx.coptions += -Wno-dangling-reference
}
+elif ($cxx.id.type == 'clang' && $cxx.version.major >= 15)
+ cxx.coptions += -Wno-unqualified-std-cast-call
# Load the cli module but only if it's available. This way a distribution
# that includes pre-generated files can be built without installing cli.
diff --git a/libbutl/b.cxx b/libbutl/b.cxx
index 86a87ff..0b4472f 100644
--- a/libbutl/b.cxx
+++ b/libbutl/b.cxx
@@ -1,59 +1,19 @@
// file : libbutl/b.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/b.mxx>
-#endif
-
-// C includes.
+#include <libbutl/b.hxx>
+#include <ios> // ios::failure
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <vector>
-#include <cstddef>
-#include <cstdint>
-#include <stdexcept>
-#include <functional>
-
-#include <ios> // ios::failure
-#include <utility> // move()
+#include <utility> // move()
#include <sstream>
#include <algorithm>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.b;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.url;
-import butl.path;
-import butl.process;
-import butl.optional;
-import butl.project_name;
-import butl.standard_version;
-#endif
-
-import butl.utility; // next_word(), eof(), etc
-import butl.path_io;
-import butl.fdstream;
-import butl.process_io; // operator<<(ostream, process_path)
-import butl.small_vector;
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/path-io.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/process-io.mxx>
-#include <libbutl/small-vector.mxx>
-#endif
+
+#include <libbutl/utility.hxx> // next_word(), eof(), etc
+#include <libbutl/path-io.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/process-io.hxx> // operator<<(ostream, process_path)
+#include <libbutl/small-vector.hxx>
using namespace std;
@@ -75,7 +35,7 @@ namespace butl
void
b_info (std::vector<b_project_info>& r,
const vector<dir_path>& projects,
- bool ext_mods,
+ b_info_flags fl,
uint16_t verb,
const function<b_callback>& cmd_callback,
const path& program,
@@ -121,13 +81,22 @@ namespace butl
else
vops.push_back ("-q");
- vector<string> ps;
- ps.reserve (projects.size ());
+ string spec ("info(");
// Note that quoting is essential here.
//
- for (const dir_path& p: projects)
- ps.push_back ("'" + p.representation () + "'");
+ for (size_t i (0); i != projects.size(); ++i)
+ {
+ if (i != 0)
+ spec += ' ';
+
+ spec += '\'' + projects[i].representation () + '\'';
+ }
+
+ if ((fl & b_info_flags::subprojects) == b_info_flags::none)
+ spec += ",no_subprojects";
+
+ spec += ')';
pr = process_start_callback (
cmd_callback ? cmd_callback : [] (const char* const*, size_t) {},
@@ -136,10 +105,12 @@ namespace butl
2 /* stderr */,
pp,
vops,
- ext_mods ? nullptr : "--no-external-modules",
+ ((fl & b_info_flags::ext_mods) == b_info_flags::none
+ ? "--no-external-modules"
+ : nullptr),
"-s",
ops,
- "info:", ps);
+ spec);
pipe.out.close ();
ifdstream is (move (pipe.in), fdstream_mode::skip, ifdstream::badbit);
@@ -337,7 +308,7 @@ namespace butl
assert (!pr.wait ());
throw b_error (
- string ("process ") + pp.recall_string () + " " + to_string (*pr.exit),
+ string ("process ") + pp.recall_string () + ' ' + to_string (*pr.exit),
move (pr.exit));
}
catch (const process_error& e)
diff --git a/libbutl/b.mxx b/libbutl/b.hxx
index cca9696..d3fd2bf 100644
--- a/libbutl/b.mxx
+++ b/libbutl/b.hxx
@@ -1,13 +1,8 @@
-// file : libbutl/b.mxx -*- C++ -*-
+// file : libbutl/b.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <utility> // move()
@@ -15,33 +10,17 @@
#include <cstdint> // uint16_t
#include <stdexcept> // runtime_error
#include <functional>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.b;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.url;
-import butl.path;
-import butl.process;
-import butl.optional;
-import butl.project_name;
-import butl.standard_version;
-#else
-#include <libbutl/url.mxx>
-#include <libbutl/path.mxx>
-#include <libbutl/process.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/project-name.mxx>
-#include <libbutl/standard-version.mxx>
-#endif
+
+#include <libbutl/url.hxx>
+#include <libbutl/path.hxx>
+#include <libbutl/process.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/project-name.hxx>
+#include <libbutl/standard-version.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
class LIBBUTL_SYMEXPORT b_error: public std::runtime_error
{
@@ -72,11 +51,6 @@ LIBBUTL_MODEXPORT namespace butl
// result vector can be used to determine which project information caused
// the error.
//
- // Unless you need information that may come from external modules
- // (operations, meta-operations, etc), pass false as the ext_mods argument,
- // which results in passing --no-external-modules to the build2 program and
- // speeds up its execution.
- //
// You can also specify the build2 verbosity level, command line callback
// (see process_run_callback() for details), build program search details,
// and additional options.
@@ -113,12 +87,35 @@ LIBBUTL_MODEXPORT namespace butl
std::vector<std::string> modules;
};
+ enum class b_info_flags: std::uint16_t
+ {
+ // Retrieve information that may come from external modules (operations,
+ // meta-operations, etc). Omitting this flag results in passing
+ // --no-external-modules to the build2 program and speeds up its
+ // execution.
+ //
+ ext_mods = 0x1,
+
+ // Discover subprojects. Omitting this flag results in passing
+ // no_subprojects info meta-operation parameter to the build2 program and
+ // speeds up its execution.
+ //
+ subprojects = 0x2,
+
+ none = 0
+ };
+
+ inline b_info_flags operator& (b_info_flags, b_info_flags);
+ inline b_info_flags operator| (b_info_flags, b_info_flags);
+ inline b_info_flags operator&= (b_info_flags&, b_info_flags);
+ inline b_info_flags operator|= (b_info_flags&, b_info_flags);
+
using b_callback = void (const char* const args[], std::size_t n);
LIBBUTL_SYMEXPORT void
b_info (std::vector<b_project_info>& result,
const std::vector<dir_path>& projects,
- bool ext_mods,
+ b_info_flags,
std::uint16_t verb = 1,
const std::function<b_callback>& cmd_callback = {},
const path& program = path ("b"),
@@ -129,7 +126,7 @@ LIBBUTL_MODEXPORT namespace butl
//
inline b_project_info
b_info (const dir_path& project,
- bool ext_mods,
+ b_info_flags fl,
std::uint16_t verb = 1,
const std::function<b_callback>& cmd_callback = {},
const path& program = path ("b"),
@@ -139,7 +136,7 @@ LIBBUTL_MODEXPORT namespace butl
std::vector<b_project_info> r;
b_info (r,
std::vector<dir_path> ({project}),
- ext_mods,
+ fl,
verb,
cmd_callback,
program,
@@ -149,3 +146,5 @@ LIBBUTL_MODEXPORT namespace butl
return std::move (r[0]);
}
}
+
+#include <libbutl/b.ixx>
diff --git a/libbutl/b.ixx b/libbutl/b.ixx
new file mode 100644
index 0000000..1667101
--- /dev/null
+++ b/libbutl/b.ixx
@@ -0,0 +1,31 @@
+// file : libbutl/b.ixx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+namespace butl
+{
+ // b_info_flags
+ //
+ inline b_info_flags operator& (b_info_flags x, b_info_flags y)
+ {
+ return x &= y;
+ }
+
+ inline b_info_flags operator| (b_info_flags x, b_info_flags y)
+ {
+ return x |= y;
+ }
+
+ inline b_info_flags operator&= (b_info_flags& x, b_info_flags y)
+ {
+ return x = static_cast<b_info_flags> (
+ static_cast<std::uint16_t> (x) &
+ static_cast<std::uint16_t> (y));
+ }
+
+ inline b_info_flags operator|= (b_info_flags& x, b_info_flags y)
+ {
+ return x = static_cast<b_info_flags> (
+ static_cast<std::uint16_t> (x) |
+ static_cast<std::uint16_t> (y));
+ }
+}
diff --git a/libbutl/backtrace.cxx b/libbutl/backtrace.cxx
index 8c9c6ae..347e231 100644
--- a/libbutl/backtrace.cxx
+++ b/libbutl/backtrace.cxx
@@ -1,15 +1,14 @@
// file : libbutl/backtrace.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/backtrace.mxx>
-#endif
+#include <libbutl/backtrace.hxx>
// We only enable backtrace during bootstrap if we can do it without any
// complications of the build scripts/makefiles.
//
// With glibc linking with -rdynamic gives (non-static) function names.
-// FreeBSD/NetBSD requires explicitly linking -lexecinfo.
+// FreeBSD/NetBSD requires explicitly linking -lexecinfo. OpenBSD only has
+// this functionality built-in from 7.0 and requires -lexecinfo.
//
// Note that some libc implementation on Linux (most notably, musl), don't
// support this, at least not out of the box.
@@ -20,6 +19,11 @@
defined(__FreeBSD__) || \
defined(__NetBSD__)
# define LIBBUTL_BACKTRACE
+# elif defined (__OpenBSD__)
+# include <sys/param.h> // OpenBSD (yyyymm)
+# if OpenBSD >= 202110 // 7.0 was released in October 2021.
+# define LIBBUTL_BACKTRACE
+# endif
# endif
#else
# if defined(__GLIBC__) || \
@@ -35,30 +39,12 @@
#include <cassert>
-#ifndef __cpp_lib_modules_ts
-#include <string>
-
#ifdef LIBBUTL_BACKTRACE
# include <memory> // unique_ptr
# include <cstddef> // size_t
#endif
#include <exception>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.backtrace;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
-
-#endif
using namespace std;
diff --git a/libbutl/backtrace.mxx b/libbutl/backtrace.hxx
index f5a63d5..6afb6ea 100644
--- a/libbutl/backtrace.mxx
+++ b/libbutl/backtrace.hxx
@@ -1,28 +1,13 @@
-// file : libbutl/backtrace.mxx -*- C++ -*-
+// file : libbutl/backtrace.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.backtrace;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Return the calling thread's backtrace or empty string if this
// functionality is not supported or an error has occurred. The exact
diff --git a/libbutl/base64.cxx b/libbutl/base64.cxx
index 527c6af..282f7c2 100644
--- a/libbutl/base64.cxx
+++ b/libbutl/base64.cxx
@@ -1,37 +1,13 @@
// file : libbutl/base64.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/base64.mxx>
-#endif
-
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <vector>
+#include <libbutl/base64.hxx>
#include <cstddef> // size_t
#include <istream>
#include <ostream>
#include <iterator> // {istreambuf, ostreambuf, back_insert}_iterator
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.base64;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
-
-#endif
using namespace std;
@@ -40,19 +16,20 @@ namespace butl
static const char codes[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ static const char codes_url[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
// base64-encode the data in the iterator range [i, e). Write the encoded
- // data starting at the iterator position o.
+ // data starting at the iterator position o. If url is true, encode using
+ // base64url.
//
template <typename I, typename O>
static void
- base64_encode (I& i, const I& e, O& o)
+ base64_encode (I& i, const I& e, O& o, bool url = false)
{
const size_t un (65); // Non-existing index of the codes string.
for (size_t n (0); i != e; ++n)
{
- if (n && n % 19 == 0)
- *o++ = '\n'; // Split into lines, like the base64 utility does.
-
auto next = [&i] () {return static_cast<unsigned char> (*i++);};
unsigned char c (next ());
@@ -75,10 +52,26 @@ namespace butl
i4 = c & 0x3F;
}
- *o++ = codes[i1];
- *o++ = codes[i2];
- *o++ = i3 == un ? '=' : codes[i3];
- *o++ = i4 == un ? '=' : codes[i4];
+ if (!url)
+ {
+ if (n && n % 19 == 0)
+ *o++ = '\n'; // Split into lines, like the base64 utility does.
+
+ *o++ = codes[i1];
+ *o++ = codes[i2];
+ *o++ = i3 == un ? '=' : codes[i3];
+ *o++ = i4 == un ? '=' : codes[i4];
+ }
+ // base64url: different 63rd and 64th characters and no padding or
+ // newlines.
+ //
+ else
+ {
+ *o++ = codes_url[i1];
+ *o++ = codes_url[i2];
+ if (i3 != un) *o++ = codes_url[i3];
+ if (i4 != un) *o++ = codes_url[i4];
+ }
}
}
@@ -194,6 +187,47 @@ namespace butl
return r;
}
+ string
+ base64url_encode (istream& is)
+ {
+ if (!is.good ())
+ throw invalid_argument ("bad stream");
+
+ string r;
+ istreambuf_iterator<char> i (is);
+ back_insert_iterator<string> o (r);
+
+ base64_encode (i, istreambuf_iterator<char> (), o, true /* url */);
+ is.setstate (istream::eofbit);
+ return r;
+ }
+
+ void
+ base64url_encode (ostream& os, istream& is)
+ {
+ if (!os.good () || !is.good ())
+ throw invalid_argument ("bad stream");
+
+ istreambuf_iterator<char> i (is);
+ ostreambuf_iterator<char> o (os);
+ base64_encode (i, istreambuf_iterator<char> (), o, true /* url */);
+
+ if (o.failed ())
+ os.setstate (istream::badbit);
+
+ is.setstate (istream::eofbit);
+ }
+
+ string
+ base64url_encode (const std::vector<char>& v)
+ {
+ string r;
+ back_insert_iterator<string> o (r);
+ auto i (v.begin ());
+ base64_encode (i, v.end (), o, true /* url */);
+ return r;
+ }
+
void
base64_decode (ostream& os, istream& is)
{
diff --git a/libbutl/base64.mxx b/libbutl/base64.hxx
index 698b7e2..a0d1450 100644
--- a/libbutl/base64.mxx
+++ b/libbutl/base64.hxx
@@ -1,31 +1,15 @@
-// file : libbutl/base64.mxx -*- C++ -*-
+// file : libbutl/base64.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <iosfwd>
#include <string>
#include <vector>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.base64;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Base64-encode a stream or a buffer. Split the output into 76 char-long
// lines (new line is the 77th). If reading from a stream, check if it has
@@ -43,6 +27,25 @@ LIBBUTL_MODEXPORT namespace butl
LIBBUTL_SYMEXPORT std::string
base64_encode (const std::vector<char>&);
+ // Encode a stream or a buffer using base64url (RFC4648), a base64 variant
+ // with different 62nd and 63rd alphabet characters (- and _ instead of ~
+ // and .; to make it filesystem safe) and optional padding because the
+ // padding character `=` would have to be percent-encoded to be safe in
+ // URLs. This implementation does not output any padding, newlines or any
+ // other whitespace (which is required, for example, by RFC7519: JSON Web
+ // Token (JWT) and RFC7515: JSON Web Signature (JWS)).
+ //
+ // Note that base64url decoding has not yet been implemented.
+ //
+ LIBBUTL_SYMEXPORT void
+ base64url_encode (std::ostream&, std::istream&);
+
+ LIBBUTL_SYMEXPORT std::string
+ base64url_encode (std::istream&);
+
+ LIBBUTL_SYMEXPORT std::string
+ base64url_encode (const std::vector<char>&);
+
// Base64-decode a stream or a string. Throw invalid_argument if the input
// is not a valid base64 representation. If reading from a stream, check if
// it has badbit, failbit, or eofbit set and throw invalid_argument if
diff --git a/libbutl/buildfile b/libbutl/buildfile
index 5398f71..ba4ad96 100644
--- a/libbutl/buildfile
+++ b/libbutl/buildfile
@@ -1,36 +1,37 @@
# file : libbutl/buildfile
# license : MIT; see accompanying LICENSE file
-# This library was modularized using the Modules TS semantics (with support
-# for dual, module/header consumption) which was subsequently partially
-# dismantled. We, however, kept some of the changes in anticipation that they
-# would be useful when attempting to modularize using the merged modules
-# semantics. Specifically, there are currently headers with both .mxx and .hxx
-# extensions and the code is littered with the `#if __cpp_[lib_]modules_ts`
-# blocks. Note that it's important for the auto-generated header support
-# that the default extension for hxx{} is .hxx.
-#
-# @@ If/when going back to using mxx{}, make sure to cleanup explicit .mxx.
-#
-lib{butl}: {hxx ixx txx cxx}{** -uuid-* +uuid-io \
- -win32-utility \
- -version \
- -builtin-options} \
- hxx{**.mxx} {hxx}{version} {hxx ixx cxx}{builtin-options}
+lib{butl}: {hxx ixx txx cxx}{** -uuid-* +uuid-io \
+ -win32-utility \
+ -mingw-* \
+ -version \
+ -builtin-options} \
+ {hxx}{version} {hxx ixx cxx}{builtin-options}
tclass = $cxx.target.class
tsys = $cxx.target.system
windows = ($tclass == 'windows')
-# Exclude these from compilation on non-Windows targets.
+# Whether to use our own implementation of C++14 threads on MinGW (note:
+# requires Windows 7 or later).
+#
+# Note that for now we use built-in POSIX thread support during bootstrap
+# (which, as a side effect, verifies we still use MinGW GCC configured with
+# POSIX support, which we still need for TLS, exceptions, and thread-safe
+# static locals).
+#
+mingw_stdthread = ($tsys == 'mingw32')
+
+# Exclude these from compilation on targets where does not apply.
#
lib{butl}: {hxx ixx cxx}{win32-utility}: include = $windows
+lib{butl}: hxx{mingw-*}: include = $mingw_stdthread
# Our C-files are always included into C++-files that wrap the corresponding
-# API so treat them as files exclude from the compilation.
+# API so treat them as files to exclude from the compilation.
#
-lib{butl}: file{*.c *.h}
+lib{butl}: file{**.c **.h}
# Platform-specific UUID implementations.
#
@@ -38,6 +39,7 @@ lib{butl}: cxx{uuid-linux}: include = ($tclass == 'linux')
lib{butl}: cxx{uuid-macos}: include = ($tclass == 'macos')
lib{butl}: cxx{uuid-windows}: include = $windows
lib{butl}: cxx{uuid-freebsd}: include = ($tsys == 'freebsd' || $tsys == 'netbsd')
+lib{butl}: cxx{uuid-openbsd}: include = ($tsys == 'openbsd')
# GCC prior to version 6 has flaky `#pragma GCC diagnostic` so we have to
# disable certain warnings outright.
@@ -63,6 +65,14 @@ switch $tclass, $tsys
case 'bsd', 'freebsd' | 'netbsd'
cxx.libs += -lexecinfo
+
+ case 'bsd', 'openbsd'
+ {
+ # Built-in libexecinfo is only available since OpenBSD 7.0.
+ #
+ if (([uint64] $regex.replace($cxx.target.version, '(\d+)\..+', '\1')) >= 7)
+ cxx.libs += -lexecinfo
+ }
}
if! $windows
@@ -83,6 +93,9 @@ hxx{version}:
#
cxx.poptions =+ "-I$out_root" "-I$src_root"
+if $mingw_stdthread
+ cxx.poptions += -D_WIN32_WINNT=0x0601 -DLIBBUTL_MINGW_STDTHREAD
+
obja{*} bmia{*}: cxx.poptions += -DLIBBUTL_STATIC_BUILD
objs{*} bmis{*}: cxx.poptions += -DLIBBUTL_SHARED_BUILD
@@ -90,6 +103,9 @@ objs{*} bmis{*}: cxx.poptions += -DLIBBUTL_SHARED_BUILD
#
lib{butl}: cxx.export.poptions = "-I$out_root" "-I$src_root"
+if $mingw_stdthread
+ lib{butl}: cxx.export.poptions += -D_WIN32_WINNT=0x0601 -DLIBBUTL_MINGW_STDTHREAD
+
liba{butl}: cxx.export.poptions += -DLIBBUTL_STATIC
libs{butl}: cxx.export.poptions += -DLIBBUTL_SHARED
diff --git a/libbutl/builtin-options.cxx b/libbutl/builtin-options.cxx
index 5a243e5..98a47cf 100644
--- a/libbutl/builtin-options.cxx
+++ b/libbutl/builtin-options.cxx
@@ -18,6 +18,7 @@
#include <utility>
#include <ostream>
#include <sstream>
+#include <cstring>
namespace butl
{
@@ -26,7 +27,7 @@ namespace butl
// unknown_option
//
unknown_option::
- ~unknown_option () throw ()
+ ~unknown_option () noexcept
{
}
@@ -37,7 +38,7 @@ namespace butl
}
const char* unknown_option::
- what () const throw ()
+ what () const noexcept
{
return "unknown option";
}
@@ -45,7 +46,7 @@ namespace butl
// unknown_argument
//
unknown_argument::
- ~unknown_argument () throw ()
+ ~unknown_argument () noexcept
{
}
@@ -56,7 +57,7 @@ namespace butl
}
const char* unknown_argument::
- what () const throw ()
+ what () const noexcept
{
return "unknown argument";
}
@@ -64,7 +65,7 @@ namespace butl
// missing_value
//
missing_value::
- ~missing_value () throw ()
+ ~missing_value () noexcept
{
}
@@ -75,7 +76,7 @@ namespace butl
}
const char* missing_value::
- what () const throw ()
+ what () const noexcept
{
return "missing option value";
}
@@ -83,7 +84,7 @@ namespace butl
// invalid_value
//
invalid_value::
- ~invalid_value () throw ()
+ ~invalid_value () noexcept
{
}
@@ -98,7 +99,7 @@ namespace butl
}
const char* invalid_value::
- what () const throw ()
+ what () const noexcept
{
return "invalid option value";
}
@@ -112,7 +113,7 @@ namespace butl
}
const char* eos_reached::
- what () const throw ()
+ what () const noexcept
{
return "end of argument stream reached";
}
@@ -252,10 +253,31 @@ namespace butl
struct parser<bool>
{
static void
- parse (bool& x, scanner& s)
+ parse (bool& x, bool& xs, scanner& s)
{
- s.next ();
- x = true;
+ const char* o (s.next ());
+
+ if (s.more ())
+ {
+ const char* v (s.next ());
+
+ if (std::strcmp (v, "1") == 0 ||
+ std::strcmp (v, "true") == 0 ||
+ std::strcmp (v, "TRUE") == 0 ||
+ std::strcmp (v, "True") == 0)
+ x = true;
+ else if (std::strcmp (v, "0") == 0 ||
+ std::strcmp (v, "false") == 0 ||
+ std::strcmp (v, "FALSE") == 0 ||
+ std::strcmp (v, "False") == 0)
+ x = false;
+ else
+ throw invalid_value (o, v);
+ }
+ else
+ throw missing_value (o);
+
+ xs = true;
}
};
@@ -365,6 +387,56 @@ namespace butl
}
};
+ template <typename K, typename V, typename C>
+ struct parser<std::multimap<K, V, C> >
+ {
+ static void
+ parse (std::multimap<K, V, C>& m, bool& xs, scanner& s)
+ {
+ const char* o (s.next ());
+
+ if (s.more ())
+ {
+ std::size_t pos (s.position ());
+ std::string ov (s.next ());
+ std::string::size_type p = ov.find ('=');
+
+ K k = K ();
+ V v = V ();
+ std::string kstr (ov, 0, p);
+ std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ()));
+
+ int ac (2);
+ char* av[] =
+ {
+ const_cast<char*> (o),
+ 0
+ };
+
+ bool dummy;
+ if (!kstr.empty ())
+ {
+ av[1] = const_cast<char*> (kstr.c_str ());
+ argv_scanner s (0, ac, av, false, pos);
+ parser<K>::parse (k, dummy, s);
+ }
+
+ if (!vstr.empty ())
+ {
+ av[1] = const_cast<char*> (vstr.c_str ());
+ argv_scanner s (0, ac, av, false, pos);
+ parser<V>::parse (v, dummy, s);
+ }
+
+ m.insert (typename std::multimap<K, V, C>::value_type (k, v));
+ }
+ else
+ throw missing_value (o);
+
+ xs = true;
+ }
+ };
+
template <typename X, typename T, T X::*M>
void
thunk (X& x, scanner& s)
@@ -372,6 +444,14 @@ namespace butl
parser<T>::parse (x.*M, s);
}
+ template <typename X, bool X::*M>
+ void
+ thunk (X& x, scanner& s)
+ {
+ s.next ();
+ x.*M = true;
+ }
+
template <typename X, typename T, T X::*M, bool X::*S>
void
thunk (X& x, scanner& s)
@@ -382,7 +462,6 @@ namespace butl
}
#include <map>
-#include <cstring>
namespace butl
{
@@ -733,15 +812,15 @@ namespace butl
_cli_cp_options_map_init ()
{
_cli_cp_options_map_["--recursive"] =
- &::butl::cli::thunk< cp_options, bool, &cp_options::recursive_ >;
+ &::butl::cli::thunk< cp_options, &cp_options::recursive_ >;
_cli_cp_options_map_["-R"] =
- &::butl::cli::thunk< cp_options, bool, &cp_options::recursive_ >;
+ &::butl::cli::thunk< cp_options, &cp_options::recursive_ >;
_cli_cp_options_map_["-r"] =
- &::butl::cli::thunk< cp_options, bool, &cp_options::recursive_ >;
+ &::butl::cli::thunk< cp_options, &cp_options::recursive_ >;
_cli_cp_options_map_["--preserve"] =
- &::butl::cli::thunk< cp_options, bool, &cp_options::preserve_ >;
+ &::butl::cli::thunk< cp_options, &cp_options::preserve_ >;
_cli_cp_options_map_["-p"] =
- &::butl::cli::thunk< cp_options, bool, &cp_options::preserve_ >;
+ &::butl::cli::thunk< cp_options, &cp_options::preserve_ >;
}
};
@@ -1007,9 +1086,9 @@ namespace butl
_cli_date_options_map_init ()
{
_cli_date_options_map_["--utc"] =
- &::butl::cli::thunk< date_options, bool, &date_options::utc_ >;
+ &::butl::cli::thunk< date_options, &date_options::utc_ >;
_cli_date_options_map_["-u"] =
- &::butl::cli::thunk< date_options, bool, &date_options::utc_ >;
+ &::butl::cli::thunk< date_options, &date_options::utc_ >;
}
};
@@ -1192,6 +1271,269 @@ namespace butl
return r;
}
+ // find_options
+ //
+
+ find_options::
+ find_options ()
+ {
+ }
+
+ bool find_options::
+ parse (int& argc,
+ char** argv,
+ bool erase,
+ ::butl::cli::unknown_mode opt,
+ ::butl::cli::unknown_mode arg)
+ {
+ ::butl::cli::argv_scanner s (argc, argv, erase);
+ bool r = _parse (s, opt, arg);
+ return r;
+ }
+
+ bool find_options::
+ parse (int start,
+ int& argc,
+ char** argv,
+ bool erase,
+ ::butl::cli::unknown_mode opt,
+ ::butl::cli::unknown_mode arg)
+ {
+ ::butl::cli::argv_scanner s (start, argc, argv, erase);
+ bool r = _parse (s, opt, arg);
+ return r;
+ }
+
+ bool find_options::
+ parse (int& argc,
+ char** argv,
+ int& end,
+ bool erase,
+ ::butl::cli::unknown_mode opt,
+ ::butl::cli::unknown_mode arg)
+ {
+ ::butl::cli::argv_scanner s (argc, argv, erase);
+ bool r = _parse (s, opt, arg);
+ end = s.end ();
+ return r;
+ }
+
+ bool find_options::
+ parse (int start,
+ int& argc,
+ char** argv,
+ int& end,
+ bool erase,
+ ::butl::cli::unknown_mode opt,
+ ::butl::cli::unknown_mode arg)
+ {
+ ::butl::cli::argv_scanner s (start, argc, argv, erase);
+ bool r = _parse (s, opt, arg);
+ end = s.end ();
+ return r;
+ }
+
+ bool find_options::
+ parse (::butl::cli::scanner& s,
+ ::butl::cli::unknown_mode opt,
+ ::butl::cli::unknown_mode arg)
+ {
+ bool r = _parse (s, opt, arg);
+ return r;
+ }
+
+ typedef
+ std::map<std::string, void (*) (find_options&, ::butl::cli::scanner&)>
+ _cli_find_options_map;
+
+ static _cli_find_options_map _cli_find_options_map_;
+
+ struct _cli_find_options_map_init
+ {
+ _cli_find_options_map_init ()
+ {
+ }
+ };
+
+ static _cli_find_options_map_init _cli_find_options_map_init_;
+
+ bool find_options::
+ _parse (const char* o, ::butl::cli::scanner& s)
+ {
+ _cli_find_options_map::const_iterator i (_cli_find_options_map_.find (o));
+
+ if (i != _cli_find_options_map_.end ())
+ {
+ (*(i->second)) (*this, s);
+ return true;
+ }
+
+ return false;
+ }
+
+ bool find_options::
+ _parse (::butl::cli::scanner& s,
+ ::butl::cli::unknown_mode opt_mode,
+ ::butl::cli::unknown_mode arg_mode)
+ {
+ // Can't skip combined flags (--no-combined-flags).
+ //
+ assert (opt_mode != ::butl::cli::unknown_mode::skip);
+
+ bool r = false;
+ bool opt = true;
+
+ while (s.more ())
+ {
+ const char* o = s.peek ();
+
+ if (std::strcmp (o, "--") == 0)
+ {
+ opt = false;
+ }
+
+ if (opt)
+ {
+ if (_parse (o, s))
+ {
+ r = true;
+ continue;
+ }
+
+ if (std::strncmp (o, "-", 1) == 0 && o[1] != '\0')
+ {
+ // Handle combined option values.
+ //
+ std::string co;
+ if (const char* v = std::strchr (o, '='))
+ {
+ co.assign (o, 0, v - o);
+ ++v;
+
+ int ac (2);
+ char* av[] =
+ {
+ const_cast<char*> (co.c_str ()),
+ const_cast<char*> (v)
+ };
+
+ ::butl::cli::argv_scanner ns (0, ac, av);
+
+ if (_parse (co.c_str (), ns))
+ {
+ // Parsed the option but not its value?
+ //
+ if (ns.end () != 2)
+ throw ::butl::cli::invalid_value (co, v);
+
+ s.next ();
+ r = true;
+ continue;
+ }
+ else
+ {
+ // Set the unknown option and fall through.
+ //
+ o = co.c_str ();
+ }
+ }
+
+ // Handle combined flags.
+ //
+ char cf[3];
+ {
+ const char* p = o + 1;
+ for (; *p != '\0'; ++p)
+ {
+ if (!((*p >= 'a' && *p <= 'z') ||
+ (*p >= 'A' && *p <= 'Z') ||
+ (*p >= '0' && *p <= '9')))
+ break;
+ }
+
+ if (*p == '\0')
+ {
+ for (p = o + 1; *p != '\0'; ++p)
+ {
+ std::strcpy (cf, "-");
+ cf[1] = *p;
+ cf[2] = '\0';
+
+ int ac (1);
+ char* av[] =
+ {
+ cf
+ };
+
+ ::butl::cli::argv_scanner ns (0, ac, av);
+
+ if (!_parse (cf, ns))
+ break;
+ }
+
+ if (*p == '\0')
+ {
+ // All handled.
+ //
+ s.next ();
+ r = true;
+ continue;
+ }
+ else
+ {
+ // Set the unknown option and fall through.
+ //
+ o = cf;
+ }
+ }
+ }
+
+ switch (opt_mode)
+ {
+ case ::butl::cli::unknown_mode::skip:
+ {
+ s.skip ();
+ r = true;
+ continue;
+ }
+ case ::butl::cli::unknown_mode::stop:
+ {
+ break;
+ }
+ case ::butl::cli::unknown_mode::fail:
+ {
+ throw ::butl::cli::unknown_option (o);
+ }
+ }
+
+ break;
+ }
+ }
+
+ switch (arg_mode)
+ {
+ case ::butl::cli::unknown_mode::skip:
+ {
+ s.skip ();
+ r = true;
+ continue;
+ }
+ case ::butl::cli::unknown_mode::stop:
+ {
+ break;
+ }
+ case ::butl::cli::unknown_mode::fail:
+ {
+ throw ::butl::cli::unknown_argument (o);
+ }
+ }
+
+ break;
+ }
+
+ return r;
+ }
+
// ln_options
//
@@ -1275,9 +1617,9 @@ namespace butl
_cli_ln_options_map_init ()
{
_cli_ln_options_map_["--symbolic"] =
- &::butl::cli::thunk< ln_options, bool, &ln_options::symbolic_ >;
+ &::butl::cli::thunk< ln_options, &ln_options::symbolic_ >;
_cli_ln_options_map_["-s"] =
- &::butl::cli::thunk< ln_options, bool, &ln_options::symbolic_ >;
+ &::butl::cli::thunk< ln_options, &ln_options::symbolic_ >;
}
};
@@ -1543,9 +1885,9 @@ namespace butl
_cli_mkdir_options_map_init ()
{
_cli_mkdir_options_map_["--parents"] =
- &::butl::cli::thunk< mkdir_options, bool, &mkdir_options::parents_ >;
+ &::butl::cli::thunk< mkdir_options, &mkdir_options::parents_ >;
_cli_mkdir_options_map_["-p"] =
- &::butl::cli::thunk< mkdir_options, bool, &mkdir_options::parents_ >;
+ &::butl::cli::thunk< mkdir_options, &mkdir_options::parents_ >;
}
};
@@ -1811,9 +2153,9 @@ namespace butl
_cli_mv_options_map_init ()
{
_cli_mv_options_map_["--force"] =
- &::butl::cli::thunk< mv_options, bool, &mv_options::force_ >;
+ &::butl::cli::thunk< mv_options, &mv_options::force_ >;
_cli_mv_options_map_["-f"] =
- &::butl::cli::thunk< mv_options, bool, &mv_options::force_ >;
+ &::butl::cli::thunk< mv_options, &mv_options::force_ >;
}
};
@@ -2080,13 +2422,13 @@ namespace butl
_cli_rm_options_map_init ()
{
_cli_rm_options_map_["--recursive"] =
- &::butl::cli::thunk< rm_options, bool, &rm_options::recursive_ >;
+ &::butl::cli::thunk< rm_options, &rm_options::recursive_ >;
_cli_rm_options_map_["-r"] =
- &::butl::cli::thunk< rm_options, bool, &rm_options::recursive_ >;
+ &::butl::cli::thunk< rm_options, &rm_options::recursive_ >;
_cli_rm_options_map_["--force"] =
- &::butl::cli::thunk< rm_options, bool, &rm_options::force_ >;
+ &::butl::cli::thunk< rm_options, &rm_options::force_ >;
_cli_rm_options_map_["-f"] =
- &::butl::cli::thunk< rm_options, bool, &rm_options::force_ >;
+ &::butl::cli::thunk< rm_options, &rm_options::force_ >;
}
};
@@ -2352,9 +2694,9 @@ namespace butl
_cli_rmdir_options_map_init ()
{
_cli_rmdir_options_map_["--force"] =
- &::butl::cli::thunk< rmdir_options, bool, &rmdir_options::force_ >;
+ &::butl::cli::thunk< rmdir_options, &rmdir_options::force_ >;
_cli_rmdir_options_map_["-f"] =
- &::butl::cli::thunk< rmdir_options, bool, &rmdir_options::force_ >;
+ &::butl::cli::thunk< rmdir_options, &rmdir_options::force_ >;
}
};
@@ -2623,13 +2965,13 @@ namespace butl
_cli_sed_options_map_init ()
{
_cli_sed_options_map_["--quiet"] =
- &::butl::cli::thunk< sed_options, bool, &sed_options::quiet_ >;
+ &::butl::cli::thunk< sed_options, &sed_options::quiet_ >;
_cli_sed_options_map_["-n"] =
- &::butl::cli::thunk< sed_options, bool, &sed_options::quiet_ >;
+ &::butl::cli::thunk< sed_options, &sed_options::quiet_ >;
_cli_sed_options_map_["--in-place"] =
- &::butl::cli::thunk< sed_options, bool, &sed_options::in_place_ >;
+ &::butl::cli::thunk< sed_options, &sed_options::in_place_ >;
_cli_sed_options_map_["-i"] =
- &::butl::cli::thunk< sed_options, bool, &sed_options::in_place_ >;
+ &::butl::cli::thunk< sed_options, &sed_options::in_place_ >;
_cli_sed_options_map_["--expression"] =
&::butl::cli::thunk< sed_options, std::vector<std::string>, &sed_options::expression_,
&sed_options::expression_specified_ >;
@@ -3165,13 +3507,13 @@ namespace butl
_cli_test_options_map_init ()
{
_cli_test_options_map_["--file"] =
- &::butl::cli::thunk< test_options, bool, &test_options::file_ >;
+ &::butl::cli::thunk< test_options, &test_options::file_ >;
_cli_test_options_map_["-f"] =
- &::butl::cli::thunk< test_options, bool, &test_options::file_ >;
+ &::butl::cli::thunk< test_options, &test_options::file_ >;
_cli_test_options_map_["--directory"] =
- &::butl::cli::thunk< test_options, bool, &test_options::directory_ >;
+ &::butl::cli::thunk< test_options, &test_options::directory_ >;
_cli_test_options_map_["-d"] =
- &::butl::cli::thunk< test_options, bool, &test_options::directory_ >;
+ &::butl::cli::thunk< test_options, &test_options::directory_ >;
}
};
diff --git a/libbutl/builtin-options.hxx b/libbutl/builtin-options.hxx
index 6288e54..70179dd 100644
--- a/libbutl/builtin-options.hxx
+++ b/libbutl/builtin-options.hxx
@@ -68,7 +68,7 @@ namespace butl
{
public:
virtual
- ~unknown_option () throw ();
+ ~unknown_option () noexcept;
unknown_option (const std::string& option);
@@ -79,7 +79,7 @@ namespace butl
print (::std::ostream&) const;
virtual const char*
- what () const throw ();
+ what () const noexcept;
private:
std::string option_;
@@ -89,7 +89,7 @@ namespace butl
{
public:
virtual
- ~unknown_argument () throw ();
+ ~unknown_argument () noexcept;
unknown_argument (const std::string& argument);
@@ -100,7 +100,7 @@ namespace butl
print (::std::ostream&) const;
virtual const char*
- what () const throw ();
+ what () const noexcept;
private:
std::string argument_;
@@ -110,7 +110,7 @@ namespace butl
{
public:
virtual
- ~missing_value () throw ();
+ ~missing_value () noexcept;
missing_value (const std::string& option);
@@ -121,7 +121,7 @@ namespace butl
print (::std::ostream&) const;
virtual const char*
- what () const throw ();
+ what () const noexcept;
private:
std::string option_;
@@ -131,7 +131,7 @@ namespace butl
{
public:
virtual
- ~invalid_value () throw ();
+ ~invalid_value () noexcept;
invalid_value (const std::string& option,
const std::string& value,
@@ -150,7 +150,7 @@ namespace butl
print (::std::ostream&) const;
virtual const char*
- what () const throw ();
+ what () const noexcept;
private:
std::string option_;
@@ -165,7 +165,7 @@ namespace butl
print (::std::ostream&) const;
virtual const char*
- what () const throw ();
+ what () const noexcept;
};
// Command line argument scanner interface.
@@ -484,6 +484,67 @@ namespace butl
bool utc_;
};
+ class find_options
+ {
+ public:
+ find_options ();
+
+ // Return true if anything has been parsed.
+ //
+ bool
+ parse (int& argc,
+ char** argv,
+ bool erase = false,
+ ::butl::cli::unknown_mode option = ::butl::cli::unknown_mode::fail,
+ ::butl::cli::unknown_mode argument = ::butl::cli::unknown_mode::stop);
+
+ bool
+ parse (int start,
+ int& argc,
+ char** argv,
+ bool erase = false,
+ ::butl::cli::unknown_mode option = ::butl::cli::unknown_mode::fail,
+ ::butl::cli::unknown_mode argument = ::butl::cli::unknown_mode::stop);
+
+ bool
+ parse (int& argc,
+ char** argv,
+ int& end,
+ bool erase = false,
+ ::butl::cli::unknown_mode option = ::butl::cli::unknown_mode::fail,
+ ::butl::cli::unknown_mode argument = ::butl::cli::unknown_mode::stop);
+
+ bool
+ parse (int start,
+ int& argc,
+ char** argv,
+ int& end,
+ bool erase = false,
+ ::butl::cli::unknown_mode option = ::butl::cli::unknown_mode::fail,
+ ::butl::cli::unknown_mode argument = ::butl::cli::unknown_mode::stop);
+
+ bool
+ parse (::butl::cli::scanner&,
+ ::butl::cli::unknown_mode option = ::butl::cli::unknown_mode::fail,
+ ::butl::cli::unknown_mode argument = ::butl::cli::unknown_mode::stop);
+
+ // Option accessors.
+ //
+ // Implementation details.
+ //
+ protected:
+ bool
+ _parse (const char*, ::butl::cli::scanner&);
+
+ private:
+ bool
+ _parse (::butl::cli::scanner&,
+ ::butl::cli::unknown_mode option,
+ ::butl::cli::unknown_mode argument);
+
+ public:
+ };
+
class ln_options
{
public:
diff --git a/libbutl/builtin-options.ixx b/libbutl/builtin-options.ixx
index b977f16..e118156 100644
--- a/libbutl/builtin-options.ixx
+++ b/libbutl/builtin-options.ixx
@@ -193,6 +193,9 @@ namespace butl
return this->utc_;
}
+ // find_options
+ //
+
// ln_options
//
diff --git a/libbutl/builtin.cli b/libbutl/builtin.cli
index adc47fa..23a5708 100644
--- a/libbutl/builtin.cli
+++ b/libbutl/builtin.cli
@@ -34,6 +34,11 @@ namespace butl
bool --utc|-u;
};
+ class find_options
+ {
+ // No options so far (expression/primaries handled as arguments).
+ };
+
class ln_options
{
bool --symbolic|-s;
diff --git a/libbutl/builtin.cxx b/libbutl/builtin.cxx
index 61df568..2755bf1 100644
--- a/libbutl/builtin.cxx
+++ b/libbutl/builtin.cxx
@@ -1,28 +1,16 @@
// file : libbutl/builtin.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/builtin.mxx>
-#endif
+#include <libbutl/builtin.hxx>
#ifdef _WIN32
# include <libbutl/win32-utility.hxx>
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <map>
-#include <string>
-#include <vector>
-#include <thread>
-#include <utility> // move(), forward()
-#include <cstdint> // uint*_t
-#include <functional>
-
#include <ios>
#include <chrono>
#include <cerrno>
+#include <cassert>
#include <ostream>
#include <sstream>
#include <cstdlib> // strtoull()
@@ -30,41 +18,16 @@
#include <exception>
#include <system_error>
-#endif
+#include <libbutl/regex.hxx>
+#include <libbutl/path-io.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream,exception),
+ // throw_generic_error()
+#include <libbutl/optional.hxx>
+#include <libbutl/filesystem.hxx>
+#include <libbutl/small-vector.hxx>
#include <libbutl/builtin-options.hxx>
-#ifdef __cpp_modules_ts
-module butl.builtin;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-import std.threading;
-#endif
-import butl.path;
-import butl.fdstream;
-import butl.timestamp;
-#endif
-
-import butl.regex;
-import butl.path_io;
-import butl.utility; // operator<<(ostream,exception),
- // throw_generic_error()
-import butl.optional;
-import butl.filesystem;
-import butl.small_vector;
-#else
-#include <libbutl/regex.mxx>
-#include <libbutl/path-io.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/filesystem.mxx>
-#include <libbutl/small-vector.mxx>
-#endif
-
// Strictly speaking a builtin which reads/writes from/to standard streams
// must be asynchronous so that the caller can communicate with it through
// pipes without being blocked on I/O operations. However, as an optimization,
@@ -507,7 +470,7 @@ namespace butl
if (cbs.create)
call (fail, cbs.create, to, false /* pre */);
- for (const auto& de: dir_iterator (from, false /* ignore_dangling */))
+ for (const auto& de: dir_iterator (from, dir_iterator::no_follow))
{
path f (from / de.path ());
path t (to / de.path ());
@@ -853,6 +816,314 @@ namespace butl
return builtin (r = 0);
}
+ // find <start-path>... [-name <pattern>]
+ // [-type <type>]
+ // [-mindepth <depth>]
+ // [-maxdepth <depth>]
+ //
+ // Note: must be executed asynchronously.
+ //
+ static uint8_t
+ find (const strings& args,
+ auto_fd in, auto_fd out, auto_fd err,
+ const dir_path& cwd,
+ const builtin_callbacks& cbs) noexcept
+ try
+ {
+ uint8_t r (1);
+ ofdstream cerr (err != nullfd ? move (err) : fddup (stderr_fd ()));
+
+ // Note that on some errors we will issue diagnostics but continue the
+ // search and return with non-zero code at the end. This is consistent
+ // with how major implementations behave (see below).
+ //
+ bool error_occured (false);
+ auto error = [&cerr, &error_occured] (bool fail = false)
+ {
+ error_occured = true;
+ return error_record (cerr, fail, "find");
+ };
+
+ auto fail = [&error] () {return error (true /* fail */);};
+
+ try
+ {
+ in.close ();
+ ofdstream cout (out != nullfd ? move (out) : fddup (stdout_fd ()));
+
+ // Parse arguments.
+ //
+ cli::vector_scanner scan (args);
+
+ // Currently, we don't expect any options.
+ //
+ parse<find_options> (scan, args, cbs.parse_option, fail);
+
+ // Parse path arguments until the first primary (starts with '-') is
+ // encountered.
+ //
+ small_vector<path, 1> paths;
+
+ while (scan.more ())
+ {
+ if (*scan.peek () == '-')
+ break;
+
+ try
+ {
+ paths.emplace_back (scan.next ());
+ }
+ catch (const invalid_path& e)
+ {
+ fail () << "invalid path '" << e.path << "'";
+ }
+ }
+
+ // Note that POSIX doesn't explicitly describe the behavior if no paths
+ // are specified on the command line. On Linux the current directory is
+ // assumed in this case. We, however, will follow the FreeBSD behavior
+ // and fail since this seems to be less error-prone.
+ //
+ if (paths.empty ())
+ fail () << "missing start path";
+
+ // Parse primaries.
+ //
+ optional<string> name;
+ optional<entry_type> type;
+ optional<uint64_t> min_depth;
+ optional<uint64_t> max_depth;
+
+ while (scan.more ())
+ {
+ const char* p (scan.next ());
+
+ // Return the string value of the current primary. Fail if absent or
+ // empty, unless empty value is allowed.
+ //
+ auto str = [p, &scan, &fail] (bool allow_empty = false)
+ {
+ if (!scan.more ())
+ {
+ fail () << "missing value for primary '" << p << "'";
+ }
+
+ string n (p); // Save for diagnostics.
+ string r (scan.next ());
+
+ if (r.empty () && !allow_empty)
+ fail () << "empty value for primary '" << n << "'";
+
+ return r;
+ };
+
+ // Return the unsigned numeric value of the current primary. Fail if
+ // absent or is not a valid number.
+ //
+ auto num = [p, &str, &fail] ()
+ {
+ string n (p); // Save for diagnostics.
+ string s (str ());
+
+ const char* b (s.c_str ());
+ char* e (nullptr);
+ errno = 0; // We must clear it according to POSIX.
+ uint64_t r (strtoull (b, &e, 10)); // Can't throw.
+
+ if (errno == ERANGE || e != b + s.size ())
+ fail () << "invalid value '" << s << "' for primary '" << n << "'";
+
+ return r;
+ };
+
+ if (strcmp (p, "-name") == 0)
+ {
+ // Note that the empty never-matching pattern is allowed.
+ //
+ name = str (true /* allow_empty */);
+ }
+ else if (strcmp (p, "-type") == 0)
+ {
+ string s (str ());
+ char t (s.size () == 1 ? s[0] : '\0');
+
+ switch (t)
+ {
+ case 'f': type = entry_type::regular; break;
+ case 'd': type = entry_type::directory; break;
+ case 'l': type = entry_type::symlink; break;
+ default: fail () << "invalid value '" << s << "' for primary '-type'";
+ }
+ }
+ else if (strcmp (p, "-mindepth") == 0)
+ {
+ min_depth = num ();
+ }
+ else if (strcmp (p, "-maxdepth") == 0)
+ {
+ max_depth = num ();
+ }
+ else
+ fail () << "unknown primary '" << p << "'";
+ }
+
+ // Print the path if the expression evaluates to true for it. Traverse
+ // further down if the path refers to a directory and the maximum depth
+ // is not specified or is not reached.
+ //
+ // Note that paths for evaluating/printing (pp) and for
+ // stating/traversing (ap) are passed separately. The former is
+ // potentially relative and the latter is absolute. Also note that
+ // for optimization we separately pass the base name simple path.
+ //
+ auto find = [&cout,
+ &name,
+ &type,
+ &min_depth,
+ &max_depth,
+ &fail] (const path& pp,
+ const path& ap,
+ const path& bp,
+ entry_type t,
+ uint64_t level,
+ const auto& find) -> void
+ {
+ // Print the path if no primary evaluates to false.
+ //
+ if ((!type || *type == t) &&
+ (!min_depth || level >= *min_depth) &&
+ (!name || path_match (bp.string (), *name)))
+ {
+ // Print the trailing directory separator, if present.
+ //
+ if (pp.to_directory ())
+ {
+ // The trailing directory separator can only be present for
+ // paths specified on the command line.
+ //
+ assert (level == 0);
+
+ cout << pp.representation () << '\n';
+ }
+ else
+ cout << pp << '\n';
+ }
+
+ // Traverse the directory, unless the max depth is specified and
+ // reached.
+ //
+ if (t == entry_type::directory && (!max_depth || level < *max_depth))
+ try
+ {
+ for (const auto& de: dir_iterator (path_cast<dir_path> (ap),
+ dir_iterator::no_follow))
+ {
+ find (pp / de.path (),
+ ap / de.path (),
+ de.path (),
+ de.ltype (),
+ level + 1,
+ find);
+ }
+ }
+ catch (const system_error& e)
+ {
+ fail () << "unable to scan directory '" << pp << "': " << e;
+ }
+ };
+
+ dir_path wd;
+
+ for (const path& p: paths)
+ {
+ // Complete the path if it is relative, so that we can properly stat
+ // it and, potentially, traverse. Note that we don't normalize it
+ // since POSIX requires that the paths should be evaluated (by
+ // primaries) and printed unaltered.
+ //
+ path ap;
+
+ if (p.relative ())
+ {
+ if (wd.empty () && cwd.relative ())
+ wd = current_directory (cwd, fail);
+
+ ap = (!wd.empty () ? wd : cwd) / p;
+ }
+
+ // Issue an error if the path is empty, doesn't exist, or has the
+ // trailing directory separator but refers to a non-directory.
+ //
+ // Note that POSIX doesn't explicitly describe the behavior if any of
+ // the above happens. We will follow the behavior which is common for
+ // both Linux and FreeBSD by issuing the diagnostics, proceeding to
+ // the subsequent paths, and returning with non-zero code at the end.
+ //
+ if (p.empty ())
+ {
+ error () << "empty path";
+ continue;
+ }
+
+ const path& fp (!ap.empty () ? ap : p);
+ pair<bool, entry_stat> pe;
+
+ try
+ {
+ pe = path_entry (fp);
+ }
+ catch (const system_error& e)
+ {
+ fail () << "unable to stat '" << p << "': " << e;
+ }
+
+ if (!pe.first)
+ {
+ error () << "'" << p << "' doesn't exists";
+ continue;
+ }
+
+ entry_type t (pe.second.type);
+
+ if (p.to_directory () && t != entry_type::directory)
+ {
+ error () << "'" << p << "' is not a directory";
+ continue;
+ }
+
+ find (p, fp, p.leaf (), t, 0 /* level */, find);
+ }
+
+ cout.close ();
+ r = !error_occured ? 0 : 1;
+ }
+ // Can be thrown while closing cin or creating, writing to, or closing
+ // cout or writing to cerr.
+ //
+ catch (const io_error& e)
+ {
+ error () << e;
+ }
+ catch (const failed&)
+ {
+ // Diagnostics has already been issued.
+ }
+ catch (const cli::exception& e)
+ {
+ error () << e;
+ }
+
+ cerr.close ();
+ return r;
+ }
+ // In particular, handles io_error exception potentially thrown while
+ // creating, writing to, or closing cerr.
+ //
+ catch (const std::exception&)
+ {
+ return 1;
+ }
+
// Create a symlink to a file or directory at the specified path and calling
// the hook for the created filesystem entries. The paths must be absolute
// and normalized. Fall back to creating a hardlink, if symlink creation is
@@ -2214,17 +2485,17 @@ namespace butl
{
unique_ptr<builtin::async_state> s (
new builtin::async_state (
+ r,
[fn,
- &r,
&args,
in = move (in), out = move (out), err = move (err),
&cwd,
- &cbs] () mutable noexcept
+ &cbs] () mutable noexcept -> uint8_t
{
- r = fn (args,
- move (in), move (out), move (err),
- cwd,
- cbs);
+ return fn (args,
+ move (in), move (out), move (err),
+ cwd,
+ cbs);
}));
return builtin (r, move (s));
@@ -2264,6 +2535,7 @@ namespace butl
{"diff", {nullptr, 2}},
{"echo", {&async_impl<&echo>, 2}},
{"false", {&false_, 0}},
+ {"find", {&async_impl<&find>, 2}},
{"ln", {&sync_impl<&ln>, 2}},
{"mkdir", {&sync_impl<&mkdir>, 2}},
{"mv", {&sync_impl<&mv>, 2}},
@@ -2283,7 +2555,7 @@ namespace butl
{
if (state_ != nullptr)
{
- unique_lock<mutex> l (state_->mutex);
+ unique_lock l (state_->mutex);
if (!state_->finished)
state_->condv.wait (l, [this] {return state_->finished;});
@@ -2298,7 +2570,7 @@ namespace butl
{
if (state_ != nullptr)
{
- unique_lock<mutex> l (state_->mutex);
+ unique_lock l (state_->mutex);
if (!state_->finished &&
!state_->condv.wait_for (l, tm, [this] {return state_->finished;}))
diff --git a/libbutl/builtin.mxx b/libbutl/builtin.hxx
index a99d6f4..b301f8a 100644
--- a/libbutl/builtin.mxx
+++ b/libbutl/builtin.hxx
@@ -1,47 +1,35 @@
-// file : libbutl/builtin.mxx -*- C++ -*-
+// file : libbutl/builtin.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-
-// C includes.
-#ifndef __cpp_lib_modules_ts
#include <map>
-#include <mutex>
#include <string>
#include <vector>
-#include <thread>
#include <chrono>
#include <memory> // unique_ptr
#include <cstddef> // size_t
#include <utility> // move()
#include <cstdint> // uint8_t
#include <functional>
-#include <condition_variable>
-#endif
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.builtin;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.threading;
-#endif
-import butl.path;
-import butl.fdstream;
-import butl.timestamp;
+#ifndef LIBBUTL_MINGW_STDTHREAD
+# include <mutex>
+# include <thread>
+# include <condition_variable>
#else
-#include <libbutl/path.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/timestamp.mxx>
+# include <libbutl/mingw-mutex.hxx>
+# include <libbutl/mingw-thread.hxx>
+# include <libbutl/mingw-condition_variable.hxx>
#endif
+#include <libbutl/path.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/timestamp.hxx>
+
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// A process/thread-like object representing a running builtin.
//
@@ -75,12 +63,26 @@ LIBBUTL_MODEXPORT namespace butl
~builtin () {if (state_ != nullptr) state_->thread.join ();}
public:
+#ifndef LIBBUTL_MINGW_STDTHREAD
+ using mutex_type = std::mutex;
+ using condition_variable_type = std::condition_variable;
+ using thread_type = std::thread;
+
+ using unique_lock = std::unique_lock<mutex_type>;
+#else
+ using mutex_type = mingw_stdthread::mutex;
+ using condition_variable_type = mingw_stdthread::condition_variable;
+ using thread_type = mingw_stdthread::thread;
+
+ using unique_lock = mingw_stdthread::unique_lock<mutex_type>;
+#endif
+
struct async_state
{
bool finished = false;
- std::mutex mutex;
- std::condition_variable condv;
- std::thread thread;
+ mutex_type mutex;
+ condition_variable_type condv;
+ thread_type thread;
// Note that we can't use std::function as an argument type to get rid
// of the template since std::function can only be instantiated with a
@@ -88,8 +90,7 @@ LIBBUTL_MODEXPORT namespace butl
// be able to capture auto_fd by value in a lambda, etc).
//
template <typename F>
- explicit
- async_state (F);
+ async_state (uint8_t&, F);
};
builtin (std::uint8_t& r, std::unique_ptr<async_state>&& s = nullptr)
diff --git a/libbutl/builtin.ixx b/libbutl/builtin.ixx
index 0356f8b..d77590b 100644
--- a/libbutl/builtin.ixx
+++ b/libbutl/builtin.ixx
@@ -25,7 +25,7 @@ namespace butl
{
if (state_ != nullptr)
{
- std::unique_lock<std::mutex> l (state_->mutex);
+ unique_lock l (state_->mutex);
if (!state_->finished)
return nullopt;
@@ -47,13 +47,14 @@ namespace butl
//
template <typename F>
inline builtin::async_state::
- async_state (F f)
- : thread ([f = std::move (f), this] () mutable noexcept
+ async_state (uint8_t& r, F f)
+ : thread ([this, &r, f = std::move (f)] () mutable noexcept
{
- f ();
+ uint8_t t (f ());
{
- std::unique_lock<std::mutex> l (this->mutex);
+ unique_lock l (this->mutex);
+ r = t;
finished = true;
}
@@ -68,9 +69,10 @@ namespace butl
{
std::unique_ptr<builtin::async_state> s (
new builtin::async_state (
- [f = std::move (f), &r] () mutable noexcept
+ r,
+ [f = std::move (f)] () mutable noexcept -> uint8_t
{
- r = f ();
+ return f ();
}));
return builtin (r, move (s));
diff --git a/libbutl/char-scanner.mxx b/libbutl/char-scanner.hxx
index 27f692b..24865b7 100644
--- a/libbutl/char-scanner.mxx
+++ b/libbutl/char-scanner.hxx
@@ -1,36 +1,21 @@
-// file : libbutl/char-scanner.mxx -*- C++ -*-
+// file : libbutl/char-scanner.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string> // char_traits
+#include <cassert>
#include <cstddef> // size_t
#include <cstdint> // uint64_t
#include <climits> // INT_*
#include <utility> // pair, make_pair()
#include <istream>
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-export module butl.char_scanner;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#else
#include <libbutl/bufstreambuf.hxx>
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Refer to utf8_validator for details.
//
@@ -62,19 +47,21 @@ LIBBUTL_MODEXPORT namespace butl
// includes a number of optimizations that assume nobody else is messing
// with the stream.
//
- // The line and position arguments can be used to override the start line
- // and position in the stream (useful when re-scanning data saved with the
- // save_* facility).
+ // The line, column, and position arguments can be used to override the
+ // start line, column, and position in the stream (useful when re-scanning
+ // data saved with the save_* facility).
//
char_scanner (std::istream&,
bool crlf = true,
std::uint64_t line = 1,
+ std::uint64_t column = 1,
std::uint64_t position = 0);
char_scanner (std::istream&,
validator_type,
bool crlf = true,
std::uint64_t line = 1,
+ std::uint64_t column = 1,
std::uint64_t position = 0);
char_scanner (const char_scanner&) = delete;
diff --git a/libbutl/char-scanner.ixx b/libbutl/char-scanner.ixx
index 57aefc2..2dc41de 100644
--- a/libbutl/char-scanner.ixx
+++ b/libbutl/char-scanner.ixx
@@ -5,8 +5,10 @@ namespace butl
{
template <typename V, std::size_t N>
inline char_scanner<V, N>::
- char_scanner (std::istream& is, bool crlf, std::uint64_t l, std::uint64_t p)
- : char_scanner (is, validator_type (), crlf, l, p)
+ char_scanner (std::istream& is,
+ bool crlf,
+ std::uint64_t l, std::uint64_t c, std::uint64_t p)
+ : char_scanner (is, validator_type (), crlf, l, c, p)
{
}
diff --git a/libbutl/char-scanner.txx b/libbutl/char-scanner.txx
index 63389f0..75ea189 100644
--- a/libbutl/char-scanner.txx
+++ b/libbutl/char-scanner.txx
@@ -1,9 +1,7 @@
// file : libbutl/char-scanner.txx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_lib_modules_ts
#include <utility> // move
-#endif
namespace butl
{
@@ -13,9 +11,10 @@ namespace butl
validator_type v,
bool crlf,
std::uint64_t l,
+ std::uint64_t c,
std::uint64_t p)
: line (l),
- column (1),
+ column (c),
position (p),
is_ (is),
val_ (std::move (v)),
diff --git a/libbutl/command.cxx b/libbutl/command.cxx
index c23dfd5..2df52dd 100644
--- a/libbutl/command.cxx
+++ b/libbutl/command.cxx
@@ -1,48 +1,18 @@
// file : libbutl/command.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/command.mxx>
-#endif
-
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <map>
-#include <string>
-#include <cstddef>
-#include <functional>
+#include <libbutl/command.hxx>
#include <ios> // ios::failure
#include <vector>
+#include <cassert>
#include <utility> // move()
#include <stdexcept> // invalid_argument
#include <system_error>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.command;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.process;
-import butl.optional;
-#endif
-
-import butl.builtin;
-import butl.fdstream;
-import butl.string_parser;
-#else
-#include <libbutl/builtin.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/string-parser.mxx>
-#endif
+
+#include <libbutl/builtin.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/string-parser.hxx>
using namespace std;
@@ -81,7 +51,7 @@ namespace butl
//
if (p == string::npos)
throw invalid_argument (string ("unmatched substitution character '") +
- open + "'");
+ open + '\'');
if (p == sp)
throw invalid_argument ("empty substitution variable");
@@ -90,12 +60,12 @@ namespace butl
if (vn.find_first_of (" \t") != string::npos)
throw invalid_argument ("whitespace in substitution variable '" +
- vn + "'");
+ vn + '\'');
// Find the variable and append its value or fail if it's unknown.
//
if (!sc (vn, r))
- throw invalid_argument ("unknown substitution variable '" + vn + "'");
+ throw invalid_argument ("unknown substitution variable '" + vn + '\'');
}
// Append the source string tail following the last substitution.
@@ -198,7 +168,7 @@ namespace butl
catch (const invalid_path& e)
{
throw invalid_argument ("invalid stdout redirect file path '" +
- e.path + "'");
+ e.path + '\'');
}
if (redir->empty ())
diff --git a/libbutl/command.mxx b/libbutl/command.hxx
index 143d406..fb7258f 100644
--- a/libbutl/command.mxx
+++ b/libbutl/command.hxx
@@ -1,34 +1,19 @@
-// file : libbutl/command.mxx -*- C++ -*-
+// file : libbutl/command.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-#ifndef __cpp_lib_modules_ts
#include <map>
#include <string>
#include <cstddef> // size_t
#include <functional>
-#endif
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.command;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.process;
-import butl.optional;
-#else
-#include <libbutl/process.mxx>
-#include <libbutl/optional.mxx>
-#endif
+#include <libbutl/process.hxx>
+#include <libbutl/optional.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Run a process or a builtin, interpreting the command line as
// whitespace-separated, potentially quoted program path/builtin name,
diff --git a/libbutl/const-ptr.mxx b/libbutl/const-ptr.hxx
index 343ecf6..1474e17 100644
--- a/libbutl/const-ptr.mxx
+++ b/libbutl/const-ptr.hxx
@@ -1,28 +1,11 @@
-// file : libbutl/const-ptr.mxx -*- C++ -*-
+// file : libbutl/const-ptr.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <cstddef> // nullptr_t
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.const_ptr;
-#ifdef __cpp_lib_modules_ts
-import std.core; // @@ MOD std.fundamental.
-#endif
-#endif
-
-#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Const-propagating pointer.
//
diff --git a/libbutl/curl.cxx b/libbutl/curl.cxx
index ac3d0cb..5649965 100644
--- a/libbutl/curl.cxx
+++ b/libbutl/curl.cxx
@@ -1,41 +1,14 @@
// file : libbutl/curl.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/curl.mxx>
-#endif
-
-// C includes.
+#include <libbutl/curl.hxx>
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-
#include <utility> // move()
+#include <cstdlib> // strtoul(), size_t
#include <exception> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.curl;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.process;
-import butl.fdstream;
-import butl.small_vector;
-#endif
-import butl.utility; // icasecmp()
-#else
-#include <libbutl/utility.mxx>
-#endif
+#include <libbutl/utility.hxx>
using namespace std;
@@ -49,7 +22,17 @@ namespace butl
case ftp_put:
throw invalid_argument ("no input specified for PUT method");
case http_post:
- throw invalid_argument ("no input specified for POST method");
+ {
+ // Post the empty data.
+ //
+ // Note that while it's tempting to specify the --request POST option
+ // instead, that can potentially overwrite the request methods for the
+ // HTTP 30X response code redirects.
+ //
+ d.options.push_back ("--data-raw");
+ d.options.push_back ("");
+ }
+ // Fall through.
case ftp_get:
case http_get:
{
@@ -170,7 +153,7 @@ namespace butl
}
curl::method_proto curl::
- translate (method_type m, const string& u, method_proto_options& o)
+ translate (method_type m, const string& u, method_proto_options& o, flags fs)
{
size_t n (u.find ("://"));
@@ -189,8 +172,11 @@ namespace butl
}
else if (icasecmp (u, "http", n) == 0 || icasecmp (u, "https", n) == 0)
{
- o.push_back ("--fail"); // Fail on HTTP errors (e.g., 404).
- o.push_back ("--location"); // Follow redirects.
+ if ((fs & flags::no_fail) == flags::none)
+ o.push_back ("--fail"); // Fail on HTTP errors (e.g., 404).
+
+ if ((fs & flags::no_location) == flags::none)
+ o.push_back ("--location"); // Follow redirects.
switch (m)
{
@@ -203,4 +189,123 @@ namespace butl
throw invalid_argument ("unsupported protocol");
}
+
+ uint16_t curl::
+ parse_http_status_code (const string& s)
+ {
+ char* e (nullptr);
+ unsigned long c (strtoul (s.c_str (), &e, 10)); // Can't throw.
+ assert (e != nullptr);
+
+ return *e == '\0' && c >= 100 && c < 600
+ ? static_cast<uint16_t> (c)
+ : 0;
+ }
+
+ string curl::
+ read_http_response_line (ifdstream& is)
+ {
+ string r;
+ getline (is, r); // Strips the trailing LF (0xA).
+
+ // Note that on POSIX CRLF is not automatically translated into LF, so we
+ // need to strip CR (0xD) manually.
+ //
+ if (!r.empty () && r.back () == '\r')
+ r.pop_back ();
+
+ return r;
+ }
+
+ curl::http_status curl::
+ read_http_status (ifdstream& is, bool skip_headers)
+ {
+ // After getting the status line, if requested, we will read until the
+ // empty line (containing just CRLF). Not being able to reach such a line
+ // is an error, which is the reason for the exception mask choice. When
+ // done, we will restore the original exception mask.
+ //
+ ifdstream::iostate es (is.exceptions ());
+ is.exceptions (ifdstream::badbit | ifdstream::failbit | ifdstream::eofbit);
+
+ auto read_status = [&is, es] ()
+ {
+ string l (read_http_response_line (is));
+
+ for (;;) // Breakout loop.
+ {
+ if (l.compare (0, 5, "HTTP/") != 0)
+ break;
+
+ size_t p (l.find (' ', 5)); // The protocol end.
+ if (p == string::npos)
+ break;
+
+ p = l.find_first_not_of (' ', p + 1); // The code start.
+ if (p == string::npos)
+ break;
+
+ size_t e (l.find (' ', p + 1)); // The code end.
+ if (e == string::npos)
+ break;
+
+ uint16_t c (parse_http_status_code (string (l, p, e - p)));
+ if (c == 0)
+ break;
+
+ string r;
+ p = l.find_first_not_of (' ', e + 1); // The reason start.
+ if (p != string::npos)
+ {
+ e = l.find_last_not_of (' '); // The reason end.
+ assert (e != string::npos && e >= p);
+
+ r = string (l, p, e - p + 1);
+ }
+
+ return http_status {c, move (r)};
+ }
+
+ is.exceptions (es); // Restore the exception mask.
+
+ throw invalid_argument ("invalid status line '" + l + "'");
+ };
+
+ // The curl output for a successfull request looks like this:
+ //
+ // HTTP/1.1 100 Continue
+ //
+ // HTTP/1.1 200 OK
+ // Content-Length: 83
+ // Content-Type: text/manifest;charset=utf-8
+ //
+ // <response-body>
+ //
+ // curl normally sends the 'Expect: 100-continue' header for uploads, so
+ // we need to handle the interim HTTP server response with the continue
+ // (100) status code.
+ //
+ // Interestingly, Apache can respond with the continue (100) code and with
+ // the not found (404) code afterwords.
+ //
+ http_status rs (read_status ());
+
+ if (rs.code == 100)
+ {
+ // Skips the interim response.
+ //
+ while (!read_http_response_line (is).empty ()) ;
+
+ rs = read_status (); // Reads the final status code.
+ }
+
+ if (skip_headers)
+ {
+ while (!read_http_response_line (is).empty ()) ; // Skips headers.
+ }
+
+ is.exceptions (es);
+
+ return rs;
+ }
}
diff --git a/libbutl/curl.mxx b/libbutl/curl.hxx
index 03aac99..ea91807 100644
--- a/libbutl/curl.mxx
+++ b/libbutl/curl.hxx
@@ -1,42 +1,20 @@
-// file : libbutl/curl.mxx -*- C++ -*-
+// file : libbutl/curl.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
+#include <cstdint> // uint16_t
#include <type_traits>
-#include <cstddef> // size_t
-#include <utility> // forward()
-#include <exception> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.curl;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.process; //@@ MOD TODO: should we re-export?
-import butl.fdstream;
-import butl.small_vector;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/process.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/small-vector.mxx>
-#endif
+#include <libbutl/path.hxx>
+#include <libbutl/process.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/small-vector.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Perform a method (GET, POST, PUT) on a URL using the curl(1) program.
// Throw process_error and io_error (both derive from system_error) in case
@@ -113,6 +91,19 @@ LIBBUTL_MODEXPORT namespace butl
public:
enum method_type {get, put, post};
+ // By default the -sS and, for the HTTP protocol, --fail and --location
+ // options are passed to curl on the command line. Optionally, these
+ // options can be suppressed.
+ //
+ enum class flags: std::uint16_t
+ {
+ no_fail = 0x01, // Don't pass --fail.
+ no_location = 0x02, // Don't pass --location
+ no_sS = 0x04, // Don't pass -sS
+
+ none = 0 // Default options set.
+ };
+
ifdstream in;
ofdstream out;
@@ -143,12 +134,77 @@ LIBBUTL_MODEXPORT namespace butl
const std::string& url,
A&&... options);
+ // Similar to the above, but allows to adjust the curl's default command
+ // line.
+ //
+ template <typename I,
+ typename O,
+ typename E,
+ typename... A>
+ curl (I&& in,
+ O&& out,
+ E&& err,
+ method_type,
+ flags,
+ const std::string& url,
+ A&&... options);
+
+ template <typename C,
+ typename I,
+ typename O,
+ typename E,
+ typename... A>
+ curl (const C&,
+ I&& in,
+ O&& out,
+ E&& err,
+ method_type,
+ flags,
+ const std::string& url,
+ A&&... options);
+
+ // Read the HTTP response status from an input stream.
+ //
+ // Specifically, read and parse the HTTP status line, by default skip over
+ // the remaining headers (leaving the stream at the beginning of the
+ // response body), and return the status code and the reason phrase. Throw
+ // std::invalid_argument if the status line could not be parsed. Pass
+ // through the ios::failure exception on the stream error.
+ //
+ // Note that if ios::failure is thrown the stream's exception mask may not
+ // be preserved.
+ //
+ struct http_status
+ {
+ std::uint16_t code;
+ std::string reason;
+ };
+
+ static http_status
+ read_http_status (ifdstream&, bool skip_headers = true);
+
+ // Parse and return the HTTP status code. Return 0 if the argument is
+ // invalid.
+ //
+ static std::uint16_t
+ parse_http_status_code (const std::string&);
+
+ // Read the CRLF-terminated line from an input stream, stripping the
+ // trailing CRLF. Pass through the ios::failure exception on the stream
+ // error.
+ //
+ static std::string
+ read_http_response_line (ifdstream&);
+
private:
enum method_proto {ftp_get, ftp_put, http_get, http_post};
using method_proto_options = small_vector<const char*, 2>;
method_proto
- translate (method_type, const std::string& url, method_proto_options&);
+ translate (method_type,
+ const std::string& url,
+ method_proto_options&,
+ flags);
private:
template <typename T>
@@ -188,6 +244,11 @@ LIBBUTL_MODEXPORT namespace butl
typename std::enable_if<is_other<O>::value, O>::type
map_out (O&&, method_proto, io_data&);
};
+
+ curl::flags operator& (curl::flags, curl::flags);
+ curl::flags operator| (curl::flags, curl::flags);
+ curl::flags operator&= (curl::flags&, curl::flags);
+ curl::flags operator|= (curl::flags&, curl::flags);
}
#include <libbutl/curl.ixx>
diff --git a/libbutl/curl.ixx b/libbutl/curl.ixx
index 61a4ff5..6dcfe13 100644
--- a/libbutl/curl.ixx
+++ b/libbutl/curl.ixx
@@ -1,7 +1,11 @@
// file : libbutl/curl.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+#include <cstddef> // size_t
+#include <utility> // forward()
+#include <exception> // invalid_argument
+
+namespace butl
{
template <typename I,
typename O,
@@ -12,6 +16,7 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
O&& out,
E&& err,
method_type m,
+ flags fs,
const std::string& url,
A&&... options)
: curl ([] (const char* [], std::size_t) {},
@@ -19,8 +24,80 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
std::forward<O> (out),
std::forward<E> (err),
m,
+ fs,
+ url,
+ std::forward<A> (options)...)
+ {
+ }
+
+ template <typename C,
+ typename I,
+ typename O,
+ typename E,
+ typename... A>
+ inline curl::
+ curl (const C& cmdc,
+ I&& in,
+ O&& out,
+ E&& err,
+ method_type m,
+ const std::string& url,
+ A&&... options)
+ : curl (cmdc,
+ std::forward<I> (in),
+ std::forward<O> (out),
+ std::forward<E> (err),
+ m,
+ flags::none,
+ url,
+ std::forward<A> (options)...)
+ {
+ }
+
+ template <typename I,
+ typename O,
+ typename E,
+ typename... A>
+ inline curl::
+ curl (I&& in,
+ O&& out,
+ E&& err,
+ method_type m,
+ const std::string& url,
+ A&&... options)
+ : curl (std::forward<I> (in),
+ std::forward<O> (out),
+ std::forward<E> (err),
+ m,
+ flags::none,
url,
std::forward<A> (options)...)
{
}
+
+ inline curl::flags
+ operator&= (curl::flags& x, curl::flags y)
+ {
+ return x = static_cast<curl::flags> (static_cast<std::uint16_t> (x) &
+ static_cast<std::uint16_t> (y));
+ }
+
+ inline curl::flags
+ operator|= (curl::flags& x, curl::flags y)
+ {
+ return x = static_cast<curl::flags> (static_cast<std::uint16_t> (x) |
+ static_cast<std::uint16_t> (y));
+ }
+
+ inline curl::flags
+ operator& (curl::flags x, curl::flags y)
+ {
+ return x &= y;
+ }
+
+ inline curl::flags
+ operator| (curl::flags x, curl::flags y)
+ {
+ return x |= y;
+ }
}
diff --git a/libbutl/curl.txx b/libbutl/curl.txx
index 0c07d35..fc74470 100644
--- a/libbutl/curl.txx
+++ b/libbutl/curl.txx
@@ -1,7 +1,7 @@
// file : libbutl/curl.txx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+namespace butl
{
template <typename I>
typename std::enable_if<curl::is_other<I>::value, I>::type curl::
@@ -65,11 +65,12 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
O&& out,
E&& err,
method_type m,
+ flags fs,
const std::string& url,
A&&... options)
{
method_proto_options mpo;
- method_proto mp (translate (m, url, mpo));
+ method_proto mp (translate (m, url, mpo, fs));
io_data in_data;
io_data out_data;
@@ -81,8 +82,9 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
map_out (std::forward<O> (out), mp, out_data),
std::forward<E> (err),
"curl",
- "-s", // Silent.
- "-S", // But do show diagnostics.
+ ((fs & flags::no_sS) == flags::none
+ ? "-sS" // Silent but do show diagnostics.
+ : nullptr),
mpo,
in_data.options,
out_data.options,
diff --git a/libbutl/default-options.mxx b/libbutl/default-options.hxx
index 1694d48..1d363b6 100644
--- a/libbutl/default-options.mxx
+++ b/libbutl/default-options.hxx
@@ -1,45 +1,18 @@
-// file : libbutl/default-options.mxx -*- C++ -*-
+// file : libbutl/default-options.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
-#include <utility> // move(), forward(), make_pair()
-#include <algorithm> // reverse()
-#include <stdexcept> // invalid_argument
-#include <system_error>
-#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/path.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/small-vector.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Default options files helper implementation.
//
diff --git a/libbutl/default-options.ixx b/libbutl/default-options.ixx
index 4a551ac..7248d7d 100644
--- a/libbutl/default-options.ixx
+++ b/libbutl/default-options.ixx
@@ -1,7 +1,7 @@
// file : libbutl/default-options.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+namespace butl
{
template <typename O>
inline O
diff --git a/libbutl/default-options.txx b/libbutl/default-options.txx
index 0c2501c..aa254b2 100644
--- a/libbutl/default-options.txx
+++ b/libbutl/default-options.txx
@@ -1,7 +1,15 @@
// file : libbutl/default-options.txx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+#include <utility> // move(), forward(), make_pair()
+#include <algorithm> // reverse()
+#include <stdexcept> // invalid_argument
+#include <system_error>
+
+#include <libbutl/git.hxx>
+#include <libbutl/filesystem.hxx>
+
+namespace butl
{
inline bool
options_dir_exists (const dir_path& d)
diff --git a/libbutl/diagnostics.cxx b/libbutl/diagnostics.cxx
index b038e5d..6ac8192 100644
--- a/libbutl/diagnostics.cxx
+++ b/libbutl/diagnostics.cxx
@@ -1,9 +1,7 @@
// file : libbutl/diagnostics.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/diagnostics.mxx>
-#endif
+#include <libbutl/diagnostics.hxx>
#ifndef _WIN32
# include <unistd.h> // write()
@@ -12,49 +10,36 @@
# include <io.h> //_write()
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <utility>
-#include <exception>
-
#include <ios> // ios::failure
#include <mutex>
#include <string>
+#include <cassert>
#include <cstddef> // size_t
#include <iostream> // cerr
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-module butl.diagnostics;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
-
-import std.threading;
-import butl.utility;
-import butl.optional;
-import butl.fdstream; // stderr_fd(), fdterm()
+#ifndef LIBBUTL_MINGW_STDTHREAD
+# include <mutex>
#else
-#include <libbutl/utility.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/fdstream.mxx>
+# include <libbutl/mingw-mutex.hxx>
#endif
+#include <libbutl/ft/lang.hxx> // thread_local
+
+#include <libbutl/utility.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/fdstream.hxx>
+
using namespace std;
namespace butl
{
ostream* diag_stream = &cerr;
- static mutex diag_mutex;
+#ifndef LIBBUTL_MINGW_STDTHREAD
+ static std::mutex diag_mutex;
+#else
+ static mingw_stdthread::mutex diag_mutex;
+#endif
string diag_progress;
static string diag_progress_blank; // Being printed blanks out the line.
@@ -158,28 +143,28 @@ namespace butl
default_writer (const diag_record& r)
{
r.os.put ('\n');
- diag_stream_lock () << r.os.str ();
+
+ diag_stream_lock l;
+ (*diag_stream) << r.os.str ();
// We can endup flushing the result of several writes. The last one may
// possibly be incomplete, but that's not a problem as it will also be
// followed by the flush() call.
//
- // @@ Strange: why not just hold the lock for both write and flush?
- //
diag_stream->flush ();
}
- void (*diag_record::writer) (const diag_record&) = &default_writer;
+ diag_writer* diag_record::writer = &default_writer;
void diag_record::
- flush () const
+ flush (void (*w) (const diag_record&)) const
{
if (!empty_)
{
if (epilogue_ == nullptr)
{
- if (writer != nullptr)
- writer (*this);
+ if (w != nullptr || (w = writer) != nullptr)
+ w (*this);
empty_ = true;
}
@@ -189,8 +174,8 @@ namespace butl
//
auto e (epilogue_);
epilogue_ = nullptr;
- e (*this); // Can throw.
- flush (); // Call ourselves to write the data in case it returns.
+ e (*this, w); // Can throw.
+ flush (w); // Call ourselves to write the data in case it returns.
}
}
}
@@ -213,4 +198,28 @@ namespace butl
flush ();
#endif
}
+
+ // Diagnostics stack.
+ //
+ static
+#ifdef __cpp_thread_local
+ thread_local
+#else
+ __thread
+#endif
+ const diag_frame* diag_frame_stack = nullptr;
+
+ const diag_frame* diag_frame::
+ stack () noexcept
+ {
+ return diag_frame_stack;
+ }
+
+ const diag_frame* diag_frame::
+ stack (const diag_frame* f) noexcept
+ {
+ const diag_frame* r (diag_frame_stack);
+ diag_frame_stack = f;
+ return r;
+ }
}
diff --git a/libbutl/diagnostics.mxx b/libbutl/diagnostics.hxx
index d41ba74..c6db34b 100644
--- a/libbutl/diagnostics.mxx
+++ b/libbutl/diagnostics.hxx
@@ -1,32 +1,19 @@
-// file : libbutl/diagnostics.mxx -*- C++ -*-
+// file : libbutl/diagnostics.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <ostream>
#include <sstream>
#include <utility> // move(), forward()
#include <exception> // uncaught_exception[s]()
-#endif
#include <libbutl/ft/exception.hxx> // uncaught_exceptions
-#ifdef __cpp_modules_ts
-export module butl.diagnostics;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
-
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Diagnostic facility base infrastructure.
//
@@ -40,8 +27,11 @@ LIBBUTL_MODEXPORT namespace butl
LIBBUTL_SYMEXPORT extern std::ostream* diag_stream;
// Acquire the diagnostics exclusive access mutex in ctor, release in dtor.
- // An object of the type must be created prior to writing to diag_stream (see
- // above).
+ // An object of the type must be created prior to writing to diag_stream
+ // (see above).
+ //
+ // Note that this class also manages the interaction with the progress
+ // printing (see below).
//
struct LIBBUTL_SYMEXPORT diag_stream_lock
{
@@ -87,13 +77,26 @@ LIBBUTL_MODEXPORT namespace butl
~diag_progress_lock ();
};
+ // Diagnostic record and marks (error, warn, etc).
//
+ // There are two ways to use this facility in a project: simple, where we
+ // just alias the types in our namespace, and complex, where instead we
+ // derive from them and "override" (hide, really) operator<< (and a few
+ // other functions) in order to make ADL look in our namespace rather than
+ // butl. In the simple case we may have to resort to defining some
+ // operator<< overloads in namespace std in order to satisfy ADL. This is
+ // usually not an acceptable approach for libraries, which is where the
+ // complex case comes in (see libbuild2 for a "canonical" example of the
+ // complex case). Note also that it doesn't seem worth templatazing epilogue
+ // so the complex case may also need to do a few casts but those should be
+ // limited to the diagnostics infrastructure.
//
struct diag_record;
template <typename> struct diag_prologue;
template <typename> struct diag_mark;
- using diag_epilogue = void (const diag_record&);
+ using diag_writer = void (const diag_record&);
+ using diag_epilogue = void (const diag_record&, diag_writer*);
struct LIBBUTL_SYMEXPORT diag_record
{
@@ -130,7 +133,7 @@ LIBBUTL_MODEXPORT namespace butl
full () const {return !empty_;}
void
- flush () const;
+ flush (diag_writer* = nullptr) const;
void
append (const char* indent, diag_epilogue* e) const
@@ -163,7 +166,7 @@ LIBBUTL_MODEXPORT namespace butl
#endif
empty_ (r.empty_),
epilogue_ (r.epilogue_),
- os (std::move (r.os))
+ os (std::move (r.os)) // Note: can throw.
{
if (!empty_)
{
@@ -181,7 +184,7 @@ LIBBUTL_MODEXPORT namespace butl
// Diagnostics writer. The default implementation writes the record text
// to diag_stream. If it is NULL, then the record text is ignored.
//
- static void (*writer) (const diag_record&);
+ static diag_writer* writer;
protected:
#ifdef __cpp_lib_uncaught_exceptions
@@ -276,4 +279,97 @@ LIBBUTL_MODEXPORT namespace butl
e.B::operator() (r);
}
};
+
+ // Diagnostics stack. Each frame is "applied" to the diag record.
+ //
+ // Unfortunately most of our use-cases don't fit into the 2-pointer small
+ // object optimization of std::function. So we have to complicate things
+ // a bit here.
+ //
+ struct LIBBUTL_SYMEXPORT diag_frame
+ {
+ explicit
+ diag_frame (void (*f) (const diag_frame&, const diag_record&))
+ : func_ (f)
+ {
+ if (func_ != nullptr)
+ prev_ = stack (this);
+ }
+
+ diag_frame (diag_frame&& x)
+ : func_ (x.func_)
+ {
+ if (func_ != nullptr)
+ {
+ prev_ = x.prev_;
+ stack (this);
+
+ x.func_ = nullptr;
+ }
+ }
+
+ diag_frame& operator= (diag_frame&&) = delete;
+
+ diag_frame (const diag_frame&) = delete;
+ diag_frame& operator= (const diag_frame&) = delete;
+
+ ~diag_frame ()
+ {
+ if (func_ != nullptr )
+ stack (prev_);
+ }
+
+ // Normally passed as an epilogue. Writer is not used.
+ //
+ static void
+ apply (const diag_record& r, diag_writer* = nullptr)
+ {
+ for (const diag_frame* f (stack ()); f != nullptr; f = f->prev_)
+ f->func_ (*f, r);
+ }
+
+ // Tip of the stack.
+ //
+ static const diag_frame*
+ stack () noexcept;
+
+ // Set the new and return the previous tip of the stack.
+ //
+ static const diag_frame*
+ stack (const diag_frame*) noexcept;
+
+ struct stack_guard
+ {
+ explicit stack_guard (const diag_frame* s): s_ (stack (s)) {}
+ ~stack_guard () {stack (s_);}
+ const diag_frame* s_;
+ };
+
+ private:
+ void (*func_) (const diag_frame&, const diag_record&);
+ const diag_frame* prev_;
+ };
+
+ template <typename F>
+ struct diag_frame_impl: diag_frame
+ {
+ explicit
+ diag_frame_impl (F f): diag_frame (&thunk), func_ (move (f)) {}
+
+ private:
+ static void
+ thunk (const diag_frame& f, const diag_record& r)
+ {
+ static_cast<const diag_frame_impl&> (f).func_ (r);
+ }
+
+ const F func_;
+ };
+
+ template <typename F>
+ inline diag_frame_impl<F>
+ make_diag_frame (F f)
+ {
+ return diag_frame_impl<F> (move (f));
+ }
}
diff --git a/libbutl/export.hxx b/libbutl/export.hxx
index 3353ca8..dc04f85 100644
--- a/libbutl/export.hxx
+++ b/libbutl/export.hxx
@@ -3,14 +3,6 @@
#pragma once
-// If modules are available, setup the module export.
-//
-#ifdef __cpp_modules_ts
-# define LIBBUTL_MODEXPORT export
-#else
-# define LIBBUTL_MODEXPORT
-#endif
-
// Normally we don't export class templates (but do complete specializations),
// inline functions, and classes with only inline member functions. Exporting
// classes that inherit from non-exported/imported bases (e.g., std::string)
diff --git a/libbutl/fdstream.cxx b/libbutl/fdstream.cxx
index eb0ec7b..07cb9f2 100644
--- a/libbutl/fdstream.cxx
+++ b/libbutl/fdstream.cxx
@@ -1,9 +1,7 @@
// file : libbutl/fdstream.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/fdstream.mxx>
-#endif
+#include <libbutl/fdstream.hxx>
#include <errno.h> // errno, E*
@@ -19,6 +17,10 @@
#else
# include <libbutl/win32-utility.hxx>
+# ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
+# define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x04
+# endif
+
# include <io.h> // _close(), _read(), _write(), _setmode(), _sopen(),
// _lseek(), _dup(), _pipe(), _chsize_s,
// _get_osfhandle()
@@ -36,55 +38,26 @@
# include <wchar.h> // wcsncmp(), wcsstr()
+# include <thread> // this_thread::yield()
# include <algorithm> // count()
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <vector>
-#include <string>
-#include <chrono>
-#include <istream>
-#include <ostream>
-#include <utility>
-#include <cstdint>
-#include <cstddef>
-
#include <ios> // ios_base::openmode, ios_base::failure
#include <new> // bad_alloc
#include <limits> // numeric_limits
-#include <cstring> // memcpy(), memmove()
+#include <cassert>
+#include <cstring> // memcpy(), memmove(), memchr(), strcmp()
+#include <cstdlib> // getenv()
#include <iostream> // cin, cout
#include <exception> // uncaught_exception[s]()
#include <stdexcept> // invalid_argument
#include <system_error>
-#endif
-#include <libbutl/ft/exception.hxx> // uncaught_exceptions
+#include <libbutl/ft/exception.hxx> // uncaught_exceptions
#include <libbutl/process-details.hxx>
-#ifdef __cpp_modules_ts
-module butl.fdstream;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-import std.threading; // Clang wants it in purview (see process-details.hxx).
-#endif
-import butl.path;
-import butl.filesystem;
-import butl.small_vector;
-#endif
-
-import butl.utility; // throw_*_ios_failure(), function_cast()
-import butl.timestamp;
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/timestamp.mxx>
-#endif
+#include <libbutl/utility.hxx> // throw_*_ios_failure(), function_cast()
+#include <libbutl/timestamp.hxx>
using namespace std;
@@ -386,14 +359,6 @@ namespace butl
return save () ? 0 : -1;
}
-#ifdef _WIN32
- static inline int
- write (int fd, const void* buf, size_t n)
- {
- return _write (fd, buf, static_cast<unsigned int> (n));
- }
-#endif
-
bool fdstreambuf::
save ()
{
@@ -405,7 +370,7 @@ namespace butl
// descriptor opened for read-only access (while -1 with errno EBADF is
// expected). This is in contrast with VC's _write() and POSIX's write().
//
- auto m (write (fd_.get (), buf_, n));
+ auto m (fdwrite (fd_.get (), buf_, n));
if (m == -1)
throw_generic_ios_failure (errno);
@@ -520,7 +485,7 @@ namespace butl
// Flush the buffer.
//
size_t wn (bn + an);
- int r (wn > 0 ? write (fd_.get (), buf_, wn) : 0);
+ streamsize r (wn > 0 ? fdwrite (fd_.get (), buf_, wn) : 0);
if (r == -1)
throw_generic_ios_failure (errno);
@@ -563,7 +528,7 @@ namespace butl
// The data tail doesn't fit the buffer so write it to the file.
//
- r = write (fd_.get (), s, n);
+ r = fdwrite (fd_.get (), s, n);
if (r == -1)
throw_generic_ios_failure (errno);
@@ -879,7 +844,7 @@ namespace butl
}
ifdstream&
- getline (ifdstream& is, string& s, char delim)
+ getline (ifdstream& is, string& l, char delim)
{
ifdstream::iostate eb (is.exceptions ());
assert (eb & ifdstream::badbit);
@@ -896,7 +861,7 @@ namespace butl
if (eb != ifdstream::badbit)
is.exceptions (ifdstream::badbit);
- std::getline (is, s, delim);
+ std::getline (is, l, delim);
// Throw if any of the newly set bits are present in the exception mask.
//
@@ -909,6 +874,58 @@ namespace butl
return is;
}
+ bool
+ getline_non_blocking (ifdstream& is, string& l, char delim)
+ {
+ assert (!is.blocking () && (is.exceptions () & ifdstream::badbit) != 0);
+
+ fdstreambuf& sb (*static_cast<fdstreambuf*> (is.rdbuf ()));
+
+ // Read until blocked (0), EOF (-1) or encounter the delimiter.
+ //
+ // Note that here we reasonably assume that any failure in in_avail()
+ // will lead to badbit and thus an exception (see showmanyc()).
+ //
+ streamsize s;
+ while ((s = sb.in_avail ()) > 0)
+ {
+ const char* p (sb.gptr ());
+ size_t n (sb.egptr () - p);
+
+ const char* e (static_cast<const char*> (memchr (p, delim, n)));
+ if (e != nullptr)
+ n = e - p;
+
+ l.append (p, n);
+
+ // Note: consume the delimiter if found.
+ //
+ sb.gbump (static_cast<int> (n + (e != nullptr ? 1 : 0)));
+
+ if (e != nullptr)
+ break;
+ }
+
+ // Here s can be:
+ //
+ // -1 -- EOF.
+ // 0 -- blocked before encountering delimiter/EOF.
+ // >0 -- encountered the delimiter.
+ //
+ if (s == -1)
+ {
+ is.setstate (ifdstream::eofbit);
+
+ // If we couldn't extract anything, not even the delimiter, then this is
+ // a failure per the getline() interface.
+ //
+ if (l.empty ())
+ is.setstate (ifdstream::failbit);
+ }
+
+ return s != 0;
+ }
+
// ofdstream
//
ofdstream::
@@ -1058,10 +1075,11 @@ namespace butl
#endif
// Unlike other platforms, *BSD allows opening a directory as a file which
- // will cause all kinds of problems upstream (e.g., cpfile()). So we detect
- // and diagnose this.
+ // will cause all kinds of problems upstream (e.g., cpfile()). So we
+ // detect and diagnose this. Note: not certain this is the case for NetBSD
+ // and OpenBSD.
//
-#if defined(__FreeBSD__) || defined(__NetBSD__)
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
{
struct stat s;
if (stat (f, &s) == 0 && S_ISDIR (s.st_mode))
@@ -1147,12 +1165,17 @@ namespace butl
// underlying CreateFile() function call (see mventry() for details). If
// that's the case, we will keep trying to open the file for two seconds.
//
- for (size_t i (0); i < 21; ++i)
+ // Also, it turns out, if someone memory-maps a file, it takes Windows
+ // some time to realize it's been unmapped and until then any attempt to
+ // open it results in EINVAL POSIX error, ERROR_USER_MAPPED_FILE system
+ // error. So we retry those as well.
+ //
+ for (size_t i (0); i < 41; ++i)
{
- // Sleep 100 milliseconds before the open retry.
+ // Sleep 50 milliseconds before the open retry.
//
if (i != 0)
- Sleep (100);
+ Sleep (50);
fd = pass_perm
? _sopen (f, of, _SH_DENYNO, pf)
@@ -1166,10 +1189,11 @@ namespace butl
// Note that MinGW's _sopen() is just a stub forwarding the call to the
// (publicly available) MSVCRT's implementation.
//
- if (!(fd == -1 &&
- out &&
- errno == EACCES &&
- GetLastError () == ERROR_SHARING_VIOLATION))
+ if (!(fd == -1 &&
+ out &&
+ (errno == EACCES || errno == EINVAL) &&
+ (GetLastError () == ERROR_SHARING_VIOLATION ||
+ GetLastError () == ERROR_USER_MAPPED_FILE)))
break;
}
@@ -1420,6 +1444,16 @@ namespace butl
throw_generic_ios_failure (errno);
}
+ bool
+ fdterm_color (int, bool)
+ {
+ const char* t (std::getenv ("TERM"));
+
+ // This test was lifted from GCC (Emacs shell sets TERM=dumb).
+ //
+ return t != nullptr && strcmp (t, "dumb") != 0;
+ }
+
static pair<size_t, size_t>
fdselect (fdselect_set& read,
fdselect_set& write,
@@ -1438,6 +1472,8 @@ namespace butl
for (fdselect_state& s: from)
{
+ s.ready = false;
+
if (s.fd == nullfd)
continue;
@@ -1445,7 +1481,6 @@ namespace butl
throw invalid_argument ("invalid file descriptor");
FD_SET (s.fd, &to);
- s.ready = false;
if (max_fd < s.fd)
max_fd = s.fd;
@@ -1552,6 +1587,12 @@ namespace butl
return read (fd, buf, n);
}
+ streamsize
+ fdwrite (int fd, const void* buf, size_t n)
+ {
+ return write (fd, buf, n);
+ }
+
#else
auto_fd
@@ -1832,6 +1873,9 @@ namespace butl
bool
fdterm (int fd)
{
+ // @@ Both GCC and Clang simply call GetConsoleMode() for this check. I
+ // wonder why we don't do the same? See also fdterm_color() below.
+
// We don't need to close it (see fd_to_handle()).
//
HANDLE h (fd_to_handle (fd));
@@ -1917,6 +1961,42 @@ namespace butl
return false;
}
+ bool
+ fdterm_color (int fd, bool enable)
+ {
+ // We don't need to close it (see fd_to_handle()).
+ //
+ HANDLE h (fd_to_handle (fd));
+
+ // See GH issue #312 for background on this logic.
+ //
+ DWORD m;
+ if (!GetConsoleMode (h, &m))
+ throw_system_ios_failure (GetLastError ());
+
+ // Some terminals (e.g. Windows Terminal) enable VT processing by default.
+ //
+ if ((m & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0)
+ return true;
+
+ if (enable)
+ {
+ // If SetConsoleMode() fails, assume VT processing is unsupported (it
+ // is only supported from a certain build of Windows 10).
+ //
+ // Note that Wine pretends to support this but doesn't handle the escape
+ // sequences. See https://bugs.winehq.org/show_bug.cgi?id=49780.
+ //
+ if (SetConsoleMode (h,
+ (m |
+ ENABLE_PROCESSED_OUTPUT |
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING)))
+ return true;
+ }
+
+ return false;
+ }
+
static pair<size_t, size_t>
fdselect (fdselect_set& read,
fdselect_set& write,
@@ -1933,13 +2013,14 @@ namespace butl
for (fdselect_state& s: read)
{
+ s.ready = false;
+
if (s.fd == nullfd)
continue;
if (s.fd < 0)
throw invalid_argument ("invalid file descriptor");
- s.ready = false;
++n;
}
@@ -1960,7 +2041,7 @@ namespace butl
//
size_t r (0);
- while (true)
+ for (size_t i (0);; ++i)
{
for (fdselect_state& s: read)
{
@@ -2033,7 +2114,11 @@ namespace butl
if (r != 0)
break;
- DWORD t (50);
+ // Use exponential backoff but not too aggressive and with 25ms max.
+ //
+ DWORD t (
+ static_cast<DWORD> (i <= 1000 ? 0 :
+ i >= 1000 + 100 ? 25 : 1 + ((i - 1000) / 4)));
if (timeout)
{
@@ -2050,7 +2135,10 @@ namespace butl
break;
}
- Sleep (t);
+ if (t == 0)
+ this_thread::yield ();
+ else
+ Sleep (t);
}
return make_pair (r, 0);
@@ -2093,6 +2181,12 @@ namespace butl
return r;
}
+ streamsize
+ fdwrite (int fd, const void* buf, size_t n)
+ {
+ return _write (fd, buf, static_cast<unsigned int> (n));
+ }
+
#endif
pair<size_t, size_t>
diff --git a/libbutl/fdstream.mxx b/libbutl/fdstream.hxx
index 0d3fd86..9c8f786 100644
--- a/libbutl/fdstream.mxx
+++ b/libbutl/fdstream.hxx
@@ -1,13 +1,8 @@
-// file : libbutl/fdstream.mxx -*- C++ -*-
+// file : libbutl/fdstream.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-
-#include <cassert>
-#ifndef __cpp_lib_modules_ts
#include <ios> // streamsize
#include <vector>
#include <string>
@@ -18,30 +13,14 @@
#include <cstdint> // uint16_t, uint64_t
#include <cstddef> // size_t
-#include <iterator>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.fdstream;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.filesystem; // permissions, entry_stat
-import butl.small_vector;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/filesystem.mxx>
-#include <libbutl/small-vector.mxx>
+#include <libbutl/path.hxx>
+#include <libbutl/filesystem.hxx> // permissions, entry_stat
+#include <libbutl/small-vector.hxx>
#include <libbutl/bufstreambuf.hxx>
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// RAII type for file descriptors. Note that failure to close the descriptor
// is silently ignored by both the destructor and reset().
@@ -55,9 +34,6 @@ LIBBUTL_MODEXPORT namespace butl
constexpr operator int () const {return -1;}
};
-#if defined(__cpp_modules_ts) && defined(__clang__) //@@ MOD Clang duplicate sym.
- inline
-#endif
constexpr nullfd_t nullfd (-1);
class LIBBUTL_SYMEXPORT auto_fd
@@ -161,6 +137,11 @@ LIBBUTL_MODEXPORT namespace butl
class LIBBUTL_SYMEXPORT fdstreambuf: public bufstreambuf
{
public:
+ // Reasonable (for stack allocation) buffer size that provides decent
+ // performance.
+ //
+ static const std::size_t buffer_size = 8192;
+
fdstreambuf () = default;
// Unless specified, the current read/write position is assumed to
@@ -198,6 +179,9 @@ LIBBUTL_MODEXPORT namespace butl
bool
blocking (bool);
+ bool
+ blocking () const {return !non_blocking_;}
+
public:
using base = bufstreambuf;
@@ -262,7 +246,7 @@ LIBBUTL_MODEXPORT namespace butl
private:
auto_fd fd_;
- char buf_[8192];
+ char buf_[buffer_size];
bool non_blocking_ = false;
};
@@ -295,7 +279,9 @@ LIBBUTL_MODEXPORT namespace butl
binary = 0x02,
skip = 0x04,
blocking = 0x08,
- non_blocking = 0x10
+ non_blocking = 0x10,
+
+ none = 0
};
inline fdstream_mode operator& (fdstream_mode, fdstream_mode);
@@ -335,6 +321,9 @@ LIBBUTL_MODEXPORT namespace butl
int
fd () const {return buf_.fd ();}
+ bool
+ blocking () const {return buf_.blocking ();}
+
protected:
fdstreambuf buf_;
};
@@ -665,6 +654,54 @@ LIBBUTL_MODEXPORT namespace butl
LIBBUTL_SYMEXPORT ifdstream&
getline (ifdstream&, std::string&, char delim = '\n');
+ // The non-blocking getline() version that reads the line in potentially
+ // multiple calls. Key differences compared to getline():
+ //
+ // - Stream must be in the non-blocking mode and exception mask must have
+ // at least badbit.
+ //
+ // - Return type is bool instead of stream. Return true if the line has been
+ // read or false if it should be called again once the stream has more
+ // data to read. Also return true on failure.
+ //
+ // - The string must be empty on the first call.
+ //
+ // - There could still be data to read in the stream's buffer (as opposed to
+ // file descriptor) after this function returns true and you should be
+ // careful not to block on fdselect() in this case. In fact, the
+ // recommended pattern is to call this function first and only call
+ // fdselect() if it returns false.
+ //
+ // The typical usage in combination with the eof() helper:
+ //
+ // fdselect_set fds {is.fd (), ...};
+ // fdselect_state& ist (fds[0]);
+ // fdselect_state& ...;
+ //
+ // for (string l; ist.fd != nullfd || ...; )
+ // {
+ // if (ist.fd != nullfd && getline_non_blocking (is, l))
+ // {
+ // if (eof (is))
+ // ist.fd = nullfd;
+ // else
+ // {
+ // // Consume line.
+ //
+ // l.clear ();
+ // }
+ //
+ // continue;
+ // }
+ //
+ // ifdselect (fds);
+ //
+ // // Handle other ready fds.
+ // }
+ //
+ LIBBUTL_SYMEXPORT bool
+ getline_non_blocking (ifdstream&, std::string&, char delim = '\n');
+
// Open a file returning an auto_fd that holds its file descriptor on
// success and throwing ios::failure otherwise.
//
@@ -864,6 +901,14 @@ LIBBUTL_MODEXPORT namespace butl
LIBBUTL_SYMEXPORT bool
fdterm (int);
+ // Test whether a terminal file descriptor supports ANSI color output. If
+ // the enable argument is true, then also try to enable color output (only
+ // applicable on some platforms, such as Windows). Throw ios::failure on the
+ // underlying OS error.
+ //
+ LIBBUTL_SYMEXPORT bool
+ fdterm_color (int, bool enable);
+
// Wait until one or more file descriptors becomes ready for input (reading)
// or output (writing). Return the pair of numbers of descriptors that are
// ready. Throw std::invalid_argument if anything is wrong with arguments
@@ -871,7 +916,7 @@ LIBBUTL_MODEXPORT namespace butl
// underlying OS error.
//
// Note that the function clears all the previously-ready entries on each
- // call. Entries with nullfd are ignored.
+ // call. Entries with nullfd are ignored (but cleared).
//
// On Windows only pipes and only their input (read) ends are supported.
//
@@ -879,11 +924,13 @@ LIBBUTL_MODEXPORT namespace butl
{
int fd;
bool ready;
+ void* data; // Arbitrary data which can be associated with the descriptor.
// Note: intentionally non-explicit to allow implicit initialization when
// pushing to fdselect_set.
//
- fdselect_state (int fd): fd (fd), ready (false) {}
+ fdselect_state (int fd, void* d = nullptr)
+ : fd (fd), ready (false), data (d) {}
};
using fdselect_set = small_vector<fdselect_state, 4>;
@@ -936,6 +983,11 @@ LIBBUTL_MODEXPORT namespace butl
//
LIBBUTL_SYMEXPORT std::streamsize
fdread (int, void*, std::size_t);
+
+ // POSIX write() function wrapper, for uniformity.
+ //
+ LIBBUTL_SYMEXPORT std::streamsize
+ fdwrite (int, const void*, std::size_t);
}
#include <libbutl/fdstream.ixx>
diff --git a/libbutl/fdstream.ixx b/libbutl/fdstream.ixx
index 9ec9e06..e024af9 100644
--- a/libbutl/fdstream.ixx
+++ b/libbutl/fdstream.ixx
@@ -1,6 +1,8 @@
// file : libbutl/fdstream.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
+#include <cassert>
+
namespace butl
{
// auto_fd
@@ -165,6 +167,8 @@ namespace butl
inline std::vector<char> ifdstream::
read_binary ()
{
+ // @@ TODO: surely there is a more efficient way! See sha256!
+
std::vector<char> v (std::istreambuf_iterator<char> (*this),
std::istreambuf_iterator<char> ());
return v;
diff --git a/libbutl/filesystem.cxx b/libbutl/filesystem.cxx
index 3427ee9..28a0de8 100644
--- a/libbutl/filesystem.cxx
+++ b/libbutl/filesystem.cxx
@@ -1,9 +1,7 @@
// file : libbutl/filesystem.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/filesystem.mxx>
-#endif
+#include <libbutl/filesystem.hxx>
#include <errno.h> // errno, E*
@@ -18,7 +16,7 @@
#else
# include <libbutl/win32-utility.hxx>
-# include <io.h> // _find*(), _unlink(), _chmod()
+# include <io.h> // _unlink(), _chmod()
# include <direct.h> // _mkdir(), _rmdir()
# include <winioctl.h> // FSCTL_SET_REPARSE_POINT
# include <sys/types.h> // _stat
@@ -30,50 +28,22 @@
# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
# endif
-# include <cwchar> // mbsrtowcs(), wcsrtombs(), mbstate_t
-# include <cstring> // strncmp()
+# include <cwchar> // mbsrtowcs(), wcsrtombs(), mbstate_t
+# include <cstring> // strncmp()
+# include <type_traits> // is_same
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <cstddef>
-#include <cstdint>
-#include <utility>
-#include <iterator>
-#include <functional>
-
+#include <chrono>
#include <vector>
#include <memory> // unique_ptr
+#include <cassert>
#include <algorithm> // find(), copy()
#include <system_error>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.filesystem;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.timestamp;
-import butl.path_pattern;
-#endif
-import butl.utility; // throw_generic_error()
-import butl.fdstream;
-import butl.small_vector;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/small-vector.mxx>
-#endif
+#include <libbutl/path.hxx>
+#include <libbutl/utility.hxx> // throw_generic_error()
+#include <libbutl/fdstream.hxx>
+#include <libbutl/small-vector.hxx>
#ifndef _WIN32
# ifndef PATH_MAX
@@ -214,6 +184,19 @@ namespace butl
// static inline constexpr int
// ansec (...) {return 0;}
+ static inline entry_time
+ entry_tm (const struct stat& s) noexcept
+ {
+ auto tm = [] (time_t sec, auto nsec) -> timestamp
+ {
+ return system_clock::from_time_t (sec) +
+ chrono::duration_cast<duration> (chrono::nanoseconds (nsec));
+ };
+
+ return {tm (s.st_mtime, mnsec<struct stat> (&s, true)),
+ tm (s.st_atime, ansec<struct stat> (&s, true))};
+ }
+
// Return the modification and access times of a regular file or directory.
//
static entry_time
@@ -231,14 +214,7 @@ namespace butl
if (dir ? !S_ISDIR (s.st_mode) : !S_ISREG (s.st_mode))
return {timestamp_nonexistent, timestamp_nonexistent};
- auto tm = [] (time_t sec, auto nsec) -> timestamp
- {
- return system_clock::from_time_t (sec) +
- chrono::duration_cast<duration> (chrono::nanoseconds (nsec));
- };
-
- return {tm (s.st_mtime, mnsec<struct stat> (&s, true)),
- tm (s.st_atime, ansec<struct stat> (&s, true))};
+ return entry_tm (s);
}
// Set the modification and access times for a regular file or directory.
@@ -340,16 +316,15 @@ namespace butl
// Open a filesystem entry for reading and optionally writing its
// meta-information and return the entry handle and meta-information if the
- // path refers to an existing entry and nullhandle otherwise. Follow reparse
- // points by default. Underlying OS errors are reported by throwing
- // std::system_error, unless ignore_error is true in which case nullhandle
- // is returned. In the latter case the error code can be obtained by calling
- // GetLastError().
+ // path refers to an existing entry and nullhandle otherwise. Underlying OS
+ // errors are reported by throwing std::system_error, unless ignore_error is
+ // true in which case nullhandle is returned. In the latter case the error
+ // code can be obtained by calling GetLastError().
//
static inline pair<win32::auto_handle, BY_HANDLE_FILE_INFORMATION>
entry_info_handle (const char* p,
bool write,
- bool fr = true,
+ bool follow_reparse_points,
bool ie = false)
{
// Open the entry for reading/writing its meta-information. Follow reparse
@@ -364,7 +339,7 @@ namespace butl
nullptr,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | // Required for a directory.
- (fr ? 0 : FILE_FLAG_OPEN_REPARSE_POINT),
+ (follow_reparse_points ? 0 : FILE_FLAG_OPEN_REPARSE_POINT),
nullptr));
if (h == nullhandle)
@@ -389,13 +364,15 @@ namespace butl
}
// Return a flag indicating whether the path is to an existing filesystem
- // entry and its meta-information if so. Follow reparse points by default.
+ // entry and its meta-information if so.
//
static inline pair<bool, BY_HANDLE_FILE_INFORMATION>
- path_entry_info (const char* p, bool fr = true, bool ie = false)
+ path_entry_handle_info (const char* p,
+ bool follow_reparse_points,
+ bool ie = false)
{
pair<auto_handle, BY_HANDLE_FILE_INFORMATION> hi (
- entry_info_handle (p, false /* write */, fr, ie));
+ entry_info_handle (p, false /* write */, follow_reparse_points, ie));
if (hi.first == nullhandle)
return make_pair (false, BY_HANDLE_FILE_INFORMATION ());
@@ -407,9 +384,34 @@ namespace butl
}
static inline pair<bool, BY_HANDLE_FILE_INFORMATION>
- path_entry_info (const path& p, bool fr = true, bool ie = false)
+ path_entry_handle_info (const path& p, bool fr, bool ie = false)
+ {
+ return path_entry_handle_info (p.string ().c_str (), fr, ie);
+ }
+
+ // Return a flag indicating whether the path is to an existing filesystem
+ // entry and its extended attributes if so. Don't follow reparse points.
+ //
+ static inline pair<bool, WIN32_FILE_ATTRIBUTE_DATA>
+ path_entry_info (const char* p, bool ie = false)
+ {
+ WIN32_FILE_ATTRIBUTE_DATA r;
+ if (!GetFileAttributesExA (p, GetFileExInfoStandard, &r))
+ {
+ DWORD ec;
+ if (ie || error_file_not_found (ec = GetLastError ()))
+ return make_pair (false, WIN32_FILE_ATTRIBUTE_DATA ());
+
+ throw_system_error (ec);
+ }
+
+ return make_pair (true, r);
+ }
+
+ static inline pair<bool, WIN32_FILE_ATTRIBUTE_DATA>
+ path_entry_info (const path& p, bool ie = false)
{
- return path_entry_info (p.string ().c_str (), fr, ie);
+ return path_entry_info (p.string ().c_str (), ie);
}
// Reparse point data.
@@ -645,8 +647,48 @@ namespace butl
return reparse_point_entry (p.string ().c_str (), ie);
}
- pair<bool, entry_stat>
- path_entry (const char* p, bool fl, bool ie)
+ static inline timestamp
+ to_timestamp (const FILETIME& t)
+ {
+ // Time in FILETIME is in 100 nanosecond "ticks" since "Windows epoch"
+ // (1601-01-01T00:00:00Z). To convert it to "UNIX epoch"
+ // (1970-01-01T00:00:00Z) we need to subtract 11644473600 seconds.
+ //
+ uint64_t nsec ((static_cast<uint64_t> (t.dwHighDateTime) << 32) |
+ t.dwLowDateTime);
+
+ nsec -= 11644473600ULL * 10000000; // Now in UNIX epoch.
+ nsec *= 100; // Now in nanoseconds.
+
+ return timestamp (
+ chrono::duration_cast<duration> (chrono::nanoseconds (nsec)));
+ }
+
+ static inline FILETIME
+ to_filetime (timestamp t)
+ {
+ // Time in FILETIME is in 100 nanosecond "ticks" since "Windows epoch"
+ // (1601-01-01T00:00:00Z). To convert "UNIX epoch" (1970-01-01T00:00:00Z)
+ // to it we need to add 11644473600 seconds.
+ //
+ uint64_t ticks (chrono::duration_cast<chrono::nanoseconds> (
+ t.time_since_epoch ()).count ());
+
+ ticks /= 100; // Now in 100 nanosecond "ticks".
+ ticks += 11644473600ULL * 10000000; // Now in "Windows epoch".
+
+ FILETIME r;
+ r.dwHighDateTime = (ticks >> 32) & 0xFFFFFFFF;
+ r.dwLowDateTime = ticks & 0xFFFFFFFF;
+ return r;
+ }
+
+ // If the being returned entry type is regular or directory and et is not
+ // NULL, then also save the entry modification and access times into the
+ // referenced variable.
+ //
+ static inline pair<bool, entry_stat>
+ path_entry (const char* p, bool fl, bool ie, entry_time* et)
{
// A path like 'C:', while being a root path in our terminology, is not as
// such for Windows, that maintains current directory for each drive, and
@@ -657,73 +699,105 @@ namespace butl
string d;
if (path::traits_type::root (p))
{
- d = p;
+ d = string (p); // GCC bug #105329.
d += path::traits_type::directory_separator;
p = d.c_str ();
}
// Stat the entry not following reparse points.
//
- pair<bool, BY_HANDLE_FILE_INFORMATION> pi (
- path_entry_info (p, false /* follow_reparse_points */, ie));
+ pair<bool, WIN32_FILE_ATTRIBUTE_DATA> pi (path_entry_info (p, ie));
if (!pi.first)
return make_pair (false, entry_stat {entry_type::unknown, 0});
- if (reparse_point (pi.second.dwFileAttributes))
+ auto entry_info = [et] (const auto& ei)
{
- pair<entry_type, path> rp (reparse_point_entry (p, ie));
+ if (et != nullptr)
+ {
+ et->modification = to_timestamp (ei.ftLastWriteTime);
+ et->access = to_timestamp (ei.ftLastAccessTime);
+ }
- if (rp.first == entry_type::symlink)
+ if (directory (ei.dwFileAttributes))
+ return make_pair (true, entry_stat {entry_type::directory, 0});
+ else
+ return make_pair (
+ true,
+ entry_stat {entry_type::regular,
+ ((uint64_t (ei.nFileSizeHigh) << 32) | ei.nFileSizeLow)});
+ };
+
+ if (!reparse_point (pi.second.dwFileAttributes))
+ return entry_info (pi.second);
+
+ pair<entry_type, path> rp (reparse_point_entry (p, ie));
+
+ if (rp.first == entry_type::symlink)
+ {
+ // If following symlinks is requested, then follow the reparse point and
+ // return its target information. Otherwise, return the symlink entry
+ // type.
+ //
+ if (fl)
{
- // If following symlinks is requested, then follow the reparse point,
- // overwrite its own information with the resolved target information,
- // and fall through. Otherwise, return the symlink entry type.
- //
- if (fl)
- {
- pi = path_entry_info (p, true /* follow_reparse_points */, ie);
+ pair<bool, BY_HANDLE_FILE_INFORMATION> pi (
+ path_entry_handle_info (p, true /* follow_reparse_points */, ie));
- if (!pi.first)
- return make_pair (false, entry_stat {entry_type::unknown, 0});
- }
- else
- return make_pair (true, entry_stat {entry_type::symlink, 0});
+ return pi.first
+ ? entry_info (pi.second)
+ : make_pair (false, entry_stat {entry_type::unknown, 0});
}
- else if (rp.first == entry_type::unknown)
- return make_pair (false, entry_stat {entry_type::unknown, 0});
- else // entry_type::other
- return make_pair (true, entry_stat {entry_type::other, 0});
+ else
+ return make_pair (true, entry_stat {entry_type::symlink, 0});
}
+ else if (rp.first == entry_type::unknown)
+ return make_pair (false, entry_stat {entry_type::unknown, 0});
+ else // entry_type::other
+ return make_pair (true, entry_stat {entry_type::other, 0});
+ }
- if (directory (pi.second.dwFileAttributes))
- return make_pair (true, entry_stat {entry_type::directory, 0});
- else
- return make_pair (
- true,
- entry_stat {entry_type::regular,
- ((uint64_t (pi.second.nFileSizeHigh) << 32) |
- pi.second.nFileSizeLow)});
+ static inline pair<bool, entry_stat>
+ path_entry (const path& p, bool fl, bool ie, entry_time* et)
+ {
+ return path_entry (p.string ().c_str (), fl, ie, et);
+ }
+
+ pair<bool, entry_stat>
+ path_entry (const char* p, bool fl, bool ie)
+ {
+ return path_entry (p, fl, ie, nullptr /* entry_time */);
}
permissions
path_permissions (const path& p)
{
- pair<bool, BY_HANDLE_FILE_INFORMATION> pi (path_entry_info (p));
+ // Let's optimize for the common case when the entry is not a reparse
+ // point.
+ //
+ auto attr_to_perm = [] (const auto& pi) -> permissions
+ {
+ if (!pi.first)
+ throw_generic_error (ENOENT);
- if (!pi.first)
- throw_generic_error (ENOENT);
+ // On Windows a filesystem entry is always readable. Also there is no
+ // notion of group/other permissions at OS level, so we extrapolate user
+ // permissions to group/other permissions (as the _stat() function
+ // does).
+ //
+ permissions r (permissions::ru | permissions::rg | permissions::ro);
- // On Windows a filesystem entry is always readable. Also there is no
- // notion of group/other permissions at OS level, so we extrapolate user
- // permissions to group/other permissions (as the _stat() function does).
- //
- permissions r (permissions::ru | permissions::rg | permissions::ro);
+ if (!readonly (pi.second.dwFileAttributes))
+ r |= permissions::wu | permissions::wg | permissions::wo;
- if (!readonly (pi.second.dwFileAttributes))
- r |= permissions::wu | permissions::wg | permissions::wo;
+ return r;
+ };
- return r;
+ pair<bool, WIN32_FILE_ATTRIBUTE_DATA> pi (path_entry_info (p));
+ return !pi.first || !reparse_point (pi.second.dwFileAttributes)
+ ? attr_to_perm (pi)
+ : attr_to_perm (
+ path_entry_handle_info (p, true /* follow_reparse_points */));
}
void
@@ -749,50 +823,26 @@ namespace butl
static entry_time
entry_tm (const char* p, bool dir)
{
- pair<bool, BY_HANDLE_FILE_INFORMATION> pi (path_entry_info (p));
-
- // If the entry is of the wrong type, then let's pretend that it doesn't
- // exists.
+ // Let's optimize for the common case when the entry is not a reparse
+ // point.
//
- if (!pi.first || directory (pi.second.dwFileAttributes) != dir)
- return {timestamp_nonexistent, timestamp_nonexistent};
-
- auto tm = [] (const FILETIME& t) -> timestamp
+ auto attr_to_time = [dir] (const auto& pi) -> entry_time
{
- // Time in FILETIME is in 100 nanosecond "ticks" since "Windows epoch"
- // (1601-01-01T00:00:00Z). To convert it to "UNIX epoch"
- // (1970-01-01T00:00:00Z) we need to subtract 11644473600 seconds.
+ // If the entry is of the wrong type, then let's pretend that it doesn't
+ // exists.
//
- uint64_t nsec ((static_cast<uint64_t> (t.dwHighDateTime) << 32) |
- t.dwLowDateTime);
-
- nsec -= 11644473600ULL * 10000000; // Now in UNIX epoch.
- nsec *= 100; // Now in nanoseconds.
+ if (!pi.first || directory (pi.second.dwFileAttributes) != dir)
+ return entry_time {timestamp_nonexistent, timestamp_nonexistent};
- return timestamp (
- chrono::duration_cast<duration> (chrono::nanoseconds (nsec)));
+ return entry_time {to_timestamp (pi.second.ftLastWriteTime),
+ to_timestamp (pi.second.ftLastAccessTime)};
};
- return {tm (pi.second.ftLastWriteTime), tm (pi.second.ftLastAccessTime)};
- }
-
- static inline FILETIME
- to_filetime (timestamp t)
- {
- // Time in FILETIME is in 100 nanosecond "ticks" since "Windows epoch"
- // (1601-01-01T00:00:00Z). To convert "UNIX epoch"
- // (1970-01-01T00:00:00Z) to it we need to add 11644473600 seconds.
- //
- uint64_t ticks (chrono::duration_cast<chrono::nanoseconds> (
- t.time_since_epoch ()).count ());
-
- ticks /= 100; // Now in 100 nanosecond "ticks".
- ticks += 11644473600ULL * 10000000; // Now in "Windows epoch".
-
- FILETIME r;
- r.dwHighDateTime = (ticks >> 32) & 0xFFFFFFFF;
- r.dwLowDateTime = ticks & 0xFFFFFFFF;
- return r;
+ pair<bool, WIN32_FILE_ATTRIBUTE_DATA> pi (path_entry_info (p));
+ return !pi.first || !reparse_point (pi.second.dwFileAttributes)
+ ? attr_to_time (pi)
+ : attr_to_time (
+ path_entry_handle_info (p, true /* follow_reparse_points */));
}
// Set the modification and access times for a regular file or directory.
@@ -803,7 +853,9 @@ namespace butl
// See also touch_file() below.
//
pair<auto_handle, BY_HANDLE_FILE_INFORMATION> hi (
- entry_info_handle (p, true /* write */));
+ entry_info_handle (p,
+ true /* write */,
+ true /* follow_reparse_points */));
// If the entry is of the wrong type, then let's pretend that it doesn't
// exist.
@@ -888,7 +940,9 @@ namespace butl
// implicitly.
//
pair<auto_handle, BY_HANDLE_FILE_INFORMATION> hi (
- entry_info_handle (p.string ().c_str (), true /* write */));
+ entry_info_handle (p.string ().c_str (),
+ true /* write */,
+ true /* follow_reparse_points */));
if (hi.first != nullhandle)
{
@@ -1037,7 +1091,7 @@ namespace butl
//
try
{
- for (const dir_entry& de: dir_iterator (p, false /* ignore_dangling */))
+ for (const dir_entry& de: dir_iterator (p, dir_iterator::no_follow))
{
path ep (p / de.path ()); //@@ Would be good to reuse the buffer.
@@ -1088,12 +1142,12 @@ namespace butl
// failure (see mventry() for details). If that's the case, we will keep
// trying to move the file for two seconds.
//
- for (size_t i (0); i < 21; ++i)
+ for (size_t i (0); i < 41; ++i)
{
- // Sleep 100 milliseconds before the removal retry.
+ // Sleep 50 milliseconds before the removal retry.
//
if (i != 0)
- Sleep (100);
+ Sleep (50);
ur = _unlink (f);
@@ -1641,9 +1695,12 @@ namespace butl
}
void
- cpfile (const path& from, const path& to, cpflags fl)
+ cpfile (const path& from,
+ const path& to,
+ cpflags fl,
+ optional<permissions> cperm)
{
- permissions perm (path_permissions (from));
+ permissions perm (cperm ? *cperm : path_permissions (from));
auto_rmfile rm;
cpfile<is_base_of<system_error, ios_base::failure>::value> (
@@ -1735,12 +1792,12 @@ namespace butl
// fdopen().
//
DWORD ec;
- for (size_t i (0); i < 21; ++i)
+ for (size_t i (0); i < 41; ++i)
{
// Sleep 100 milliseconds before the move retry.
//
if (i != 0)
- Sleep (100);
+ Sleep (50);
if (MoveFileExA (f, t, mfl))
return;
@@ -1842,7 +1899,7 @@ namespace butl
h_ = x.h_;
x.h_ = nullptr;
- ignore_dangling_ = x.ignore_dangling_;
+ mode_ = x.mode_;
}
return *this;
}
@@ -1863,6 +1920,11 @@ namespace butl
entry_type dir_entry::
type (bool follow_symlinks) const
{
+ // Note that this function can only be used for resolving an entry type
+ // lazily and thus can't be used with the detect_dangling dir_iterator
+ // mode (see dir_iterator::next () implementation for details). Thus, we
+ // always throw on the stat()/lstat() failure.
+ //
path_type p (b_ / p_);
struct stat s;
if ((follow_symlinks
@@ -1870,7 +1932,18 @@ namespace butl
: lstat (p.string ().c_str (), &s)) != 0)
throw_generic_error (errno);
- return butl::type (s);
+ entry_type r (butl::type (s));
+
+ // While at it, also save the entry modification and access times.
+ //
+ if (r != entry_type::symlink)
+ {
+ entry_time t (entry_tm (s));
+ mtime_ = t.modification;
+ atime_ = t.access;
+ }
+
+ return r;
}
// dir_iterator
@@ -1881,8 +1954,8 @@ namespace butl
};
dir_iterator::
- dir_iterator (const dir_path& d, bool ignore_dangling)
- : ignore_dangling_ (ignore_dangling)
+ dir_iterator (const dir_path& d, mode m)
+ : mode_ (m)
{
unique_ptr<DIR, dir_deleter> h (opendir (d.string ().c_str ()));
h_ = h.get ();
@@ -1898,7 +1971,7 @@ namespace butl
}
template <typename D>
- static inline /*constexpr*/ entry_type
+ static inline /*constexpr*/ optional<entry_type>
d_type (const D* d, decltype(d->d_type)*)
{
switch (d->d_type)
@@ -1926,13 +1999,13 @@ namespace butl
#endif
return entry_type::other;
- default: return entry_type::unknown;
+ default: return nullopt;
}
}
template <typename D>
- static inline constexpr entry_type
- d_type (...) {return entry_type::unknown;}
+ static inline constexpr optional<entry_type>
+ d_type (...) {return nullopt;}
void dir_iterator::
next ()
@@ -1954,25 +2027,43 @@ namespace butl
e_.p_ = move (p);
e_.t_ = d_type<struct dirent> (de, nullptr);
- e_.lt_ = entry_type::unknown;
+ e_.lt_ = nullopt;
+
+ e_.mtime_ = timestamp_unknown;
+ e_.atime_ = timestamp_unknown;
// If requested, we ignore dangling symlinks, skipping ones with
- // non-existing or inaccessible targets.
+ // non-existing or inaccessible targets (ignore_dangling mode), or set
+ // the entry_type::unknown type for them (detect_dangling mode).
//
- if (ignore_dangling_)
+ if (mode_ != no_follow)
{
- // Note that ltype () can potentially lstat() (see d_type() for
+ bool dd (mode_ == detect_dangling);
+
+ // Note that ltype () can potentially lstat() (see type() for
// details) and so throw. We, however, need to skip the entry if it
// is already removed (due to a race) and throw on any other error.
//
path fp (e_.base () / e_.path ());
const char* p (fp.string ().c_str ());
- if (e_.t_ == entry_type::unknown)
+ if (!e_.t_)
{
struct stat s;
if (lstat (p, &s) != 0)
{
+ // Given that we have already enumerated the filesystem entry,
+ // these error codes can only mean that the entry doesn't exist
+ // anymore and so we always skip it.
+ //
+ // If errno is EACCES, then the permission to search a directory
+ // we currently iterate over has been revoked. Throwing in this
+ // case sounds like the best choice.
+ //
+ // Note that according to POSIX the filesystem entry we call
+ // lstat() on doesn't require any specific permissions to be
+ // granted.
+ //
if (errno == ENOENT || errno == ENOTDIR)
continue;
@@ -1980,21 +2071,53 @@ namespace butl
}
e_.t_ = type (s);
+
+ if (*e_.t_ != entry_type::symlink)
+ {
+ entry_time t (entry_tm (s));
+ e_.mtime_ = t.modification;
+ e_.atime_ = t.access;
+ }
}
- if (e_.t_ == entry_type::symlink)
+ // The entry type should be present and may not be
+ // entry_type::unknown.
+ //
+ //assert (e_.t_ && *e_.t_ != entry_type::unknown);
+
+ // Check if the symlink target exists and is accessible and set the
+ // target type.
+ //
+ if (*e_.t_ == entry_type::symlink)
{
struct stat s;
if (stat (p, &s) != 0)
{
if (errno == ENOENT || errno == ENOTDIR || errno == EACCES)
- continue;
-
- throw_generic_error (errno);
+ {
+ if (dd)
+ e_.lt_ = entry_type::unknown;
+ else
+ continue;
+ }
+ else
+ throw_generic_error (errno);
}
+ else
+ {
+ e_.lt_ = type (s);
- e_.lt_ = type (s); // While at it, set the target type.
+ entry_time t (entry_tm (s));
+ e_.mtime_ = t.modification;
+ e_.atime_ = t.access;
+ }
}
+
+ // The symlink target type should be present and in the
+ // ignore_dangling mode it may not be entry_type::unknown.
+ //
+ //assert (*e_.t_ != entry_type::symlink ||
+ // (e_.lt_ && (dd || *e_.lt_ != entry_type::unknown)));
}
}
else if (errno == 0)
@@ -2015,11 +2138,49 @@ namespace butl
// dir_entry
//
+ entry_type dir_entry::
+ type (bool follow_symlinks) const
+ {
+ // Note that this function can only be used for resolving an entry type
+ // lazily and thus can't be used with the detect_dangling dir_iterator
+ // mode (see dir_iterator::next () implementation for details). Thus, we
+ // always throw if the entry info can't be retrieved.
+ //
+ // While at it, also save the entry modification and access times.
+ //
+ path_type p (base () / path ());
+ entry_time et;
+ pair<bool, entry_stat> e (
+ path_entry (p, follow_symlinks, false /* ignore_error */, &et));
+
+ if (!e.first)
+ throw_generic_error (ENOENT);
+
+ if (e.second.type == entry_type::regular ||
+ e.second.type == entry_type::directory)
+ {
+ mtime_ = et.modification;
+ atime_ = et.access;
+ }
+
+ return e.second.type;
+ }
+
+ // dir_iterator
+ //
+ static_assert(is_same<HANDLE, void*>::value, "HANDLE is not void*");
+
+ static inline HANDLE
+ to_handle (intptr_t h)
+ {
+ return reinterpret_cast<HANDLE> (h);
+ }
+
dir_iterator::
~dir_iterator ()
{
if (h_ != -1)
- _findclose (h_); // Ignore any errors.
+ FindClose (to_handle (h_)); // Ignore any errors.
}
dir_iterator& dir_iterator::
@@ -2029,56 +2190,32 @@ namespace butl
{
e_ = move (x.e_);
- if (h_ != -1 && _findclose (h_) == -1)
- throw_generic_error (errno);
+ if (h_ != -1 && !FindClose (to_handle (h_)))
+ throw_system_error (GetLastError ());
h_ = x.h_;
x.h_ = -1;
- ignore_dangling_ = x.ignore_dangling_;
+ mode_ = x.mode_;
}
return *this;
}
- entry_type dir_entry::
- type (bool follow_symlinks) const
- {
- path_type p (base () / path ());
- pair<bool, entry_stat> e (path_entry (p, follow_symlinks));
-
- if (!e.first)
- throw_generic_error (ENOENT);
-
- return e.second.type;
- }
-
- // dir_iterator
- //
- struct auto_dir
+ dir_iterator::
+ dir_iterator (const dir_path& d, mode m)
+ : mode_ (m)
{
- explicit
- auto_dir (intptr_t& h): h_ (&h) {}
-
- auto_dir (const auto_dir&) = delete;
- auto_dir& operator= (const auto_dir&) = delete;
-
- ~auto_dir ()
+ struct deleter
{
- if (h_ != nullptr && *h_ != -1)
- _findclose (*h_);
- }
-
- void release () {h_ = nullptr;}
+ void operator() (intptr_t* p) const
+ {
+ if (p != nullptr && *p != -1)
+ FindClose (to_handle (*p));
+ }
+ };
- private:
- intptr_t* h_;
- };
+ unique_ptr<intptr_t, deleter> h (&h_);
- dir_iterator::
- dir_iterator (const dir_path& d, bool ignore_dangling)
- : ignore_dangling_ (ignore_dangling)
- {
- auto_dir h (h_);
e_.b_ = d; // Used by next().
next ();
@@ -2091,31 +2228,37 @@ namespace butl
for (;;)
{
bool r;
- _finddata_t fi;
+ WIN32_FIND_DATA fi;
if (h_ == -1)
{
// The call is made from the constructor. Any other call with h_ == -1
// is illegal.
//
-
- // Check to distinguish non-existent vs empty directories.
+ // Note that we used to check for the directory existence before
+ // iterating over it. However, let's not pessimize things and only
+ // check for the directory existence if FindFirstFileExA() fails.
//
- if (!dir_exists (e_.base ()))
- throw_generic_error (ENOENT);
- h_ = _findfirst ((e_.base () / path ("*")).string ().c_str (), &fi);
- r = h_ != -1;
+ h_ = reinterpret_cast<intptr_t> (
+ FindFirstFileExA ((e_.base () / path ("*")).string ().c_str (),
+ FindExInfoBasic,
+ &fi,
+ FindExSearchNameMatch,
+ NULL,
+ 0));
+
+ r = (h_ != -1);
}
else
- r = _findnext (h_, &fi) == 0;
+ r = FindNextFileA (to_handle (h_), &fi);
if (r)
{
// We can accept some overhead for '.' and '..' (relying on short
// string optimization) in favor of a more compact code.
//
- path p (fi.name);
+ path p (fi.cFileName);
// Skip '.' and '..'.
//
@@ -2124,26 +2267,47 @@ namespace butl
e_.p_ = move (p);
- // Note that the entry type detection always requires to additionally
- // query the entry information. Thus, we evaluate its type lazily.
+ DWORD a (fi.dwFileAttributes);
+ bool rp (reparse_point (a));
+
+ // Evaluate the entry type lazily if this is a reparse point since it
+ // requires to additionally query the entry information (see
+ // reparse_point_entry() for details).
//
- e_.t_ = entry_type::unknown;
+ e_.t_ = rp ? nullopt :
+ directory (a) ? optional<entry_type> (entry_type::directory) :
+ optional<entry_type> (entry_type::regular) ;
- e_.lt_ = entry_type::unknown;
+ e_.lt_ = nullopt;
+
+ e_.mtime_ = rp ? timestamp_unknown : to_timestamp (fi.ftLastWriteTime);
+
+ // Note that according to MSDN for the FindFirstFile[Ex]() function
+ // "the NTFS file system delays updates to the last access time for a
+ // file by up to 1 hour after the last access" and "on the FAT file
+ // system access time has a resolution of 1 day".
+ //
+ e_.atime_ = timestamp_unknown;
// If requested, we ignore dangling symlinks and junctions, skipping
- // ones with non-existing or inaccessible targets.
+ // ones with non-existing or inaccessible targets (ignore_dangling
+ // mode), or set the entry_type::unknown type for them
+ // (detect_dangling mode).
//
- if (ignore_dangling_)
+ if (rp && mode_ != no_follow)
{
+ bool dd (mode_ == detect_dangling);
+
// Check the last error code throwing for codes other than "path not
- // found" and "access denied".
+ // found" and "access denied" and returning this error code
+ // otherwise.
//
auto verify_error = [] ()
{
DWORD ec (GetLastError ());
if (!error_file_not_found (ec) && ec != ERROR_ACCESS_DENIED)
throw_system_error (ec);
+ return ec;
};
// Note that ltype() queries the entry information due to the type
@@ -2154,48 +2318,50 @@ namespace butl
path fp (e_.base () / e_.path ());
const char* p (fp.string ().c_str ());
- DWORD a (GetFileAttributesA (p));
- if (a == INVALID_FILE_ATTRIBUTES)
- {
- // Note that sometimes trying to obtain attributes for a
- // filesystem entry that was potentially removed ends up with
- // ERROR_ACCESS_DENIED. One can argue that there can be another
- // reason for this error (antivirus, indexer, etc). However, given
- // that the entry is seen by a _find*() function and normally you
- // can retrieve attributes for a read-only entry and for an entry
- // opened in the non-shared mode (see the CreateFile() function
- // documentation for details) the only meaningful explanation for
- // ERROR_ACCESS_DENIED is that the entry is being removed. Also
- // the DeleteFile() documentation mentions such a possibility.
- //
- verify_error ();
- continue;
- }
+ pair<entry_type, path> rpe (
+ reparse_point_entry (p, true /* ignore_error */));
- if (reparse_point (a))
+ if (rpe.first == entry_type::unknown)
{
- pair<entry_type, path> rp (
- reparse_point_entry (p, true /* ignore_error */));
+ DWORD ec (verify_error ());
- if (rp.first == entry_type::unknown)
- {
- verify_error ();
+ // Silently skip the entry if it is not found (being already
+ // deleted) or we are in the ignore dangling mode. Otherwise, set
+ // the entry type to unknown.
+ //
+ // Note that sometimes trying to obtain information for a being
+ // removed filesystem entry ends up with ERROR_ACCESS_DENIED (see
+ // DeleteFile() and CreateFile() for details). Probably getting
+ // this error code while trying to obtain the reparse point
+ // information (involves calling CreateFile(FILE_READ_EA) and
+ // DeviceIoControl()) can also be interpreted differently. We,
+ // however, always treat it as "access denied" in the detect
+ // dangling mode for good measure. Let's see if that won't be too
+ // noisy.
+ //
+ if (ec != ERROR_ACCESS_DENIED || !dd)
continue;
- }
- e_.t_ = rp.first;
+ // Fall through.
}
- else
- e_.t_ = directory (a)
- ? entry_type::directory
- : entry_type::regular;
- if (e_.t_ == entry_type::symlink)
+ e_.t_ = rpe.first;
+
+ // In this mode the entry type should be present and in the
+ // ignore_dangling mode it may not be entry_type::unknown.
+ //
+ //assert (e_.t_ && (dd || *e_.t_ != entry_type::unknown));
+
+ // Check if the symlink target exists and is accessible and set the
+ // target type.
+ //
+ if (*e_.t_ == entry_type::symlink)
{
// Query the target info.
//
// Note that we use entry_info_handle() rather than
- // path_entry_info() to be able to verify an error on failure.
+ // path_entry_handle_info() to be able to verify an error on
+ // failure.
//
pair<auto_handle, BY_HANDLE_FILE_INFORMATION> ti (
entry_info_handle (p,
@@ -2206,31 +2372,59 @@ namespace butl
if (ti.first == nullhandle)
{
verify_error ();
- continue;
+
+ if (dd)
+ e_.lt_ = entry_type::unknown;
+ else
+ continue;
}
+ else
+ {
+ ti.first.close (); // Checks for error.
- ti.first.close (); // Checks for error.
+ e_.lt_ = directory (ti.second.dwFileAttributes)
+ ? entry_type::directory
+ : entry_type::regular;
- // While at it, set the target type.
- //
- e_.lt_ = directory (ti.second.dwFileAttributes)
- ? entry_type::directory
- : entry_type::regular;
+ e_.mtime_ = to_timestamp (ti.second.ftLastWriteTime);
+ e_.atime_ = to_timestamp (ti.second.ftLastAccessTime);
+ }
}
+
+ // In this mode the symlink target type should be present and in the
+ // ignore_dangling mode it may not be entry_type::unknown.
+ //
+ //assert (*e_.t_ != entry_type::symlink ||
+ // (e_.lt_ && (dd || *e_.lt_ != entry_type::unknown)));
}
}
- else if (errno == ENOENT)
+ else
{
- // End of stream.
+ DWORD ec (GetLastError ());
+ bool first (h_ == -1);
+
+ // Check to distinguish non-existent vs empty directories.
//
- if (h_ != -1)
+ // Note that dir_exists() handles not only the "filesystem entry does
+ // not exist" case but also the case when the entry exists but is not
+ // a directory.
+ //
+ if (first && !dir_exists (e_.base ()))
+ throw_generic_error (ENOENT);
+
+ if (ec == (first ? ERROR_FILE_NOT_FOUND : ERROR_NO_MORE_FILES))
{
- _findclose (h_);
- h_ = -1;
+ // End of stream.
+ //
+ if (h_ != -1)
+ {
+ FindClose (to_handle (h_));
+ h_ = -1;
+ }
}
+ else
+ throw_system_error (ec);
}
- else
- throw_generic_error (errno);
break;
}
@@ -2238,14 +2432,27 @@ namespace butl
#endif
// Search for paths matching the pattern and call the specified function for
- // each matching path. Return false if the underlying func() call returns
- // false. Otherwise the function conforms to the path_search() description.
+ // each matching path. Return false if the underlying func() or
+ // dangling_func() call returns false. Otherwise the function conforms to
+ // the path_search() description.
//
// Note that the access to the traversed directory tree (real or virtual) is
// performed through the provided filesystem object.
//
static const string any_dir ("*/");
+ // Filesystem traversal callbacks.
+ //
+ // Called before entering a directory for the recursive traversal. If
+ // returns false, then the directory is not entered.
+ //
+ using preopen = function<bool (const dir_path&)>;
+
+ // Called before skipping a dangling link. If returns false, then the
+ // traversal is stopped.
+ //
+ using preskip = function<bool (const dir_entry&)>;
+
template <typename FS>
static bool
search (
@@ -2253,11 +2460,14 @@ namespace butl
dir_path pattern_dir,
path_match_flags fl,
const function<bool (path&&, const string& pattern, bool interm)>& func,
+ const function<bool (const dir_entry&)>& dangling_func,
FS& filesystem)
{
bool follow_symlinks ((fl & path_match_flags::follow_symlinks) !=
path_match_flags::none);
+ assert (follow_symlinks || dangling_func == nullptr);
+
// Fast-forward the leftmost pattern non-wildcard components. So, for
// example, search for foo/f* in /bar/ becomes search for f* in /bar/foo/.
//
@@ -2304,17 +2514,47 @@ namespace butl
//
bool simple (pattern.simple ());
- // Note that we rely on "small function object" optimization here.
+ // If symlinks need to be followed, then pass the preskip callback for the
+ // filesystem iterator.
+ //
+ bool fs (follow_symlinks || !simple);
+ preskip ps;
+ bool dangling_stop (false);
+
+ if (fs)
+ {
+ if (dangling_func != nullptr)
+ {
+ // Note that we rely on the "small function object" optimization here.
+ //
+ ps = [&dangling_func, &dangling_stop] (const dir_entry& de) -> bool
+ {
+ dangling_stop = !dangling_func (de);
+ return !dangling_stop;
+ };
+ }
+ else
+ {
+ ps = [] (const dir_entry& de) -> bool
+ {
+ throw_generic_error (
+ de.ltype () == entry_type::symlink ? ENOENT : EACCES);
+ };
+ }
+ }
+
+ // Note that we rely on the "small function object" optimization here.
//
typename FS::iterator_type i (filesystem.iterator (
pattern_dir,
path_pattern_recursive (pcr),
path_pattern_self_matching (pcr),
- follow_symlinks || !simple,
+ fs,
[&pattern_dir, &func] (const dir_path& p) -> bool // Preopen.
{
return func (pattern_dir / p, any_dir, true);
- }));
+ },
+ move (ps)));
// Canonicalize the pattern component collapsing consecutive stars (used to
// express that it is recursive) into a single one.
@@ -2360,7 +2600,7 @@ namespace butl
// represented by the iterator as an empty path, and so we need to
// compute it (the leaf would actually be enough) for matching. This
// leaf can be acquired from the pattern_dir (if not empty) or
- // start_dir. We don't expect the start_dir to be empty, as the
+ // start_dir. We don't expect the start_dir to be empty, as the
// filesystem object must replace an empty start directory with the
// current one. This is the case when we search in the current directory
// (start_dir is empty) with a pattern that starts with a *** wildcard
@@ -2399,10 +2639,14 @@ namespace butl
pattern_dir / path_cast<dir_path> (move (p)),
fl,
func,
+ dangling_func,
filesystem))
return false;
}
+ if (dangling_stop)
+ return false;
+
// If requested, also search with the absent-matching pattern path
// component omitted, unless this is the only pattern component.
//
@@ -2410,8 +2654,15 @@ namespace butl
pc.to_directory () &&
(!pattern_dir.empty () || !simple) &&
pc.string ().find_first_not_of ('*') == string::npos &&
- !search (pattern.leaf (pc), pattern_dir, fl, func, filesystem))
+ !search (pattern.leaf (pc),
+ pattern_dir,
+ fl,
+ func,
+ dangling_func,
+ filesystem))
+ {
return false;
+ }
return true;
}
@@ -2420,8 +2671,6 @@ namespace butl
//
static const dir_path empty_dir;
- using preopen = function<bool (const dir_path&)>;
-
// Base for filesystem (see above) implementations.
//
// Don't copy start directory. It is expected to exist till the end of the
@@ -2471,13 +2720,17 @@ namespace butl
bool recursive,
bool self,
bool fs,
- preopen po)
+ preopen po,
+ preskip ps)
: start_ (move (p)),
recursive_ (recursive),
self_ (self),
follow_symlinks_ (fs),
- preopen_ (move (po))
+ preopen_ (move (po)),
+ preskip_ (move (ps))
{
+ assert (fs || ps == nullptr);
+
open (dir_path (), self_);
}
@@ -2487,12 +2740,16 @@ namespace butl
recursive_dir_iterator& operator= (const recursive_dir_iterator&) = delete;
recursive_dir_iterator (recursive_dir_iterator&&) = default;
- // Return false if no more entries left. Otherwise save the next entry path
- // and return true. The path is relative to the directory being
+ // Return false if no more entries left. Otherwise save the next entry
+ // path and return true. The path is relative to the directory being
// traversed and contains a trailing separator for sub-directories. Throw
// std::system_error in case of a failure (insufficient permissions,
// dangling symlink encountered, etc).
//
+ // If symlinks need to be followed, then skip inaccessible/dangling
+ // entries or, if the preskip callback is specified and returns false for
+ // such an entry, stop the entire traversal.
+ //
bool
next (path& p)
{
@@ -2501,44 +2758,64 @@ namespace butl
auto& i (iters_.back ());
- // If we got to the end of directory sub-entries, then go one level up
- // and return this directory path.
- //
- if (i.first == dir_iterator ())
+ for (;;) // Skip inaccessible/dangling entries.
{
- path d (move (i.second));
- iters_.pop_back ();
+ // If we got to the end of directory sub-entries, then go one level up
+ // and return this directory path.
+ //
+ if (i.first == dir_iterator ())
+ {
+ path d (move (i.second));
+ iters_.pop_back ();
+
+ // Return the path unless it is the last one (the directory we
+ // started to iterate from) and the self flag is not set.
+ //
+ if (iters_.empty () && !self_)
+ return false;
+
+ p = move (d);
+ return true;
+ }
- // Return the path unless it is the last one (the directory we started
- // to iterate from) and the self flag is not set.
+ const dir_entry& de (*i.first);
+
+ // Append separator if a directory. Note that dir_entry::type() can
+ // throw.
//
- if (iters_.empty () && !self_)
- return false;
+ entry_type et (follow_symlinks_ ? de.type () : de.ltype ());
- p = move (d);
- return true;
- }
+ // If the entry turned out to be inaccessible/dangling, then skip it
+ // if the preskip function is not specified or returns true and stop
+ // the entire traversal otherwise.
+ //
+ if (et == entry_type::unknown)
+ {
+ if (preskip_ != nullptr && !preskip_ (de))
+ {
+ iters_.clear ();
+ return false;
+ }
- const dir_entry& de (*i.first);
+ ++i.first;
+ continue;
+ }
- // Append separator if a directory. Note that dir_entry::type() can
- // throw.
- //
- entry_type et (follow_symlinks_ ? de.type () : de.ltype ());
- path pe (et == entry_type::directory
- ? path_cast<dir_path> (i.second / de.path ())
- : i.second / de.path ());
+ path pe (et == entry_type::directory
+ ? path_cast<dir_path> (i.second / de.path ())
+ : i.second / de.path ());
- ++i.first;
+ ++i.first;
- if (recursive_ && pe.to_directory ())
- {
- open (path_cast<dir_path> (move (pe)), true);
- return next (p);
- }
+ if (recursive_ && pe.to_directory ())
+ {
+ open (path_cast<dir_path> (move (pe)), true);
+ return next (p);
+ }
- p = move (pe);
- return true;
+ p = move (pe);
+ return true;
+ }
}
private:
@@ -2560,10 +2837,15 @@ namespace butl
{
dir_path d (start_ / p);
- // If we follow symlinks, then we ignore the dangling ones.
+ // If we follow symlinks, then we may need to skip the dangling
+ // ones. Note, however, that we will be skipping them not at the
+ // dir_iterator level but ourselves, after calling the preskip
+ // callback function (see next() for details).
//
i = dir_iterator (!d.empty () ? d : dir_path ("."),
- follow_symlinks_);
+ follow_symlinks_
+ ? dir_iterator::detect_dangling
+ : dir_iterator::no_follow);
}
iters_.emplace_back (move (i), move (p));
@@ -2593,6 +2875,7 @@ namespace butl
bool self_;
bool follow_symlinks_;
preopen preopen_;
+ preskip preskip_;
small_vector<pair<dir_iterator, dir_path>, 1> iters_;
};
@@ -2616,13 +2899,15 @@ namespace butl
bool recursive,
bool self,
bool follow_symlinks,
- preopen po) const
+ preopen po,
+ preskip ps) const
{
return iterator_type (start_ / p,
recursive,
self,
follow_symlinks,
- move (po));
+ move (po),
+ move (ps));
}
};
@@ -2631,10 +2916,11 @@ namespace butl
const path& pattern,
const function<bool (path&&, const string& pattern, bool interm)>& func,
const dir_path& start,
- path_match_flags flags)
+ path_match_flags flags,
+ const function<bool (const dir_entry&)>& dangling_func)
{
real_filesystem fs (pattern.relative () ? start : empty_dir);
- search (pattern, dir_path (), flags, func, fs);
+ search (pattern, dir_path (), flags, func, dangling_func, fs);
}
// Search path in the directory tree represented by a path.
@@ -2792,7 +3078,8 @@ namespace butl
bool recursive,
bool self,
bool /*follow_symlinks*/,
- preopen po)
+ preopen po,
+ preskip)
{
// If path and sub-path are non-empty, and both are absolute or relative,
// then no extra effort is required (prior to checking if one is a
@@ -2851,6 +3138,6 @@ namespace butl
path_match_flags flags)
{
path_filesystem fs (start, entry);
- search (pattern, dir_path (), flags, func, fs);
+ search (pattern, dir_path (), flags, func, nullptr /* dangle_func */, fs);
}
}
diff --git a/libbutl/filesystem.mxx b/libbutl/filesystem.hxx
index eb03ab2..0f5fb0b 100644
--- a/libbutl/filesystem.mxx
+++ b/libbutl/filesystem.hxx
@@ -1,9 +1,7 @@
-// file : libbutl/filesystem.mxx -*- C++ -*-
+// file : libbutl/filesystem.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
#include <errno.h> // E*
@@ -22,7 +20,6 @@
using mode_t = int;
#endif
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <cstddef> // ptrdiff_t
#include <cstdint> // uint16_t, etc
@@ -30,36 +27,41 @@
#include <iterator> // input_iterator_tag
#include <functional>
-#include <chrono> //@@ MOD needed by timestamp module (no re-export).
-#endif
+#include <libbutl/path.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/timestamp.hxx>
+#include <libbutl/path-pattern.hxx> // path_match_flags
-// Other includes.
-#ifdef __cpp_modules_ts
-export module butl.filesystem;
+#include <libbutl/export.hxx>
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
+namespace butl
+{
+ // Path permissions.
+ //
+ enum class permissions: std::uint16_t
+ {
+ // Note: matching POSIX values.
+ //
+ xo = 0001,
+ wo = 0002,
+ ro = 0004,
-import butl.path;
-import butl.optional;
-import butl.timestamp;
-import butl.path_pattern; // path_match_flags
+ xg = 0010,
+ wg = 0020,
+ rg = 0040,
-import butl.utility; // operator<<(ostream,exception), throw_generic_error()
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/timestamp.mxx>
-#include <libbutl/path-pattern.mxx>
+ xu = 0100,
+ wu = 0200,
+ ru = 0400,
-#include <libbutl/utility.mxx>
-#endif
+ none = 0
+ };
-#include <libbutl/export.hxx>
+ inline permissions operator& (permissions, permissions);
+ inline permissions operator| (permissions, permissions);
+ inline permissions operator&= (permissions&, permissions);
+ inline permissions operator|= (permissions&, permissions);
-LIBBUTL_MODEXPORT namespace butl
-{
// Return true if the path is to an existing regular file. Note that by
// default this function follows symlinks. Underlying OS errors are reported
// by throwing std::system_error, unless ignore_error is true (in which case
@@ -239,8 +241,8 @@ LIBBUTL_MODEXPORT namespace butl
// Movable-only type. Move-assignment cancels the lhs object.
//
- auto_rm (auto_rm&&);
- auto_rm& operator= (auto_rm&&);
+ auto_rm (auto_rm&&) noexcept;
+ auto_rm& operator= (auto_rm&&) noexcept;
auto_rm (const auto_rm&) = delete;
auto_rm& operator= (const auto_rm&) = delete;
@@ -405,11 +407,13 @@ LIBBUTL_MODEXPORT namespace butl
inline cpflags operator&= (cpflags&, cpflags);
inline cpflags operator|= (cpflags&, cpflags);
- // Copy a regular file, including its permissions, and optionally timestamps.
- // Throw std::system_error on failure. Fail if the destination file exists
- // and the overwrite_content flag is not set. Leave permissions of an
- // existing destination file intact unless the overwrite_permissions flag is
- // set. Delete incomplete copies before throwing.
+ // Copy a regular file, including its permissions (unless custom permissions
+ // are specified), and optionally timestamps. Throw std::system_error on
+ // failure. Fail if the destination file exists and the overwrite_content
+ // flag is not set. Leave permissions of an existing destination file intact
+ // (including if custom permissions are specified) unless the
+ // overwrite_permissions flag is set. Delete incomplete copies before
+ // throwing.
//
// Note that in case of overwriting, the existing destination file gets
// truncated (not deleted) prior to being overwritten. As a side-effect,
@@ -421,7 +425,10 @@ LIBBUTL_MODEXPORT namespace butl
// fail.
//
LIBBUTL_SYMEXPORT void
- cpfile (const path& from, const path& to, cpflags = cpflags::none);
+ cpfile (const path& from,
+ const path& to,
+ cpflags = cpflags::none,
+ optional<permissions> perm = nullopt);
// Copy a regular file into (inside) an existing directory.
//
@@ -629,32 +636,6 @@ LIBBUTL_MODEXPORT namespace butl
return dir_atime (p.string ().c_str (), t);
}
- // Path permissions.
- //
- enum class permissions: std::uint16_t
- {
- // Note: matching POSIX values.
- //
- xo = 0001,
- wo = 0002,
- ro = 0004,
-
- xg = 0010,
- wg = 0020,
- rg = 0040,
-
- xu = 0100,
- wu = 0200,
- ru = 0400,
-
- none = 0
- };
-
- inline permissions operator& (permissions, permissions);
- inline permissions operator| (permissions, permissions);
- inline permissions operator&= (permissions&, permissions);
- inline permissions operator|= (permissions&, permissions);
-
// Get path permissions. Throw std::system_error on failure. Note that this
// function resolves symlinks.
//
@@ -676,12 +657,45 @@ LIBBUTL_MODEXPORT namespace butl
// Symlink target type in case of the symlink, ltype() otherwise.
//
+ // If type() returns entry_type::unknown then this entry is inaccessible
+ // (ltype() also returns entry_type::unknown) or is a dangling symlink
+ // (ltype() returns entry_type::symlink). Used with the detect_dangling
+ // dir_iterator mode. Note that on POSIX ltype() can never return unknown
+ // (because it is part of the directory iteration result).
+ //
entry_type
type () const;
entry_type
ltype () const;
+ // Modification and access times of the filesystem entry if it is not a
+ // symlink and of the symlink target otherwise.
+ //
+ // These are provided as an optimization if they can be obtained as a
+ // byproduct of work that is already being done anyway (iteration itself,
+ // calls to [l]type(), etc). If (not yet) available, timestamp_unknown is
+ // returned.
+ //
+ // Specifically:
+ //
+ // - On Windows mtime is always set by dir_iterator for entries other than
+ // reparse points.
+ //
+ // - On all platforms mtime and atime are always set for symlink targets
+ // by dir_iterator in the {detect,ignore}_dangling modes.
+ //
+ // - On all platforms mtime and atime can potentially be set by [l]type()
+ // if the stat() call is required to retrieve the type information (the
+ // native directory entry iterating API doesn't provide it, the type of
+ // the symlink target is queried, etc).
+ //
+ timestamp
+ mtime () const {return mtime_;}
+
+ timestamp
+ atime () const {return atime_;}
+
// Entry path (excluding the base). To get the full path, do
// base () / path ().
//
@@ -692,8 +706,17 @@ LIBBUTL_MODEXPORT namespace butl
base () const {return b_;}
dir_entry () = default;
- dir_entry (entry_type t, path_type p, dir_path b)
- : t_ (t), p_ (std::move (p)), b_ (std::move (b)) {}
+
+ dir_entry (entry_type t,
+ path_type p,
+ dir_path b,
+ timestamp mt = timestamp_unknown,
+ timestamp at = timestamp_unknown)
+ : t_ (t),
+ mtime_ (mt),
+ atime_ (at),
+ p_ (std::move (p)),
+ b_ (std::move (b)) {}
private:
entry_type
@@ -702,8 +725,14 @@ LIBBUTL_MODEXPORT namespace butl
private:
friend class dir_iterator;
- mutable entry_type t_ = entry_type::unknown; // Lazy evaluation.
- mutable entry_type lt_ = entry_type::unknown; // Lazy evaluation.
+ // Note: lazy evaluation.
+ //
+ mutable optional<entry_type> t_; // Entry type.
+ mutable optional<entry_type> lt_; // Symlink target type.
+
+ mutable timestamp mtime_ = timestamp_unknown;
+ mutable timestamp atime_ = timestamp_unknown;
+
path_type p_;
dir_path b_;
};
@@ -720,12 +749,15 @@ LIBBUTL_MODEXPORT namespace butl
~dir_iterator ();
dir_iterator () = default;
- // If it is requested to ignore dangling symlinks, then the increment
- // operator will skip symlinks that refer to non-existing or inaccessible
- // targets. That implies that it will always try to stat() symlinks.
+ // If the mode is either ignore_dangling or detect_dangling, then stat()
+ // the entry and either ignore inaccessible/dangling entry or return it
+ // with the corresponding dir_entry type set to unknown (see dir_entry
+ // type()/ltype() for details).
//
+ enum mode {no_follow, detect_dangling, ignore_dangling};
+
explicit
- dir_iterator (const dir_path&, bool ignore_dangling);
+ dir_iterator (const dir_path&, mode);
dir_iterator (const dir_iterator&) = delete;
dir_iterator& operator= (const dir_iterator&) = delete;
@@ -751,10 +783,10 @@ LIBBUTL_MODEXPORT namespace butl
#ifndef _WIN32
DIR* h_ = nullptr;
#else
- intptr_t h_ = -1;
+ intptr_t h_ = -1; // INVALID_HANDLE_VALUE
#endif
- bool ignore_dangling_ = false;
+ mode mode_ = no_follow;
};
// Range-based for loop support.
@@ -780,7 +812,7 @@ LIBBUTL_MODEXPORT namespace butl
// Wildcard pattern search (aka glob).
//
- // For details on the wildcard patterns see <libbutl/path-pattern.mxx>
+ // For details on the wildcard patterns see <libbutl/path-pattern.hxx>
// Search for paths matching the pattern calling the specified function for
// each matching path (see below for details).
@@ -845,9 +877,20 @@ LIBBUTL_MODEXPORT namespace butl
// (a/b/, b*/, true)
// (a/b/c/, c*/, false)
//
- // Note that recursive iterating through directories currently goes
- // depth-first which make sense for the cleanup use cases. In future we may
- // want to make it controllable.
+ // Note that recursive iterating through directories currently goes depth-
+ // first which make sense for the cleanup use cases. In the future we may
+ // want to make this controllable.
+ //
+ // If the match flags contain follow_symlinks, then call the dangling
+ // callback function for inaccessible/dangling entries if specified, and
+ // throw appropriate std::system_error otherwise. If the callback function
+ // returns true, then inaccessible/dangling entry is ignored. Otherwise,
+ // the entire search is stopped.
+ //
+ // Note also that if pattern is not simple (that is, contains directory
+ // components), then some symlinks (those that are matched against the
+ // directory components) may still be followed and thus the dangling
+ // function called.
//
LIBBUTL_SYMEXPORT void
path_search (const path& pattern,
@@ -855,7 +898,8 @@ LIBBUTL_MODEXPORT namespace butl
const std::string& pattern,
bool interm)>&,
const dir_path& start = dir_path (),
- path_match_flags = path_match_flags::follow_symlinks);
+ path_match_flags = path_match_flags::follow_symlinks,
+ const std::function<bool (const dir_entry&)>& dangling = nullptr);
// Same as above, but behaves as if the directory tree being searched
// through contains only the specified entry. The start directory is used if
diff --git a/libbutl/filesystem.ixx b/libbutl/filesystem.ixx
index 8cd8456..b3f9224 100644
--- a/libbutl/filesystem.ixx
+++ b/libbutl/filesystem.ixx
@@ -1,6 +1,9 @@
// file : libbutl/filesystem.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
+#include <libbutl/utility.hxx> // operator<<(ostream,exception),
+ // throw_generic_error()
+
namespace butl
{
inline bool
@@ -8,7 +11,7 @@ namespace butl
{
// @@ Could 0 size be a valid and faster way?
//
- return dir_iterator (d, false /* ignore_dangling */) == dir_iterator ();
+ return dir_iterator (d, dir_iterator::no_follow) == dir_iterator ();
}
inline bool
@@ -70,7 +73,7 @@ namespace butl
//
template <typename P>
inline auto_rm<P>::
- auto_rm (auto_rm&& x)
+ auto_rm (auto_rm&& x) noexcept
: path (std::move (x.path)), active (x.active)
{
x.active = false;
@@ -78,7 +81,7 @@ namespace butl
template <typename P>
inline auto_rm<P>& auto_rm<P>::
- operator= (auto_rm&& x)
+ operator= (auto_rm&& x) noexcept
{
if (this != &x)
{
@@ -134,54 +137,28 @@ namespace butl
static_cast<std::uint16_t> (y));
}
- // path_match_flags
- //
- inline path_match_flags operator& (path_match_flags x, path_match_flags y)
- {
- return x &= y;
- }
-
- inline path_match_flags operator| (path_match_flags x, path_match_flags y)
- {
- return x |= y;
- }
-
- inline path_match_flags operator&= (path_match_flags& x, path_match_flags y)
- {
- return x = static_cast<path_match_flags> (
- static_cast<std::uint16_t> (x) &
- static_cast<std::uint16_t> (y));
- }
-
- inline path_match_flags operator|= (path_match_flags& x, path_match_flags y)
- {
- return x = static_cast<path_match_flags> (
- static_cast<std::uint16_t> (x) |
- static_cast<std::uint16_t> (y));
- }
-
// dir_entry
//
inline entry_type dir_entry::
ltype () const
{
- return t_ != entry_type::unknown ? t_ : (t_ = type (false));
+ return t_ ? *t_ : *(t_ = type (false /* follow_symlinks */));
}
inline entry_type dir_entry::
type () const
{
entry_type t (ltype ());
- return t != entry_type::symlink
- ? t
- : lt_ != entry_type::unknown ? lt_ : (lt_ = type (true));
+ return t != entry_type::symlink ? t :
+ lt_ ? *lt_ :
+ *(lt_ = type (true /* follow_symlinks */));
}
// dir_iterator
//
inline dir_iterator::
dir_iterator (dir_iterator&& x) noexcept
- : e_ (std::move (x.e_)), h_ (x.h_), ignore_dangling_ (x.ignore_dangling_)
+ : e_ (std::move (x.e_)), h_ (x.h_), mode_ (x.mode_)
{
#ifndef _WIN32
x.h_ = nullptr;
diff --git a/libbutl/ft/lang.hxx b/libbutl/ft/lang.hxx
index 567f5a4..82971d2 100644
--- a/libbutl/ft/lang.hxx
+++ b/libbutl/ft/lang.hxx
@@ -7,9 +7,14 @@
// __cpp_thread_local (extension)
//
// If this macro is undefined then one may choose to fallback to __thread.
-// Note, however, that it only for values that do not require dynamic
+// Note, however, that it only works for values that do not require dynamic
// (runtime) initialization.
//
+// Note that thread_local with dynamic allocation/destruction appears to be
+// broken when we use our own implementation of C++14 threads on MinGW. So
+// we restrict ourselves to __thread which appears to be functioning, at
+// least in the POSIX threads GCC configuration.
+//
#ifndef __cpp_thread_local
//
// Apparently Apple's Clang "temporarily disabled" C++11 thread_local until
@@ -20,7 +25,7 @@
# if __apple_build_version__ >= 8000000
# define __cpp_thread_local 201103
# endif
-# else
+# elif !defined(LIBBUTL_MINGW_STDTHREAD)
# define __cpp_thread_local 201103
# endif
#endif
diff --git a/libbutl/git.cxx b/libbutl/git.cxx
index b9dd9bc..f37e16a 100644
--- a/libbutl/git.cxx
+++ b/libbutl/git.cxx
@@ -1,43 +1,11 @@
// file : libbutl/git.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/git.mxx>
-#endif
+#include <libbutl/git.hxx>
-// C includes.
-
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-
-#include <cstddef> // size_t
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.git;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.optional;
-import butl.semantic_version
-#endif
-
-import butl.utility; // digit()
-import butl.filesystem; // entry_exists()
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/filesystem.mxx>
-#include <libbutl/semantic-version.mxx>
-#endif
+#include <libbutl/optional.hxx>
+#include <libbutl/filesystem.hxx> // entry_exists()
+#include <libbutl/semantic-version.hxx>
using namespace std;
@@ -68,7 +36,9 @@ namespace butl
// MinGit: git version 2.16.1.windows.1
//
if (s.compare (0, 12, "git version ") == 0)
- return parse_semantic_version (s, 12, "" /* build_separators */);
+ return parse_semantic_version (s, 12,
+ semantic_version::allow_build,
+ "" /* build_separators */);
return nullopt;
}
diff --git a/libbutl/git.mxx b/libbutl/git.hxx
index 3f003be..add721e 100644
--- a/libbutl/git.mxx
+++ b/libbutl/git.hxx
@@ -1,35 +1,17 @@
-// file : libbutl/git.mxx -*- C++ -*-
+// file : libbutl/git.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-export module butl.git;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.optional;
-import butl.semantic_version;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/semantic-version.mxx>
-#endif
+#include <libbutl/path.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/semantic-version.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Return true if the specified directory is a git repository root (contains
// the .git filesystem entry).
diff --git a/libbutl/host-os-release.cxx b/libbutl/host-os-release.cxx
new file mode 100644
index 0000000..f13f62c
--- /dev/null
+++ b/libbutl/host-os-release.cxx
@@ -0,0 +1,323 @@
+// file : libbutl/host-os-release.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <libbutl/host-os-release.hxx>
+
+#include <sstream>
+#include <stdexcept> // runtime_error
+
+#include <libbutl/path.hxx>
+#include <libbutl/path-io.hxx>
+#include <libbutl/utility.hxx>
+#include <libbutl/process.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/filesystem.hxx> // file_exists()
+#include <libbutl/string-parser.hxx> // parse_quoted()
+
+#ifdef _WIN32
+# include <libbutl/win32-utility.hxx>
+#endif
+
+using namespace std;
+
+namespace butl
+{
+ // Note: exported for access from the test.
+ //
+ LIBBUTL_SYMEXPORT os_release
+ host_os_release_linux (path f = {})
+ {
+ os_release r;
+
+ // According to os-release(5), we should use /etc/os-release and fallback
+ // to /usr/lib/os-release if the former does not exist. It also lists the
+ // fallback values for individual variables, in case some are not present.
+ //
+ auto exists = [] (const path& f)
+ {
+ try
+ {
+ return file_exists (f);
+ }
+ catch (const system_error& e)
+ {
+ ostringstream os;
+ os << "unable to stat path " << f << ": " << e;
+ throw runtime_error (os.str ());
+ }
+ };
+
+ if (!f.empty ()
+ ? exists (f)
+ : (exists (f = path ("/etc/os-release")) ||
+ exists (f = path ("/usr/lib/os-release"))))
+ {
+ try
+ {
+ ifdstream ifs (f, ifdstream::badbit);
+
+ string l;
+ for (uint64_t ln (1); !eof (getline (ifs, l)); ++ln)
+ {
+ trim (l);
+
+ // Skip blanks lines and comments.
+ //
+ if (l.empty () || l[0] == '#')
+ continue;
+
+ // The variable assignments are in the "shell style" and so can be
+ // quoted/escaped. For now we only handle quoting, which is what all
+ // the instances seen in the wild seems to use.
+ //
+ size_t p (l.find ('='));
+ if (p == string::npos)
+ continue;
+
+ string n (l, 0, p);
+ l.erase (0, p + 1);
+
+ using string_parser::parse_quoted;
+ using string_parser::invalid_string;
+
+ try
+ {
+ if (n == "ID_LIKE")
+ {
+ r.like_ids.clear ();
+
+ vector<string> vs (parse_quoted (l, true /* unquote */));
+ for (const string& v: vs)
+ {
+ for (size_t b (0), e (0); next_word (v, b, e); )
+ {
+ r.like_ids.push_back (string (v, b, e - b));
+ }
+ }
+ }
+ else if (string* p = (n == "ID" ? &r.name_id :
+ n == "VERSION_ID" ? &r.version_id :
+ n == "VARIANT_ID" ? &r.variant_id :
+ n == "NAME" ? &r.name :
+ n == "VERSION_CODENAME" ? &r.version_codename :
+ n == "VARIANT" ? &r.variant :
+ nullptr))
+ {
+ vector<string> vs (parse_quoted (l, true /* unquote */));
+ switch (vs.size ())
+ {
+ case 0: *p = ""; break;
+ case 1: *p = move (vs.front ()); break;
+ default: throw invalid_string (0, "multiple values");
+ }
+ }
+ }
+ catch (const invalid_string& e)
+ {
+ ostringstream os;
+ os << "invalid " << n << " value in " << f << ':' << ln << ": "
+ << e;
+ throw runtime_error (os.str ());
+ }
+ }
+
+ ifs.close ();
+ }
+ catch (const ios::failure& e)
+ {
+ ostringstream os;
+ os << "unable to read from " << f << ": " << e;
+ throw runtime_error (os.str ());
+ }
+ }
+
+ // Assign fallback values.
+ //
+ if (r.name_id.empty ()) r.name_id = "linux";
+ if (r.name.empty ()) r.name = "Linux";
+
+ return r;
+ }
+
+ static os_release
+ host_os_release_macos ()
+ {
+ // Run sw_vers -productVersion to get Mac OS version.
+ //
+ try
+ {
+ process pr;
+ try
+ {
+ fdpipe pipe (fdopen_pipe ());
+
+ pr = process_start (0, pipe, 2, "sw_vers", "-productVersion");
+
+ pipe.out.close ();
+ ifdstream is (move (pipe.in), fdstream_mode::skip, ifdstream::badbit);
+
+ // The output should be one line containing the version.
+ //
+ optional<string> v;
+ for (string l; !eof (getline (is, l)); )
+ {
+ if (l.empty () || v)
+ {
+ v = nullopt;
+ break;
+ }
+
+ v = move (l);
+ }
+
+ is.close (); // Detect errors.
+
+ if (pr.wait ())
+ {
+ if (!v)
+ throw runtime_error ("unexpected sw_vers -productVersion output");
+
+ return os_release {"macos", {}, move (*v), "", "Mac OS", "", ""};
+ }
+
+ }
+ catch (const ios::failure& e)
+ {
+ if (pr.wait ())
+ {
+ ostringstream os;
+ os << "error reading sw_vers output: " << e;
+ throw runtime_error (os.str ());
+ }
+
+ // Fall through.
+ }
+
+ // We should only get here if the child exited with an error status.
+ //
+ assert (!pr.wait ());
+ throw runtime_error ("process sw_vers exited with non-zero code");
+ }
+ catch (const process_error& e)
+ {
+ ostringstream os;
+ os << "unable to execute sw_vers: " << e;
+ throw runtime_error (os.str ());
+ }
+ }
+
+ static os_release
+ host_os_release_windows ()
+ {
+#ifdef _WIN32
+ // The straightforward way to get the version would be the GetVersionEx()
+ // Win32 function. However, if the application is built with a certain
+ // assembly manifest, this function will return the version the
+ // application was built for rather than what's actually running.
+ //
+ // The other plausible options are to call the `ver` program and parse it
+ // output (of questionable regularity) or to call RtlGetVersion(). The
+ // latter combined with GetProcAddress() seems to be a widely-used
+ // approach, so we are going with that (seeing that we employ a similar
+ // technique in quite a few places).
+ //
+ HMODULE nh (GetModuleHandle ("ntdll.dll"));
+ if (nh == nullptr)
+ throw runtime_error ("unable to get handle to ntdll.dll");
+
+ using RtlGetVersion = LONG /*NTSTATUS*/ (WINAPI*)(PRTL_OSVERSIONINFOW);
+
+ RtlGetVersion gv (
+ function_cast<RtlGetVersion> (
+ GetProcAddress (nh, "RtlGetVersion")));
+
+ // RtlGetVersion() is available from Windows 2000 which is way before
+ // anything we might possibly care about (e.g., XP or 7).
+ //
+ if (gv == nullptr)
+ throw runtime_error ("unable to get address of RtlGetVersion()");
+
+ RTL_OSVERSIONINFOW vi;
+ vi.dwOSVersionInfoSize = sizeof (vi);
+ gv (&vi); // Always succeeds, according to documentation.
+
+ // Ok, the real mess starts here. Here is how the commonly known Windows
+ // versions correspond to the major/minor/build numbers and how we will
+ // map them (note that there are also Server versions in the mix; see the
+ // OSVERSIONINFOEXW struct documentation for the complete picture):
+ //
+ // major minor build mapped
+ // Windows 11 10 0 >=22000 11
+ // Windows 10 10 0 <22000 10
+ // Windows 8.1 6 3 8.1
+ // Windows 8 6 2 8
+ // Windows 7 6 1 7
+ // Windows Vista 6 0 6
+ // Windows XP Pro/64-bit 5 2 5.2
+ // Windows XP 5 1 5.1
+ // Windows 2000 5 0 5
+ //
+ // Based on this it's probably not wise to try to map any future versions
+ // automatically.
+ //
+ string v;
+ if (vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0)
+ {
+ v = vi.dwBuildNumber >= 22000 ? "11" : "10";
+ }
+ else if (vi.dwMajorVersion == 6 && vi.dwMinorVersion == 3) v = "8.1";
+ else if (vi.dwMajorVersion == 6 && vi.dwMinorVersion == 2) v = "8";
+ else if (vi.dwMajorVersion == 6 && vi.dwMinorVersion == 1) v = "7";
+ else if (vi.dwMajorVersion == 6 && vi.dwMinorVersion == 0) v = "6";
+ else if (vi.dwMajorVersion == 5 && vi.dwMinorVersion == 2) v = "5.2";
+ else if (vi.dwMajorVersion == 5 && vi.dwMinorVersion == 1) v = "5.1";
+ else if (vi.dwMajorVersion == 5 && vi.dwMinorVersion == 0) v = "5";
+ else throw ("unknown windows version " +
+ std::to_string (vi.dwMajorVersion) + '.' +
+ std::to_string (vi.dwMinorVersion) + '.' +
+ std::to_string (vi.dwBuildNumber));
+
+ return os_release {"windows", {}, move (v), "", "Windows", "", ""};
+#else
+ throw runtime_error ("unexpected host operating system");
+#endif
+ }
+
+ optional<os_release>
+ host_os_release (const target_triplet& h)
+ {
+ const string& c (h.class_);
+ const string& s (h.system);
+
+ if (c == "linux")
+ return host_os_release_linux ();
+
+ if (c == "macos")
+ return host_os_release_macos ();
+
+ if (c == "windows")
+ return host_os_release_windows ();
+
+ if (c == "bsd")
+ {
+ // @@ TODO: ideally we would want to run uname and obtain the actual
+ // version we are runnig on rather than what we've been built for.
+ // (Think also how this will affect tests).
+ //
+ if (s == "freebsd")
+ return os_release {"freebsd", {}, h.version, "", "FreeBSD", "", ""};
+
+ if (s == "netbsd")
+ return os_release {"netbsd", {}, h.version, "", "NetBSD", "", ""};
+
+ if (s == "openbsd")
+ return os_release {"openbsd", {}, h.version, "", "OpenBSD", "", ""};
+
+ // Assume some other BSD.
+ //
+ return os_release {s, {}, h.version, "", s, "", ""};
+ }
+
+ return nullopt;
+ }
+}
diff --git a/libbutl/host-os-release.hxx b/libbutl/host-os-release.hxx
new file mode 100644
index 0000000..058afdc
--- /dev/null
+++ b/libbutl/host-os-release.hxx
@@ -0,0 +1,86 @@
+// file : libbutl/host-os-release.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <libbutl/optional.hxx>
+#include <libbutl/target-triplet.hxx>
+
+#include <libbutl/export.hxx>
+
+namespace butl
+{
+ // Information extracted from /etc/os-release on Linux. See os-release(5)
+ // for background. For other platforms we derive the equivalent information
+ // from other sources. Some examples:
+ //
+ // {"debian", {}, "10", "",
+ // "Debian GNU/Linux", "buster", ""}
+ //
+ // {"fedora", {}, "35", "workstation",
+ // "Fedora Linux", "", "Workstation Edition"}
+ //
+ // {"ubuntu", {"debian"}, "20.04", "",
+ // "Ubuntu", "focal", ""}
+ //
+ // {"macos", {}, "12.5", "",
+ // "Mac OS", "", ""}
+ //
+ // {"freebsd", {}, "13.1", "",
+ // "FreeBSD", "", ""}
+ //
+ // {"windows", {}, "10", "",
+ // "Windows", "", ""}
+ //
+ // Note that for Mac OS, the version is the Mac OS version (as printed by
+ // sw_vers) rather than Darwin version (as printed by uname).
+ //
+ // For Windows we currently do not distinguish the Server edition and the
+ // version mapping is as follows:
+ //
+ // Windows 11 11
+ // Windows 10 10
+ // Windows 8.1 8.1
+ // Windows 8 8
+ // Windows 7 7
+ // Windows Vista 6
+ // Windows XP Pro/64-bit 5.2
+ // Windows XP 5.1
+ // Windows 2000 5
+ //
+ // Note that version_id may be empty, for example, on Debian testing:
+ //
+ // {"debian", {}, "", "",
+ // "Debian GNU/Linux", "", ""}
+ //
+ // Note also that we don't extract PRETTY_NAME because its content is
+ // unpredictable. For example, it may include variant, as in "Fedora Linux
+ // 35 (Workstation Edition)". Instead, construct it from the individual
+ // components as appropriate, normally "$name $version ($version_codename)".
+ //
+ struct os_release
+ {
+ std::string name_id; // ID
+ std::vector<std::string> like_ids; // ID_LIKE
+ std::string version_id; // VERSION_ID
+ std::string variant_id; // VARIANT_ID
+
+ std::string name; // NAME
+ std::string version_codename; // VERSION_CODENAME
+ std::string variant; // VARIANT
+ };
+
+ // Return the release information for the specified host or nullopt if the
+ // specific host is unknown/unsupported. Throw std::runtime_error if
+ // anything goes wrong.
+ //
+ // Note that "host" here implies that we may be running programs, reading
+ // files, examining environment variables, etc., of the machine we are
+ // running on.
+ //
+ LIBBUTL_SYMEXPORT optional<os_release>
+ host_os_release (const target_triplet& host);
+}
diff --git a/libbutl/json/event.hxx b/libbutl/json/event.hxx
new file mode 100644
index 0000000..77185cc
--- /dev/null
+++ b/libbutl/json/event.hxx
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+namespace butl
+{
+ namespace json
+ {
+ // Parsing/serialization event.
+ //
+ enum class event: std::uint8_t
+ {
+ begin_object = 1,
+ end_object,
+ begin_array,
+ end_array,
+ name,
+ string,
+ number,
+ boolean,
+ null
+ };
+
+ constexpr std::size_t event_count = 9;
+ }
+}
diff --git a/libbutl/json/parser.cxx b/libbutl/json/parser.cxx
new file mode 100644
index 0000000..8ef7422
--- /dev/null
+++ b/libbutl/json/parser.cxx
@@ -0,0 +1,645 @@
+#define PDJSON_SYMEXPORT static // See below.
+
+#include <libbutl/json/parser.hxx>
+
+#include <istream>
+
+// There is an issue (segfault) with using std::current_exception() and
+// std::rethrow_exception() with older versions of libc++ on Linux. While the
+// exact root cause hasn't been determined, the suspicion is that something
+// gets messed up if we "smuggle" std::exception_ptr through extern "C" call
+// frames (we cannot even destroy such an exception without a segfault). We
+// also could not determine in which version exactly this has been fixed but
+// we know that libc++ 6.0.0 doesn't appear to have this issue (though we are
+// not entirely sure the issue is (only) in libc++; libgcc_s could also be
+// involved).
+//
+// The workaround is to just catch (and note) the exception and then throw a
+// new instance of generic std::istream::failure. In order not to drag the
+// below test into the header, we wrap exception_ptr with optional<> and use
+// NULL to indicate the presence of the exception when the workaround is
+// required.
+//
+// Note that if/when we drop this workaround, we should also get rid of
+// optional<> in stream::exception member.
+//
+#undef LIBBUTL_JSON_NO_EXCEPTION_PTR
+
+#if defined (__linux__) && defined(__clang__)
+# if __has_include(<__config>)
+# include <__config> // _LIBCPP_VERSION
+# if _LIBCPP_VERSION < 6000
+# define LIBBUTL_JSON_NO_EXCEPTION_PTR 1
+# endif
+# endif
+#endif
+
+namespace butl
+{
+ namespace json
+ {
+ using namespace std;
+
+ parser::
+ ~parser ()
+ {
+ json_close (impl_);
+ }
+
+ static int
+ stream_get (void* x)
+ {
+ auto& s (*static_cast<parser::stream*> (x));
+
+ // In the multi-value mode reading of whitespaces/separators is split
+ // between our code and pdjson's. As a result, these functions may end
+ // up being called more than once after EOF is reached. Which is
+ // something iostream does not handle gracefully.
+ //
+ if (!s.is->eof ())
+ {
+ try
+ {
+ // We first peek not to trip failbit on EOF.
+ //
+ if (s.is->peek () != istream::traits_type::eof ())
+ return static_cast<char> (s.is->get ());
+ }
+ catch (...)
+ {
+#ifndef LIBBUTL_JSON_NO_EXCEPTION_PTR
+ s.exception = current_exception ();
+#else
+ s.exception = nullptr;
+#endif
+ }
+ }
+
+ return EOF;
+ }
+
+ static int
+ stream_peek (void* x)
+ {
+ auto& s (*static_cast<parser::stream*> (x));
+
+ if (!s.is->eof ())
+ {
+ try
+ {
+ auto c (s.is->peek ());
+ if (c != istream::traits_type::eof ())
+ return static_cast<char> (c);
+ }
+ catch (...)
+ {
+#ifndef LIBBUTL_JSON_NO_EXCEPTION_PTR
+ s.exception = current_exception ();
+#else
+ s.exception = nullptr;
+#endif
+ }
+ }
+
+ return EOF;
+ }
+
+ // NOTE: watch out for exception safety (specifically, doing anything that
+ // might throw after opening the stream).
+ //
+ parser::
+ parser (istream& is, const char* n, bool mv, const char* sep) noexcept
+ : input_name (n),
+ stream_ {&is, nullopt},
+ multi_value_ (mv),
+ separators_ (sep),
+ raw_s_ (nullptr),
+ raw_n_ (0)
+ {
+ json_open_user (impl_, &stream_get, &stream_peek, &stream_);
+ json_set_streaming (impl_, multi_value_);
+ }
+
+ parser::
+ parser (const void* t,
+ size_t s,
+ const char* n,
+ bool mv,
+ const char* sep) noexcept
+ : input_name (n),
+ stream_ {nullptr, nullopt},
+ multi_value_ (mv),
+ separators_ (sep),
+ raw_s_ (nullptr),
+ raw_n_ (0)
+ {
+ json_open_buffer (impl_, t, s);
+ json_set_streaming (impl_, multi_value_);
+ }
+
+ optional<event> parser::
+ next ()
+ {
+ name_p_ = value_p_ = location_p_ = false;
+
+ // Note that for now we don't worry about the state of the parser if
+ // next_impl() throws assuming it is not going to be reused.
+ //
+ if (peeked_)
+ {
+ parsed_ = peeked_;
+ peeked_ = nullopt;
+ }
+ else
+ parsed_ = next_impl ();
+
+ return translate (*parsed_);
+ }
+
+ optional<event> parser::
+ peek ()
+ {
+ if (!peeked_)
+ {
+ if (parsed_)
+ {
+ cache_parsed_data ();
+ cache_parsed_location ();
+ }
+ peeked_ = next_impl ();
+ }
+ return translate (*peeked_);
+ }
+
+ static inline const char*
+ event_name (event e)
+ {
+ switch (e)
+ {
+ case event::begin_object: return "beginning of object";
+ case event::end_object: return "end of object";
+ case event::begin_array: return "beginning of array";
+ case event::end_array: return "end of array";
+ case event::name: return "member name";
+ case event::string: return "string value";
+ case event::number: return "numeric value";
+ case event::boolean: return "boolean value";
+ case event::null: return "null value";
+ }
+
+ return "";
+ }
+
+ bool parser::
+ next_expect (event p, optional<event> s)
+ {
+ optional<event> e (next ());
+ bool r;
+ if (e && ((r = *e == p) || (s && *e == *s)))
+ return r;
+
+ string d ("expected ");
+ d += event_name (p);
+
+ if (s)
+ {
+ d += " or ";
+ d += event_name (*s);
+ }
+
+ if (e)
+ {
+ d += " instead of ";
+ d += event_name (*e);
+ }
+
+ throw invalid_json_input (input_name != nullptr ? input_name : "",
+ line (),
+ column (),
+ position (),
+ move (d));
+ }
+
+ void parser::
+ next_expect_name (const char* n, bool su)
+ {
+ for (;;)
+ {
+ next_expect (event::name);
+
+ if (name () == n)
+ return;
+
+ if (!su)
+ break;
+
+ next_expect_value_skip ();
+ }
+
+ string d ("expected object member name '");
+ d += n;
+ d += "' instead of '";
+ d += name ();
+ d += '\'';
+
+ throw invalid_json_input (input_name != nullptr ? input_name : "",
+ line (),
+ column (),
+ position (),
+ move (d));
+ }
+
+ void parser::
+ next_expect_value_skip ()
+ {
+ optional<event> e (next ());
+
+ if (e)
+ {
+ switch (*e)
+ {
+ case event::begin_object:
+ case event::begin_array:
+ {
+ // Skip until matching end_object/array keeping track of nesting.
+ // We are going to rely on the fact that we should either get such
+ // an event or next() should throw.
+ //
+ event be (*e);
+ event ee (be == event::begin_object
+ ? event::end_object
+ : event::end_array);
+
+ for (size_t n (0);; )
+ {
+ event e (*next ());
+
+ if (e == ee)
+ {
+ if (n == 0)
+ break;
+
+ --n;
+ }
+ else if (e == be)
+ ++n;
+ }
+
+ return;
+ }
+ case event::string:
+ case event::number:
+ case event::boolean:
+ case event::null:
+ return;
+ case event::name:
+ case event::end_object:
+ case event::end_array:
+ break;
+ }
+ }
+
+ string d ("expected value");
+
+ if (e)
+ {
+ d += " instead of ";
+ d += event_name (*e);
+ }
+
+ throw invalid_json_input (input_name != nullptr ? input_name : "",
+ line (),
+ column (),
+ position (),
+ move (d));
+ }
+
+ std::uint64_t parser::
+ line () const noexcept
+ {
+ if (!location_p_)
+ {
+ if (!parsed_)
+ return 0;
+
+ assert (!peeked_);
+
+ return static_cast<uint64_t> (
+ json_get_lineno (const_cast<json_stream*> (impl_)));
+ }
+
+ return line_;
+ }
+
+ std::uint64_t parser::
+ column () const noexcept
+ {
+ if (!location_p_)
+ {
+ if (!parsed_)
+ return 0;
+
+ assert (!peeked_);
+
+ return static_cast<uint64_t> (
+ json_get_column (const_cast<json_stream*> (impl_)));
+ }
+
+ return column_;
+ }
+
+ std::uint64_t parser::
+ position () const noexcept
+ {
+ if (!location_p_)
+ {
+ if (!parsed_)
+ return 0;
+
+ assert (!peeked_);
+
+ return static_cast<uint64_t> (
+ json_get_position (const_cast<json_stream*> (impl_)));
+ }
+
+ return position_;
+ }
+
+ json_type parser::
+ next_impl ()
+ {
+ raw_s_ = nullptr;
+ raw_n_ = 0;
+ json_type e;
+
+ // Read characters between values skipping required separators and JSON
+ // whitespaces. Return whether a required separator was encountered as
+ // well as the first non-separator/whitespace character (which, if EOF,
+ // should trigger a check for input/output errors).
+ //
+ // Note that the returned non-separator will not have been extracted
+ // from the input (so position, column, etc. will still refer to its
+ // predecessor).
+ //
+ auto skip_separators = [this] () -> pair<bool, int>
+ {
+ bool r (separators_ == nullptr);
+
+ int c;
+ for (; (c = json_source_peek (impl_)) != EOF; json_source_get (impl_))
+ {
+ // User separator.
+ //
+ if (separators_ != nullptr && *separators_ != '\0')
+ {
+ if (strchr (separators_, c) != nullptr)
+ {
+ r = true;
+ continue;
+ }
+ }
+
+ // JSON separator.
+ //
+ if (json_isspace (c))
+ {
+ if (separators_ != nullptr && *separators_ == '\0')
+ r = true;
+
+ continue;
+ }
+
+ break;
+ }
+
+ return make_pair (r, c);
+ };
+
+ // In the multi-value mode skip any instances of required separators
+ // (and any other JSON whitespace) preceding the first JSON value.
+ //
+ if (multi_value_ && !parsed_ && !peeked_)
+ {
+ if (skip_separators ().second == EOF && stream_.is != nullptr)
+ {
+ if (stream_.exception) goto fail_rethrow;
+ if (stream_.is->fail ()) goto fail_stream;
+ }
+ }
+
+ e = json_next (impl_);
+
+ // First check for a pending input/output error.
+ //
+ if (stream_.is != nullptr)
+ {
+ if (stream_.exception) goto fail_rethrow;
+ if (stream_.is->fail ()) goto fail_stream;
+ }
+
+ // There are two ways to view separation between two values: as following
+ // the first value or as preceding the second value. And one aspect that
+ // is determined by this is whether a separation violation is a problem
+ // with the first value or with the second, which becomes important if
+ // the user bails out before parsing the second value.
+ //
+ // Consider these two unseparated value (yes, in JSON they are two
+ // values, leading zeros are not allowed in JSON numbers):
+ //
+ // 01
+ //
+ // If the user bails out after parsing 0 in a stream that should have
+ // been newline-delimited, they most likely would want to get an error
+ // since this is most definitely an invalid value rather than two
+ // values that are not properly separated. So in this light we handle
+ // separators at the end of the first value.
+ //
+ switch (e)
+ {
+ case JSON_DONE:
+ {
+ // Deal with the following value separators.
+ //
+ // Note that we must not do this for the second JSON_DONE (or the
+ // first one in case there are no values) that signals the end of
+ // input.
+ //
+ if (multi_value_ &&
+ (parsed_ || peeked_) &&
+ (peeked_ ? *peeked_ : *parsed_) != JSON_DONE)
+ {
+ auto p (skip_separators ());
+
+ if (p.second == EOF && stream_.is != nullptr)
+ {
+ if (stream_.exception) goto fail_rethrow;
+ if (stream_.is->fail ()) goto fail_stream;
+ }
+
+ // Note that we don't require separators after the last value.
+ //
+ if (!p.first && p.second != EOF)
+ {
+ json_source_get (impl_); // Consume to update column number.
+ goto fail_separation;
+ }
+
+ json_reset (impl_);
+ }
+ break;
+ }
+ case JSON_ERROR: goto fail_json;
+ case JSON_STRING:
+ case JSON_NUMBER:
+ raw_s_ = json_get_string (impl_, &raw_n_);
+ raw_n_--; // Includes terminating `\0`.
+ break;
+ case JSON_TRUE: raw_s_ = "true"; raw_n_ = 4; break;
+ case JSON_FALSE: raw_s_ = "false"; raw_n_ = 5; break;
+ case JSON_NULL: raw_s_ = "null"; raw_n_ = 4; break;
+ default: break;
+ }
+
+ return e;
+
+ fail_json:
+ throw invalid_json_input (
+ input_name != nullptr ? input_name : "",
+ static_cast<uint64_t> (json_get_lineno (impl_)),
+ static_cast<uint64_t> (json_get_column (impl_)),
+ static_cast<uint64_t> (json_get_position (impl_)),
+ json_get_error (impl_));
+
+ fail_separation:
+ throw invalid_json_input (
+ input_name != nullptr ? input_name : "",
+ static_cast<uint64_t> (json_get_lineno (impl_)),
+ static_cast<uint64_t> (json_get_column (impl_)),
+ static_cast<uint64_t> (json_get_position (impl_)),
+ "missing separator between JSON values");
+
+ fail_stream:
+ throw invalid_json_input (
+ input_name != nullptr ? input_name : "",
+ static_cast<uint64_t> (json_get_lineno (impl_)),
+ static_cast<uint64_t> (json_get_column (impl_)),
+ static_cast<uint64_t> (json_get_position (impl_)),
+ "unable to read JSON input text");
+
+ fail_rethrow:
+#ifndef LIBBUTL_JSON_NO_EXCEPTION_PTR
+ rethrow_exception (move (*stream_.exception));
+#else
+ throw istream::failure ("unable to read");
+#endif
+ }
+
+ optional<event> parser::
+ translate (json_type e) const noexcept
+ {
+ switch (e)
+ {
+ case JSON_DONE: return nullopt;
+ case JSON_OBJECT: return event::begin_object;
+ case JSON_OBJECT_END: return event::end_object;
+ case JSON_ARRAY: return event::begin_array;
+ case JSON_ARRAY_END: return event::end_array;
+ case JSON_STRING:
+ {
+ // This can be a value or, inside an object, a name from the
+ // name/value pair.
+ //
+ size_t n;
+ return json_get_context (const_cast<json_stream*> (impl_), &n) ==
+ JSON_OBJECT &&
+ n % 2 == 1
+ ? event::name
+ : event::string;
+ }
+ case JSON_NUMBER: return event::number;
+ case JSON_TRUE: return event::boolean;
+ case JSON_FALSE: return event::boolean;
+ case JSON_NULL: return event::null;
+ case JSON_ERROR: assert (false); // Should've been handled by caller.
+ }
+
+ return nullopt; // Should never reach.
+ }
+
+ void parser::
+ cache_parsed_data ()
+ {
+ name_p_ = value_p_ = false;
+ if (const optional<event> e = translate (*parsed_))
+ {
+ if (e == event::name)
+ {
+ name_.assign (raw_s_, raw_n_);
+ name_p_ = true;
+ }
+ else if (value_event (e))
+ {
+ value_.assign (raw_s_, raw_n_);
+ value_p_ = true;
+ }
+ }
+ }
+
+ void parser::
+ cache_parsed_location () noexcept
+ {
+ line_ = static_cast<uint64_t> (json_get_lineno (impl_));
+ column_ = static_cast<uint64_t> (json_get_column (impl_));
+ position_ = static_cast<uint64_t> (json_get_position (impl_));
+ location_p_ = true;
+ }
+
+ bool parser::
+ value_event (optional<event> e) noexcept
+ {
+ if (!e)
+ return false;
+
+ switch (*e)
+ {
+ case event::string:
+ case event::number:
+ case event::boolean:
+ case event::null:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ [[noreturn]] void parser::
+ throw_invalid_value (const char* type, const char* v, size_t n) const
+ {
+ string d (string ("invalid ") + type + " value: '");
+ d.append (v, n);
+ d += '\'';
+
+ throw invalid_json_input (input_name != nullptr ? input_name : "",
+ line (),
+ column (),
+ position (),
+ move (d));
+ }
+ } // namespace json
+} // namespace butl
+
+// Include the implementation into our translation unit (instead of compiling
+// it separately) to (hopefully) get function inlining without LTO.
+//
+// Let's keep it last since the implementation defines a couple of macros.
+//
+#if defined(__clang__) || defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wunused-function"
+#endif
+
+extern "C"
+{
+#define PDJSON_STACK_INC 16
+#define PDJSON_STACK_MAX 2048
+#include "pdjson.c"
+}
diff --git a/libbutl/json/parser.hxx b/libbutl/json/parser.hxx
new file mode 100644
index 0000000..95d9c4e
--- /dev/null
+++ b/libbutl/json/parser.hxx
@@ -0,0 +1,705 @@
+#pragma once
+
+#ifdef BUILD2_BOOTSTRAP
+# error JSON parser not available during bootstrap
+#endif
+
+#include <iosfwd>
+#include <string>
+#include <cstddef> // size_t
+#include <cstdint> // uint64_t
+#include <utility> // pair
+#include <exception> // exception_ptr
+#include <stdexcept> // invalid_argument
+
+#include <libbutl/optional.hxx> // butl::optional is std::optional or similar.
+
+#include <libbutl/json/event.hxx>
+
+#include <libbutl/json/pdjson.h> // Implementation details.
+
+#include <libbutl/export.hxx>
+
+namespace butl
+{
+ // Using the RFC8259 terminology: JSON (input) text, JSON value, object
+ // member.
+ //
+ namespace json
+ {
+ class invalid_json_input: public std::invalid_argument
+ {
+ public:
+ std::string name;
+ std::uint64_t line;
+ std::uint64_t column;
+ std::uint64_t position;
+
+ invalid_json_input (std::string name,
+ std::uint64_t line,
+ std::uint64_t column,
+ std::uint64_t position,
+ const std::string& description);
+
+ invalid_json_input (std::string name,
+ std::uint64_t line,
+ std::uint64_t column,
+ std::uint64_t position,
+ const char* description);
+ };
+
+ class LIBBUTL_SYMEXPORT parser
+ {
+ public:
+ const char* input_name;
+
+ // Construction.
+ //
+
+ // Parse JSON input text from std::istream.
+ //
+ // The name argument is used to identify the input being parsed. Note
+ // that the stream, name, and separators are kept as references so they
+ // must outlive the parser instance.
+ //
+ // If stream exceptions are enabled then the std::ios_base::failure
+ // exception is used to report input/output errors (badbit and failbit).
+ // Otherwise, those are reported as the invalid_json_input exception.
+ //
+ // If multi_value is true, enable the multi-value mode in which case the
+ // input stream may contain multiple JSON values (more precisely, zero
+ // or more). If false (the default), parsing will fail unless there is
+ // exactly one JSON value in the input stream.
+ //
+ // If multi_value is true, the separators argument specifies the
+ // required separator characters between JSON values. At least one of
+ // them must be present between every pair of JSON values (in addition
+ // to any number of JSON whitespaces). No separators are required after
+ // the last JSON value (but any found will be skipped).
+ //
+ // Specifically, if it is NULL, then no separation is required (that is,
+ // both `{...}{...}` and `{...} {...}` would be valid). If it is empty,
+ // then at least one JSON whitespace is required. And if it is non-
+ // empty, then at least one of its characters must be present (for
+ // example, "\n\t" would require at least one newline or TAB character
+ // between JSON values).
+ //
+ // Note that a separator need not be valid JSON whitespace: any
+ // character is acceptable (though it probably shouldn't be an object,
+ // array, or string delimiter and should not occur within a non-self-
+ // delimited top-level value, such as `true`, `false`, `null`, or a
+ // number). All instances of required separators before and after a
+ // value are skipped. Therefore JSON Text Sequences (RFC 7464; AKA
+ // Record Separator-delimited JSON), which requires the RS (0x1E)
+ // character before each value, can be handled as well.
+ //
+ parser (std::istream&,
+ const std::string& name,
+ bool multi_value = false,
+ const char* separators = nullptr) noexcept;
+
+ parser (std::istream&,
+ const char* name,
+ bool multi_value = false,
+ const char* separators = nullptr) noexcept;
+
+ parser (std::istream&,
+ std::string&&,
+ bool = false,
+ const char* = nullptr) = delete;
+
+ // Parse a memory buffer that contains the entire JSON input text.
+ //
+ // The name argument is used to identify the input being parsed. Note
+ // that the buffer, name, and separators are kept as references so they
+ // must outlive the parser instance.
+ //
+ parser (const void* text,
+ std::size_t size,
+ const std::string& name,
+ bool multi_value = false,
+ const char* separators = nullptr) noexcept;
+
+ parser (const void* text,
+ std::size_t size,
+ const char* name,
+ bool multi_value = false,
+ const char* separators = nullptr) noexcept;
+
+ parser (const void*,
+ std::size_t,
+ std::string&&,
+ bool = false,
+ const char* = nullptr) = delete;
+
+ // Similar to the above but parse a string.
+ //
+ parser (const std::string& text,
+ const std::string& name,
+ bool multi_value = false,
+ const char* separators = nullptr) noexcept;
+
+ parser (const std::string& text,
+ const char* name,
+ bool multi_value = false,
+ const char* separators = nullptr) noexcept;
+
+ parser (const std::string&,
+ std::string&&,
+ bool = false,
+ const char* = nullptr) = delete;
+
+ // Similar to the above but parse a C-string.
+ //
+ parser (const char* text,
+ const std::string& name,
+ bool multi_value = false,
+ const char* separators = nullptr) noexcept;
+
+ parser (const char* text,
+ const char* name,
+ bool multi_value = false,
+ const char* separators = nullptr) noexcept;
+
+ parser (const char*,
+ std::string&&,
+ bool = false,
+ const char* = nullptr) = delete;
+
+ parser (parser&&) = delete;
+ parser (const parser&) = delete;
+
+ parser& operator= (parser&&) = delete;
+ parser& operator= (const parser&) = delete;
+
+ // Event iteration.
+ //
+
+ // Return the next event or nullopt if end of input is reached.
+ //
+ // In the single-value parsing mode (default) the parsing code could
+ // look like this:
+ //
+ // while (optional<event> e = p.next ())
+ // {
+ // switch (*e)
+ // {
+ // // ...
+ // }
+ // }
+ //
+ // In the multi-value mode the parser additionally returns nullopt after
+ // every JSON value parsed (so there will be two nullopt's after the
+ // last JSON value, the second indicating the end of input).
+ //
+ // One way to perform multi-value parsing is with the help of the peek()
+ // function (see below):
+ //
+ // while (p.peek ())
+ // {
+ // while (optional<event> e = p.next ())
+ // {
+ // switch (*e)
+ // {
+ // //...
+ // }
+ // }
+ // }
+ //
+ // Note that while the single-value mode will always parse exactly one
+ // value, the multi-value mode will accept zero values in which case a
+ // single nullopt is returned.
+ //
+ optional<event>
+ next ();
+
+ // The range-based for loop support.
+ //
+ // In the single-value parsing mode (default) the parsing code could
+ // look like this:
+ //
+ // for (event e: p)
+ // {
+ // switch (e)
+ // {
+ // //...
+ // }
+ // }
+ //
+ // And in the multi-value mode (see next() for more information) like
+ // this:
+ //
+ // while (p.peek ())
+ // {
+ // for (event e: p)
+ // {
+ // switch (e)
+ // {
+ // //...
+ // }
+ // }
+ // }
+ //
+ // Note that generally, the iterator interface doesn't make much sense
+ // for the parser so for now we have an implementation that is just
+ // enough for the range-based for.
+ //
+ struct iterator;
+
+ iterator begin () {return iterator (this, next ());}
+ iterator end () {return iterator (nullptr, nullopt);}
+
+ // Return the next event without considering it parsed. In other words,
+ // after this call, any subsequent calls to peek() and the next call to
+ // next() (if any) will all return the same event.
+ //
+ // Note that the name, value, and line corresponding to the peeked event
+ // are not accessible with name(), value() and line(); these functions
+ // will still return values corresponding to the most recent call to
+ // next(). The peeked values, however, can be accessed in the raw form
+ // using data().
+ //
+ optional<event>
+ peek ();
+
+
+ // Event data access.
+ //
+
+ // Return the object member name.
+ //
+ const std::string&
+ name ();
+
+ // Any value (string, number, boolean, and null) can be retrieved as a
+ // string. Calling this function after any non-value events is illegal.
+ //
+ // Note that the value is returned as a non-const string reference and
+ // you are allowed to move the value out of it. However, this should not
+ // be done unnecessarily or in cases where the small string optimization
+ // is likely since the string's buffer is reused to store subsequent
+ // values.
+ //
+ std::string&
+ value ();
+
+ // Convert the value to an integer, floating point, or bool. Throw
+ // invalid_json_input if the conversion is impossible without a loss.
+ //
+ template <typename T>
+ T
+ value () const;
+
+ // Return the value or object member name in the raw form.
+ //
+ // Calling this function on non-value/name events is legal in which case
+ // NULL is returned. Note also that the returned data corresponds to the
+ // most recent event, whether peeked or parsed.
+ //
+ std::pair<const char*, std::size_t>
+ data () const {return std::make_pair (raw_s_, raw_n_);}
+
+
+ // Higher-level API suitable for parsing specific JSON vocabularies.
+ //
+ // The API summary:
+ //
+ // void next_expect (event);
+ // bool next_expect (event primary, event secondary);
+ //
+ // void next_expect_name (string name, bool skip_unknown = false);
+ //
+ // std::string& next_expect_string ();
+ // T next_expect_string<T> ();
+ // std::string& next_expect_number ();
+ // T next_expect_number<T> ();
+ // std::string& next_expect_boolean ();
+ // T next_expect_boolean<T>();
+ //
+ // std::string* next_expect_string_null ();
+ // optional<T> next_expect_string_null<T> ();
+ // std::string* next_expect_number_null ();
+ // optional<T> next_expect_number_null<T> ();
+ // std::string* next_expect_boolean_null ();
+ // optional<T> next_expect_boolean_null<T>();
+ //
+ // std::string& next_expect_member_string (string name, bool = false);
+ // T next_expect_member_string<T> (string name, bool = false);
+ // std::string& next_expect_member_number (string name, bool = false);
+ // T next_expect_member_number<T> (string name, bool = false);
+ // std::string& next_expect_member_boolean (string name, bool = false);
+ // T next_expect_member_boolean<T>(string name, bool = false);
+ //
+ // std::string* next_expect_member_string_null (string, bool = false);
+ // optional<T> next_expect_member_string_null<T> (string, bool = false);
+ // std::string* next_expect_member_number_null (string, bool = false);
+ // optional<T> next_expect_member_number_null<T> (string, bool = false);
+ // std::string* next_expect_member_boolean_null (string, bool = false);
+ // optional<T> next_expect_member_boolean_null<T>(string, bool = false);
+ //
+ // void next_expect_member_object (string name, bool = false);
+ // bool next_expect_member_object_null(string name, bool = false);
+ //
+ // void next_expect_member_array (string name, bool = false);
+ // bool next_expect_member_array_null(string name, bool = false);
+ //
+ // void next_expect_value_skip();
+
+ // Get the next event and make sure that it's what's expected: primary
+ // or, if specified, secondary event. If it is not either, then throw
+ // invalid_json_input with appropriate description. Return true if it is
+ // primary.
+ //
+ // The secondary expected event is primarily useful for handling
+ // optional members. For example:
+ //
+ // while (p.next_expect (event::name, event::end_object))
+ // {
+ // // Handle object member.
+ // }
+ //
+ // Or homogeneous arrays:
+ //
+ // while (p.next_expect (event::string, event::end_array))
+ // {
+ // // Handle array element.
+ // }
+ //
+ // Or values that can be null:
+ //
+ // if (p.next_expect (event::begin_object, event::null))
+ // {
+ // // Parse object.
+ // }
+ //
+ bool
+ next_expect (event primary, optional<event> secondary = nullopt);
+
+ // Get the next event and make sure it is event::name and the object
+ // member matches the specified name. If either is not, then throw
+ // invalid_json_input with appropriate description. If skip_unknown is
+ // true, then skip over unknown member names until a match is found.
+ //
+ void
+ next_expect_name (const char* name, bool skip_unknown = false);
+
+ void
+ next_expect_name (const std::string&, bool = false);
+
+ // Get the next event and make sure it is event::<type> returning its
+ // value similar to the value() functions. If it is not, then throw
+ // invalid_json_input with appropriate description.
+ //
+ std::string&
+ next_expect_string ();
+
+ template <typename T>
+ T
+ next_expect_string ();
+
+ std::string&
+ next_expect_number ();
+
+ template <typename T>
+ T
+ next_expect_number ();
+
+ std::string&
+ next_expect_boolean ();
+
+ template <typename T>
+ T
+ next_expect_boolean ();
+
+ // Similar to next_expect_<type>() but in addition to event::<type> also
+ // allow event::null, in which case returning no value.
+ //
+ std::string*
+ next_expect_string_null ();
+
+ template <typename T>
+ optional<T>
+ next_expect_string_null ();
+
+ std::string*
+ next_expect_number_null ();
+
+ template <typename T>
+ optional<T>
+ next_expect_number_null ();
+
+ std::string*
+ next_expect_boolean_null ();
+
+ template <typename T>
+ optional<T>
+ next_expect_boolean_null ();
+
+ // Call next_expect_name() followed by next_expect_<type>[_null]()
+ // returning its result. In other words, parse the entire object member
+ // with the specifed name and of type <type>, returning its value.
+
+ // next_expect_member_string()
+ //
+ std::string&
+ next_expect_member_string (const char* name, bool skip_unknown = false);
+
+ std::string&
+ next_expect_member_string (const std::string&, bool = false);
+
+ template <typename T>
+ T
+ next_expect_member_string (const char*, bool = false);
+
+ template <typename T>
+ T
+ next_expect_member_string (const std::string&, bool = false);
+
+ // next_expect_member_number()
+ //
+ std::string&
+ next_expect_member_number (const char* name, bool skip_unknown = false);
+
+ std::string&
+ next_expect_member_number (const std::string&, bool = false);
+
+ template <typename T>
+ T
+ next_expect_member_number (const char*, bool = false);
+
+ template <typename T>
+ T
+ next_expect_member_number (const std::string&, bool = false);
+
+ // next_expect_member_boolean()
+ //
+ std::string&
+ next_expect_member_boolean (const char* name, bool skip_unknown = false);
+
+ std::string&
+ next_expect_member_boolean (const std::string&, bool = false);
+
+ template <typename T>
+ T
+ next_expect_member_boolean (const char*, bool = false);
+
+ template <typename T>
+ T
+ next_expect_member_boolean (const std::string&, bool = false);
+
+ // next_expect_member_string_null()
+ //
+ std::string*
+ next_expect_member_string_null (const char*, bool = false);
+
+ std::string*
+ next_expect_member_string_null (const std::string&, bool = false);
+
+ template <typename T>
+ optional<T>
+ next_expect_member_string_null (const char*, bool = false);
+
+ template <typename T>
+ optional<T>
+ next_expect_member_string_null (const std::string&, bool = false);
+
+ // next_expect_member_number_null()
+ //
+ std::string*
+ next_expect_member_number_null (const char*, bool = false);
+
+ std::string*
+ next_expect_member_number_null (const std::string&, bool = false);
+
+ template <typename T>
+ optional<T>
+ next_expect_member_number_null (const char*, bool = false);
+
+ template <typename T>
+ optional<T>
+ next_expect_member_number_null (const std::string&, bool = false);
+
+ // next_expect_member_boolean_null()
+ //
+ std::string*
+ next_expect_member_boolean_null (const char*, bool = false);
+
+ std::string*
+ next_expect_member_boolean_null (const std::string&, bool = false);
+
+ template <typename T>
+ optional<T>
+ next_expect_member_boolean_null (const char*, bool = false);
+
+ template <typename T>
+ optional<T>
+ next_expect_member_boolean_null (const std::string&, bool = false);
+
+ // Call next_expect_name() followed by next_expect(event::begin_object).
+ // In the _null version also allow event::null, in which case return
+ // false.
+ //
+ void
+ next_expect_member_object (const char* name, bool skip_unknown = false);
+
+ void
+ next_expect_member_object (const std::string&, bool = false);
+
+ bool
+ next_expect_member_object_null (const char*, bool = false);
+
+ bool
+ next_expect_member_object_null (const std::string&, bool = false);
+
+ // Call next_expect_name() followed by next_expect(event::begin_array).
+ // In the _null version also allow event::null, in which case return
+ // false.
+ //
+ void
+ next_expect_member_array (const char* name, bool skip_unknown = false);
+
+ void
+ next_expect_member_array (const std::string&, bool = false);
+
+ bool
+ next_expect_member_array_null (const char*, bool = false);
+
+ bool
+ next_expect_member_array_null (const std::string&, bool = false);
+
+ // Get the next event and make sure it is the beginning of a value
+ // (begin_object, begin_array, string, number, boolean, null). If it is
+ // not, then throw invalid_json_input with appropriate description.
+ // Otherwise, skip until the end of the value, recursively in case of
+ // object and array.
+ //
+ // This function is primarily useful for skipping unknown object
+ // members, for example:
+ //
+ // while (p.next_expect (event::name, event::end_object))
+ // {
+ // if (p.name () == "known")
+ // {
+ // // Handle known member.
+ // }
+ // else
+ // p.next_expect_value_skip ();
+ // }
+ //
+ void
+ next_expect_value_skip ();
+
+ // Parsing location.
+ //
+
+ // Return the line number (1-based) corresponding to the most recently
+ // parsed event or 0 if nothing has been parsed yet.
+ //
+ std::uint64_t
+ line () const noexcept;
+
+ // Return the column number (1-based) corresponding to the beginning of
+ // the most recently parsed event or 0 if nothing has been parsed yet.
+ //
+ std::uint64_t
+ column () const noexcept;
+
+ // Return the position (byte offset) pointing immediately after the most
+ // recently parsed event or 0 if nothing has been parsed yet.
+ //
+ std::uint64_t
+ position () const noexcept;
+
+ // Implementation details.
+ //
+ public:
+ struct iterator
+ {
+ using value_type = event;
+
+ explicit
+ iterator (parser* p = nullptr, optional<event> e = nullopt)
+ : p_ (p), e_ (e) {}
+
+ event operator* () const {return *e_;}
+ iterator& operator++ () {e_ = p_->next (); return *this;}
+
+ // Comparison only makes sense when comparing to end (eof).
+ //
+ bool operator== (iterator y) const {return !e_ && !y.e_;}
+ bool operator!= (iterator y) const {return !(*this == y);}
+
+ private:
+ parser* p_;
+ optional<event> e_;
+ };
+
+ struct stream
+ {
+ std::istream* is;
+ optional<std::exception_ptr> exception;
+ };
+
+ [[noreturn]] void
+ throw_invalid_value (const char* type, const char*, std::size_t) const;
+
+ ~parser ();
+
+ private:
+ // Functionality shared by next() and peek().
+ //
+ json_type
+ next_impl ();
+
+ // Translate the event produced by the most recent call to next_impl().
+ //
+ // Note that the underlying parser state determines whether name or
+ // value is returned when translating JSON_STRING.
+ //
+ optional<event>
+ translate (json_type) const noexcept;
+
+ // Cache state (name/value) produced by the most recent call to
+ // next_impl().
+ //
+ void
+ cache_parsed_data ();
+
+ // Cache the location numbers as determined by the most recent call to
+ // next_impl().
+ //
+ void
+ cache_parsed_location () noexcept;
+
+ // Return true if this is a value event (string, number, boolean, or
+ // null).
+ //
+ static bool
+ value_event (optional<event>) noexcept;
+
+ stream stream_;
+
+ bool multi_value_;
+ const char* separators_;
+
+ // The *_p_ members indicate whether the value is present (cached).
+ // Note: not using optional not to reallocate the string's buffer.
+ //
+ std::string name_; bool name_p_ = false;
+ std::string value_; bool value_p_ = false;
+ std::uint64_t line_, column_, position_; bool location_p_ = false;
+
+ optional<json_type> parsed_; // Current parsed event if any.
+ optional<json_type> peeked_; // Current peeked event if any.
+
+ ::json_stream impl_[1];
+
+ // Cached raw value.
+ //
+ const char* raw_s_;
+ std::size_t raw_n_;
+ };
+ }
+}
+
+#include <libbutl/json/parser.ixx>
diff --git a/libbutl/json/parser.ixx b/libbutl/json/parser.ixx
new file mode 100644
index 0000000..cf6dca3
--- /dev/null
+++ b/libbutl/json/parser.ixx
@@ -0,0 +1,552 @@
+#include <cerrno>
+#include <limits> // numeric_limits
+#include <utility> // move()
+#include <cassert>
+#include <cstdlib> // strto*()
+#include <type_traits> // enable_if, is_*
+#include <cstring> // strlen()
+
+namespace butl
+{
+ namespace json
+ {
+ inline invalid_json_input::
+ invalid_json_input (std::string n,
+ std::uint64_t l,
+ std::uint64_t c,
+ std::uint64_t p,
+ const std::string& d)
+ : invalid_json_input (move (n), l, c, p, d.c_str ())
+ {
+ }
+
+ inline invalid_json_input::
+ invalid_json_input (std::string n,
+ std::uint64_t l,
+ std::uint64_t c,
+ std::uint64_t p,
+ const char* d)
+ : invalid_argument (d),
+ name (std::move (n)),
+ line (l), column (c), position (p)
+ {
+ }
+
+ inline parser::
+ parser (std::istream& is,
+ const std::string& n,
+ bool mv,
+ const char* sep) noexcept
+ : parser (is, n.c_str (), mv, sep)
+ {
+ }
+
+ inline parser::
+ parser (const void* t,
+ std::size_t s,
+ const std::string& n,
+ bool mv,
+ const char* sep) noexcept
+ : parser (t, s, n.c_str (), mv, sep)
+ {
+ }
+
+ inline parser::
+ parser (const std::string& t,
+ const std::string& n,
+ bool mv,
+ const char* sep) noexcept
+ : parser (t.data (), t.size (), n.c_str (), mv, sep)
+ {
+ }
+
+ inline parser::
+ parser (const std::string& t,
+ const char* n,
+ bool mv,
+ const char* sep) noexcept
+ : parser (t.data (), t.size (), n, mv, sep)
+ {
+ }
+
+ inline parser::
+ parser (const char* t,
+ const std::string& n,
+ bool mv,
+ const char* sep) noexcept
+ : parser (t, std::strlen (t), n.c_str (), mv, sep)
+ {
+ }
+
+ inline parser::
+ parser (const char* t,
+ const char* n,
+ bool mv,
+ const char* sep) noexcept
+ : parser (t, std::strlen (t), n, mv, sep)
+ {
+ }
+
+ inline const std::string& parser::
+ name ()
+ {
+ if (!name_p_)
+ {
+ assert (parsed_ && !peeked_ && !value_p_);
+ cache_parsed_data ();
+ assert (name_p_);
+ }
+ return name_;
+ }
+
+ inline std::string& parser::
+ value ()
+ {
+ if (!value_p_)
+ {
+ assert (parsed_ && !peeked_ && !name_p_);
+ cache_parsed_data ();
+ assert (value_p_);
+ }
+ return value_;
+ }
+
+ // Note: one day we will be able to use C++17 from_chars() which was made
+ // exactly for this.
+ //
+ template <typename T>
+ inline typename std::enable_if<std::is_same<T, bool>::value, T>::type
+ parse_value (const char* b, size_t, const parser&)
+ {
+ return *b == 't';
+ }
+
+ template <typename T>
+ inline typename std::enable_if<
+ std::is_integral<T>::value &&
+ std::is_signed<T>::value &&
+ !std::is_same<T, bool>::value, T>::type
+ parse_value (const char* b, size_t n, const parser& p)
+ {
+ char* e (nullptr);
+ errno = 0; // We must clear it according to POSIX.
+ std::int64_t v (strtoll (b, &e, 10)); // Can't throw.
+
+ if (e == b || e != b + n || errno == ERANGE ||
+ v < std::numeric_limits<T>::min () ||
+ v > std::numeric_limits<T>::max ())
+ p.throw_invalid_value ("signed integer", b, n);
+
+ return static_cast<T> (v);
+ }
+
+ template <typename T>
+ inline typename std::enable_if<
+ std::is_integral<T>::value &&
+ std::is_unsigned<T>::value &&
+ !std::is_same<T, bool>::value, T>::type
+ parse_value (const char* b, size_t n, const parser& p)
+ {
+ char* e (nullptr);
+ errno = 0; // We must clear it according to POSIX.
+ std::uint64_t v (strtoull (b, &e, 10)); // Can't throw.
+
+ if (e == b || e != b + n || errno == ERANGE ||
+ v > std::numeric_limits<T>::max ())
+ p.throw_invalid_value ("unsigned integer", b, n);
+
+ return static_cast<T> (v);
+ }
+
+ template <typename T>
+ inline typename std::enable_if<std::is_same<T, float>::value, T>::type
+ parse_value (const char* b, size_t n, const parser& p)
+ {
+ char* e (nullptr);
+ errno = 0; // We must clear it according to POSIX.
+ T r (std::strtof (b, &e));
+
+ if (e == b || e != b + n || errno == ERANGE)
+ p.throw_invalid_value ("float", b, n);
+
+ return r;
+ }
+
+ template <typename T>
+ inline typename std::enable_if<std::is_same<T, double>::value, T>::type
+ parse_value (const char* b, size_t n, const parser& p)
+ {
+ char* e (nullptr);
+ errno = 0; // We must clear it according to POSIX.
+ T r (std::strtod (b, &e));
+
+ if (e == b || e != b + n || errno == ERANGE)
+ p.throw_invalid_value ("double", b, n);
+
+ return r;
+ }
+
+ template <typename T>
+ inline typename std::enable_if<std::is_same<T, long double>::value, T>::type
+ parse_value (const char* b, size_t n, const parser& p)
+ {
+ char* e (nullptr);
+ errno = 0; // We must clear it according to POSIX.
+ T r (std::strtold (b, &e));
+
+ if (e == b || e != b + n || errno == ERANGE)
+ p.throw_invalid_value ("long double", b, n);
+
+ return r;
+ }
+
+ template <typename T>
+ inline T parser::
+ value () const
+ {
+ if (!value_p_)
+ {
+ assert (parsed_ && !peeked_ && value_event (translate (*parsed_)));
+ return parse_value<T> (raw_s_, raw_n_, *this);
+ }
+
+ return parse_value<T> (value_.data (), value_.size (), *this);
+ }
+
+ inline void parser::
+ next_expect_name (const std::string& n, bool su)
+ {
+ next_expect_name (n.c_str (), su);
+ }
+
+ // next_expect_<type>()
+ //
+ inline std::string& parser::
+ next_expect_string ()
+ {
+ next_expect (event::string);
+ return value ();
+ }
+
+ template <typename T>
+ inline T parser::
+ next_expect_string ()
+ {
+ next_expect (event::string);
+ return value<T> ();
+ }
+
+ inline std::string& parser::
+ next_expect_number ()
+ {
+ next_expect (event::number);
+ return value ();
+ }
+
+ template <typename T>
+ inline T parser::
+ next_expect_number ()
+ {
+ next_expect (event::number);
+ return value<T> ();
+ }
+
+ inline std::string& parser::
+ next_expect_boolean ()
+ {
+ next_expect (event::boolean);
+ return value ();
+ }
+
+ template <typename T>
+ inline T parser::
+ next_expect_boolean ()
+ {
+ next_expect (event::boolean);
+ return value<T> ();
+ }
+
+ // next_expect_<type>_null()
+ //
+ inline std::string* parser::
+ next_expect_string_null ()
+ {
+ return next_expect (event::string, event::null) ? &value () : nullptr;
+ }
+
+ template <typename T>
+ inline optional<T> parser::
+ next_expect_string_null ()
+ {
+ return next_expect (event::string, event::null)
+ ? optional<T> (value<T> ())
+ : nullopt;
+ }
+
+ inline std::string* parser::
+ next_expect_number_null ()
+ {
+ return next_expect (event::number, event::null) ? &value () : nullptr;
+ }
+
+ template <typename T>
+ inline optional<T> parser::
+ next_expect_number_null ()
+ {
+ return next_expect (event::number, event::null)
+ ? optional<T> (value<T> ())
+ : nullopt;
+ }
+
+ inline std::string* parser::
+ next_expect_boolean_null ()
+ {
+ return next_expect (event::boolean, event::null) ? &value () : nullptr;
+ }
+
+ template <typename T>
+ inline optional<T> parser::
+ next_expect_boolean_null ()
+ {
+ return next_expect (event::boolean, event::null)
+ ? optional<T> (value<T> ())
+ : nullopt;
+ }
+
+ // next_expect_member_string()
+ //
+ inline std::string& parser::
+ next_expect_member_string (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_string ();
+ }
+
+ inline std::string& parser::
+ next_expect_member_string (const std::string& n, bool su)
+ {
+ return next_expect_member_string (n.c_str (), su);
+ }
+
+ template <typename T>
+ inline T parser::
+ next_expect_member_string (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_string<T> ();
+ }
+
+ template <typename T>
+ inline T parser::
+ next_expect_member_string (const std::string& n, bool su)
+ {
+ return next_expect_member_string<T> (n.c_str (), su);
+ }
+
+ // next_expect_member_number()
+ //
+ inline std::string& parser::
+ next_expect_member_number (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_number ();
+ }
+
+ inline std::string& parser::
+ next_expect_member_number (const std::string& n, bool su)
+ {
+ return next_expect_member_number (n.c_str (), su);
+ }
+
+ template <typename T>
+ inline T parser::
+ next_expect_member_number (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_number<T> ();
+ }
+
+ template <typename T>
+ inline T parser::
+ next_expect_member_number (const std::string& n, bool su)
+ {
+ return next_expect_member_number<T> (n.c_str (), su);
+ }
+
+ // next_expect_member_boolean()
+ //
+ inline std::string& parser::
+ next_expect_member_boolean (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_boolean ();
+ }
+
+ inline std::string& parser::
+ next_expect_member_boolean (const std::string& n, bool su)
+ {
+ return next_expect_member_boolean (n.c_str (), su);
+ }
+
+ template <typename T>
+ inline T parser::
+ next_expect_member_boolean (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_boolean<T> ();
+ }
+
+ template <typename T>
+ inline T parser::
+ next_expect_member_boolean (const std::string& n, bool su)
+ {
+ return next_expect_member_boolean<T> (n.c_str (), su);
+ }
+
+ // next_expect_member_string_null()
+ //
+ inline std::string* parser::
+ next_expect_member_string_null (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_string_null ();
+ }
+
+ inline std::string* parser::
+ next_expect_member_string_null (const std::string& n, bool su)
+ {
+ return next_expect_member_string_null (n.c_str (), su);
+ }
+
+ template <typename T>
+ inline optional<T> parser::
+ next_expect_member_string_null (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_string_null<T> ();
+ }
+
+ template <typename T>
+ inline optional<T> parser::
+ next_expect_member_string_null (const std::string& n, bool su)
+ {
+ return next_expect_member_string_null<T> (n.c_str (), su);
+ }
+
+ // next_expect_member_number_null()
+ //
+ inline std::string* parser::
+ next_expect_member_number_null (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_number_null ();
+ }
+
+ inline std::string* parser::
+ next_expect_member_number_null (const std::string& n, bool su)
+ {
+ return next_expect_member_number_null (n.c_str (), su);
+ }
+
+ template <typename T>
+ inline optional<T> parser::
+ next_expect_member_number_null (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_number_null<T> ();
+ }
+
+ template <typename T>
+ inline optional<T> parser::
+ next_expect_member_number_null (const std::string& n, bool su)
+ {
+ return next_expect_member_number_null<T> (n.c_str (), su);
+ }
+
+ // next_expect_member_boolean_null()
+ //
+ inline std::string* parser::
+ next_expect_member_boolean_null (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_boolean_null ();
+ }
+
+ inline std::string* parser::
+ next_expect_member_boolean_null (const std::string& n, bool su)
+ {
+ return next_expect_member_boolean_null (n.c_str (), su);
+ }
+
+ template <typename T>
+ inline optional<T> parser::
+ next_expect_member_boolean_null (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect_boolean_null<T> ();
+ }
+
+ template <typename T>
+ inline optional<T> parser::
+ next_expect_member_boolean_null (const std::string& n, bool su)
+ {
+ return next_expect_member_boolean_null<T> (n.c_str (), su);
+ }
+
+ // next_expect_member_object[_null]()
+ //
+ inline void parser::
+ next_expect_member_object (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ next_expect (event::begin_object);
+ }
+
+ inline void parser::
+ next_expect_member_object (const std::string& n, bool su)
+ {
+ next_expect_member_object (n.c_str (), su);
+ }
+
+ inline bool parser::
+ next_expect_member_object_null (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect (event::begin_object, event::null);
+ }
+
+ inline bool parser::
+ next_expect_member_object_null (const std::string& n, bool su)
+ {
+ return next_expect_member_object_null (n.c_str (), su);
+ }
+
+ // next_expect_member_array[_null]()
+ //
+ inline void parser::
+ next_expect_member_array (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ next_expect (event::begin_array);
+ }
+
+ inline void parser::
+ next_expect_member_array (const std::string& n, bool su)
+ {
+ next_expect_member_array (n.c_str (), su);
+ }
+
+ inline bool parser::
+ next_expect_member_array_null (const char* n, bool su)
+ {
+ next_expect_name (n, su);
+ return next_expect (event::begin_array, event::null);
+ }
+
+ inline bool parser::
+ next_expect_member_array_null (const std::string& n, bool su)
+ {
+ return next_expect_member_array_null (n.c_str (), su);
+ }
+ }
+}
diff --git a/libbutl/json/pdjson.c b/libbutl/json/pdjson.c
new file mode 100644
index 0000000..ae10c95
--- /dev/null
+++ b/libbutl/json/pdjson.c
@@ -0,0 +1,1044 @@
+#ifndef _POSIX_C_SOURCE
+# define _POSIX_C_SOURCE 200112L
+#elif _POSIX_C_SOURCE < 200112L
+# error incompatible _POSIX_C_SOURCE level
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifndef PDJSON_H
+# include "pdjson.h"
+#endif
+
+#define JSON_FLAG_ERROR (1u << 0)
+#define JSON_FLAG_STREAMING (1u << 1)
+
+#if defined(_MSC_VER) && (_MSC_VER < 1900)
+
+#define json_error(json, format, ...) \
+ if (!(json->flags & JSON_FLAG_ERROR)) { \
+ json->flags |= JSON_FLAG_ERROR; \
+ _snprintf_s(json->errmsg, sizeof(json->errmsg), \
+ _TRUNCATE, \
+ format, \
+ __VA_ARGS__); \
+ } \
+
+#else
+
+#define json_error(json, format, ...) \
+ if (!(json->flags & JSON_FLAG_ERROR)) { \
+ json->flags |= JSON_FLAG_ERROR; \
+ snprintf(json->errmsg, sizeof(json->errmsg), \
+ format, \
+ __VA_ARGS__); \
+ } \
+
+#endif /* _MSC_VER */
+
+/* See also PDJSON_STACK_MAX below. */
+#ifndef PDJSON_STACK_INC
+# define PDJSON_STACK_INC 4
+#endif
+
+struct json_stack {
+ enum json_type type;
+ long count;
+};
+
+static enum json_type
+push(json_stream *json, enum json_type type)
+{
+ json->stack_top++;
+
+#ifdef PDJSON_STACK_MAX
+ if (json->stack_top > PDJSON_STACK_MAX) {
+ json_error(json, "%s", "maximum depth of nesting reached");
+ return JSON_ERROR;
+ }
+#endif
+
+ if (json->stack_top >= json->stack_size) {
+ struct json_stack *stack;
+ size_t size = (json->stack_size + PDJSON_STACK_INC) * sizeof(*json->stack);
+ stack = (struct json_stack *)json->alloc.realloc(json->stack, size);
+ if (stack == NULL) {
+ json_error(json, "%s", "out of memory");
+ return JSON_ERROR;
+ }
+
+ json->stack_size += PDJSON_STACK_INC;
+ json->stack = stack;
+ }
+
+ json->stack[json->stack_top].type = type;
+ json->stack[json->stack_top].count = 0;
+
+ return type;
+}
+
+/* Note: c is assumed not to be EOF. */
+static enum json_type
+pop(json_stream *json, int c, enum json_type expected)
+{
+ if (json->stack == NULL || json->stack[json->stack_top].type != expected) {
+ json_error(json, "unexpected byte '%c'", c);
+ return JSON_ERROR;
+ }
+ json->stack_top--;
+ return expected == JSON_ARRAY ? JSON_ARRAY_END : JSON_OBJECT_END;
+}
+
+static int buffer_peek(struct json_source *source)
+{
+ if (source->position < source->source.buffer.length)
+ return source->source.buffer.buffer[source->position];
+ else
+ return EOF;
+}
+
+static int buffer_get(struct json_source *source)
+{
+ int c = source->peek(source);
+ if (c != EOF)
+ source->position++;
+ return c;
+}
+
+static int stream_get(struct json_source *source)
+{
+ int c = fgetc(source->source.stream.stream);
+ if (c != EOF)
+ source->position++;
+ return c;
+}
+
+static int stream_peek(struct json_source *source)
+{
+ int c = fgetc(source->source.stream.stream);
+ ungetc(c, source->source.stream.stream);
+ return c;
+}
+
+static void init(json_stream *json)
+{
+ json->lineno = 1;
+ json->linepos = 0;
+ json->lineadj = 0;
+ json->linecon = 0;
+ json->colno = 0;
+ json->flags = JSON_FLAG_STREAMING;
+ json->errmsg[0] = '\0';
+ json->ntokens = 0;
+ json->next = (enum json_type)0;
+
+ json->stack = NULL;
+ json->stack_top = -1;
+ json->stack_size = 0;
+
+ json->data.string = NULL;
+ json->data.string_size = 0;
+ json->data.string_fill = 0;
+ json->source.position = 0;
+
+ json->alloc.malloc = malloc;
+ json->alloc.realloc = realloc;
+ json->alloc.free = free;
+}
+
+static enum json_type
+is_match(json_stream *json, const char *pattern, enum json_type type)
+{
+ int c;
+ for (const char *p = pattern; *p; p++) {
+ if (*p != (c = json->source.get(&json->source))) {
+ if (c != EOF) {
+ json_error(json, "expected '%c' instead of byte '%c'", *p, c);
+ } else {
+ json_error(json, "expected '%c' instead of end of text", *p);
+ }
+ return JSON_ERROR;
+ }
+ }
+ return type;
+}
+
+static int pushchar(json_stream *json, int c)
+{
+ if (json->data.string_fill == json->data.string_size) {
+ size_t size = json->data.string_size * 2;
+ char *buffer = (char *)json->alloc.realloc(json->data.string, size);
+ if (buffer == NULL) {
+ json_error(json, "%s", "out of memory");
+ return -1;
+ } else {
+ json->data.string_size = size;
+ json->data.string = buffer;
+ }
+ }
+ json->data.string[json->data.string_fill++] = c;
+ return 0;
+}
+
+static int init_string(json_stream *json)
+{
+ json->data.string_fill = 0;
+ if (json->data.string == NULL) {
+ json->data.string_size = 1024;
+ json->data.string = (char *)json->alloc.malloc(json->data.string_size);
+ if (json->data.string == NULL) {
+ json_error(json, "%s", "out of memory");
+ return -1;
+ }
+ }
+ json->data.string[0] = '\0';
+ return 0;
+}
+
+static int encode_utf8(json_stream *json, unsigned long c)
+{
+ if (c < 0x80UL) {
+ return pushchar(json, c);
+ } else if (c < 0x0800UL) {
+ return !((pushchar(json, (c >> 6 & 0x1F) | 0xC0) == 0) &&
+ (pushchar(json, (c >> 0 & 0x3F) | 0x80) == 0));
+ } else if (c < 0x010000UL) {
+ if (c >= 0xd800 && c <= 0xdfff) {
+ json_error(json, "invalid codepoint %06lx", c);
+ return -1;
+ }
+ return !((pushchar(json, (c >> 12 & 0x0F) | 0xE0) == 0) &&
+ (pushchar(json, (c >> 6 & 0x3F) | 0x80) == 0) &&
+ (pushchar(json, (c >> 0 & 0x3F) | 0x80) == 0));
+ } else if (c < 0x110000UL) {
+ return !((pushchar(json, (c >> 18 & 0x07) | 0xF0) == 0) &&
+ (pushchar(json, (c >> 12 & 0x3F) | 0x80) == 0) &&
+ (pushchar(json, (c >> 6 & 0x3F) | 0x80) == 0) &&
+ (pushchar(json, (c >> 0 & 0x3F) | 0x80) == 0));
+ } else {
+ json_error(json, "unable to encode %06lx as UTF-8", c);
+ return -1;
+ }
+}
+
+static int hexchar(int c)
+{
+ switch (c) {
+ case '0': return 0;
+ case '1': return 1;
+ case '2': return 2;
+ case '3': return 3;
+ case '4': return 4;
+ case '5': return 5;
+ case '6': return 6;
+ case '7': return 7;
+ case '8': return 8;
+ case '9': return 9;
+ case 'a':
+ case 'A': return 10;
+ case 'b':
+ case 'B': return 11;
+ case 'c':
+ case 'C': return 12;
+ case 'd':
+ case 'D': return 13;
+ case 'e':
+ case 'E': return 14;
+ case 'f':
+ case 'F': return 15;
+ default:
+ return -1;
+ }
+}
+
+static long
+read_unicode_cp(json_stream *json)
+{
+ long cp = 0;
+ int shift = 12;
+
+ for (size_t i = 0; i < 4; i++) {
+ int c = json->source.get(&json->source);
+ int hc;
+
+ if (c == EOF) {
+ json_error(json, "%s", "unterminated string literal in Unicode");
+ return -1;
+ } else if ((hc = hexchar(c)) == -1) {
+ json_error(json, "invalid escape Unicode byte '%c'", c);
+ return -1;
+ }
+
+ cp += hc * (1 << shift);
+ shift -= 4;
+ }
+
+
+ return cp;
+}
+
+static int read_unicode(json_stream *json)
+{
+ long cp, h, l;
+
+ if ((cp = read_unicode_cp(json)) == -1) {
+ return -1;
+ }
+
+ if (cp >= 0xd800 && cp <= 0xdbff) {
+ /* This is the high portion of a surrogate pair; we need to read the
+ * lower portion to get the codepoint
+ */
+ h = cp;
+
+ int c = json->source.get(&json->source);
+ if (c == EOF) {
+ json_error(json, "%s", "unterminated string literal in Unicode");
+ return -1;
+ } else if (c != '\\') {
+ json_error(json, "invalid continuation for surrogate pair '%c', "
+ "expected '\\'", c);
+ return -1;
+ }
+
+ c = json->source.get(&json->source);
+ if (c == EOF) {
+ json_error(json, "%s", "unterminated string literal in Unicode");
+ return -1;
+ } else if (c != 'u') {
+ json_error(json, "invalid continuation for surrogate pair '%c', "
+ "expected 'u'", c);
+ return -1;
+ }
+
+ if ((l = read_unicode_cp(json)) == -1) {
+ return -1;
+ }
+
+ if (l < 0xdc00 || l > 0xdfff) {
+ json_error(json, "surrogate pair continuation \\u%04lx out "
+ "of range (dc00-dfff)", l);
+ return -1;
+ }
+
+ cp = ((h - 0xd800) * 0x400) + ((l - 0xdc00) + 0x10000);
+ } else if (cp >= 0xdc00 && cp <= 0xdfff) {
+ json_error(json, "dangling surrogate \\u%04lx", cp);
+ return -1;
+ }
+
+ return encode_utf8(json, cp);
+}
+
+static int
+read_escaped(json_stream *json)
+{
+ int c = json->source.get(&json->source);
+ if (c == EOF) {
+ json_error(json, "%s", "unterminated string literal in escape");
+ return -1;
+ } else if (c == 'u') {
+ if (read_unicode(json) != 0)
+ return -1;
+ } else {
+ switch (c) {
+ case '\\':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ case '/':
+ case '"':
+ {
+ const char *codes = "\\bfnrt/\"";
+ const char *p = strchr(codes, c);
+ if (pushchar(json, "\\\b\f\n\r\t/\""[p - codes]) != 0)
+ return -1;
+ }
+ break;
+ default:
+ json_error(json, "invalid escaped byte '%c'", c);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+char_needs_escaping(int c)
+{
+ if ((c >= 0) && (c < 0x20 || c == 0x22 || c == 0x5c)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+utf8_seq_length(char byte)
+{
+ unsigned char u = (unsigned char) byte;
+ if (u < 0x80) return 1;
+
+ if (0x80 <= u && u <= 0xBF)
+ {
+ // second, third or fourth byte of a multi-byte
+ // sequence, i.e. a "continuation byte"
+ return 0;
+ }
+ else if (u == 0xC0 || u == 0xC1)
+ {
+ // overlong encoding of an ASCII byte
+ return 0;
+ }
+ else if (0xC2 <= u && u <= 0xDF)
+ {
+ // 2-byte sequence
+ return 2;
+ }
+ else if (0xE0 <= u && u <= 0xEF)
+ {
+ // 3-byte sequence
+ return 3;
+ }
+ else if (0xF0 <= u && u <= 0xF4)
+ {
+ // 4-byte sequence
+ return 4;
+ }
+ else
+ {
+ // u >= 0xF5
+ // Restricted (start of 4-, 5- or 6-byte sequence) or invalid UTF-8
+ return 0;
+ }
+}
+
+static int
+is_legal_utf8(const unsigned char *bytes, int length)
+{
+ if (0 == bytes || 0 == length) return 0;
+
+ unsigned char a;
+ const unsigned char* srcptr = bytes + length;
+ switch (length)
+ {
+ default:
+ return 0;
+ // Everything else falls through when true.
+ case 4:
+ if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0;
+ /* FALLTHRU */
+ case 3:
+ if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0;
+ /* FALLTHRU */
+ case 2:
+ a = (*--srcptr);
+ switch (*bytes)
+ {
+ case 0xE0:
+ if (a < 0xA0 || a > 0xBF) return 0;
+ break;
+ case 0xED:
+ if (a < 0x80 || a > 0x9F) return 0;
+ break;
+ case 0xF0:
+ if (a < 0x90 || a > 0xBF) return 0;
+ break;
+ case 0xF4:
+ if (a < 0x80 || a > 0x8F) return 0;
+ break;
+ default:
+ if (a < 0x80 || a > 0xBF) return 0;
+ break;
+ }
+ /* FALLTHRU */
+ case 1:
+ if (*bytes >= 0x80 && *bytes < 0xC2) return 0;
+ }
+ return *bytes <= 0xF4;
+}
+
+static int
+read_utf8(json_stream* json, int next_char)
+{
+ int count = utf8_seq_length(next_char);
+ if (!count)
+ {
+ json_error(json, "%s", "invalid UTF-8 character");
+ return -1;
+ }
+
+ char buffer[4];
+ buffer[0] = next_char;
+ int i;
+ for (i = 1; i < count; ++i)
+ {
+ if ((next_char = json->source.get(&json->source)) == EOF)
+ break;
+
+ buffer[i] = next_char;
+ json->lineadj++;
+ }
+
+ if (i != count || !is_legal_utf8((unsigned char*) buffer, count))
+ {
+ json_error(json, "%s", "invalid UTF-8 text");
+ return -1;
+ }
+
+ for (i = 0; i < count; ++i)
+ {
+ if (pushchar(json, buffer[i]) != 0)
+ return -1;
+ }
+ return 0;
+}
+
+static enum json_type
+read_string(json_stream *json)
+{
+ if (init_string(json) != 0)
+ return JSON_ERROR;
+ while (1) {
+ int c = json->source.get(&json->source);
+ if (c == EOF) {
+ json_error(json, "%s", "unterminated string literal");
+ return JSON_ERROR;
+ } else if (c == '"') {
+ if (pushchar(json, '\0') == 0)
+ return JSON_STRING;
+ else
+ return JSON_ERROR;
+ } else if (c == '\\') {
+ if (read_escaped(json) != 0)
+ return JSON_ERROR;
+ } else if ((unsigned) c >= 0x80) {
+ if (read_utf8(json, c) != 0)
+ return JSON_ERROR;
+ } else {
+ if (char_needs_escaping(c)) {
+ json_error(json, "%s", "unescaped control character in string");
+ return JSON_ERROR;
+ }
+
+ if (pushchar(json, c) != 0)
+ return JSON_ERROR;
+ }
+ }
+ return JSON_ERROR;
+}
+
+static int
+is_digit(int c)
+{
+ return c >= 48 /*0*/ && c <= 57 /*9*/;
+}
+
+static int
+read_digits(json_stream *json)
+{
+ int c;
+ unsigned nread = 0;
+ while (is_digit(c = json->source.peek(&json->source))) {
+ if (pushchar(json, json->source.get(&json->source)) != 0)
+ return -1;
+
+ nread++;
+ }
+
+ if (nread == 0) {
+ if (c != EOF) {
+ json_error(json, "expected digit instead of byte '%c'", c);
+ } else {
+ json_error(json, "%s", "expected digit instead of end of text");
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+static enum json_type
+read_number(json_stream *json, int c)
+{
+ if (pushchar(json, c) != 0)
+ return JSON_ERROR;
+ if (c == '-') {
+ c = json->source.get(&json->source);
+ if (is_digit(c)) {
+ return read_number(json, c);
+ } else {
+ if (c != EOF) {
+ json_error(json, "unexpected byte '%c' in number", c);
+ } else {
+ json_error(json, "%s", "unexpected end of text in number");
+ }
+ return JSON_ERROR;
+ }
+ } else if (strchr("123456789", c) != NULL) {
+ c = json->source.peek(&json->source);
+ if (is_digit(c)) {
+ if (read_digits(json) != 0)
+ return JSON_ERROR;
+ }
+ }
+ /* Up to decimal or exponent has been read. */
+ c = json->source.peek(&json->source);
+ if (strchr(".eE", c) == NULL) {
+ if (pushchar(json, '\0') != 0)
+ return JSON_ERROR;
+ else
+ return JSON_NUMBER;
+ }
+ if (c == '.') {
+ json->source.get(&json->source); // consume .
+ if (pushchar(json, c) != 0)
+ return JSON_ERROR;
+ if (read_digits(json) != 0)
+ return JSON_ERROR;
+ }
+ /* Check for exponent. */
+ c = json->source.peek(&json->source);
+ if (c == 'e' || c == 'E') {
+ json->source.get(&json->source); // consume e/E
+ if (pushchar(json, c) != 0)
+ return JSON_ERROR;
+ c = json->source.peek(&json->source);
+ if (c == '+' || c == '-') {
+ json->source.get(&json->source); // consume
+ if (pushchar(json, c) != 0)
+ return JSON_ERROR;
+ if (read_digits(json) != 0)
+ return JSON_ERROR;
+ } else if (is_digit(c)) {
+ if (read_digits(json) != 0)
+ return JSON_ERROR;
+ } else {
+ json->source.get(&json->source); // consume (for column)
+ if (c != EOF) {
+ json_error(json, "unexpected byte '%c' in number", c);
+ } else {
+ json_error(json, "%s", "unexpected end of text in number");
+ }
+ return JSON_ERROR;
+ }
+ }
+ if (pushchar(json, '\0') != 0)
+ return JSON_ERROR;
+ else
+ return JSON_NUMBER;
+}
+
+bool
+json_isspace(int c)
+{
+ switch (c) {
+ case 0x09:
+ case 0x0a:
+ case 0x0d:
+ case 0x20:
+ return true;
+ }
+
+ return false;
+}
+
+static void newline(json_stream *json)
+{
+ json->lineno++;
+ json->linepos = json->source.position;
+ json->lineadj = 0;
+ json->linecon = 0;
+}
+
+/* Returns the next non-whitespace character in the stream.
+ *
+ * Note that this is the only function (besides user-facing json_source_get())
+ * that needs to worry about newline housekeeping.
+ */
+static int next(json_stream *json)
+{
+ int c;
+ while (json_isspace(c = json->source.get(&json->source)))
+ if (c == '\n')
+ newline(json);
+ return c;
+}
+
+static enum json_type
+read_value(json_stream *json, int c)
+{
+ enum json_type type;
+ size_t colno = json_get_column(json);
+
+ json->ntokens++;
+
+ switch (c) {
+ case EOF:
+ json_error(json, "%s", "unexpected end of text");
+ type = JSON_ERROR;
+ break;
+ case '{':
+ type = push(json, JSON_OBJECT);
+ break;
+ case '[':
+ type = push(json, JSON_ARRAY);
+ break;
+ case '"':
+ type = read_string(json);
+ break;
+ case 'n':
+ type = is_match(json, "ull", JSON_NULL);
+ break;
+ case 'f':
+ type = is_match(json, "alse", JSON_FALSE);
+ break;
+ case 't':
+ type = is_match(json, "rue", JSON_TRUE);
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '-':
+ type = init_string(json) == 0 ? read_number(json, c) : JSON_ERROR;
+ break;
+ default:
+ type = JSON_ERROR;
+ json_error(json, "unexpected byte '%c' in value", c);
+ break;
+ }
+
+ if (type != JSON_ERROR)
+ json->colno = colno;
+
+ return type;
+}
+
+enum json_type json_peek(json_stream *json)
+{
+ enum json_type next;
+ if (json->next)
+ next = json->next;
+ else
+ next = json->next = json_next(json);
+ return next;
+}
+
+enum json_type json_next(json_stream *json)
+{
+ if (json->flags & JSON_FLAG_ERROR)
+ return JSON_ERROR;
+ if (json->next != 0) {
+ enum json_type next = json->next;
+ json->next = (enum json_type)0;
+ return next;
+ }
+
+ json->colno = 0;
+
+ if (json->ntokens > 0 && json->stack_top == (size_t)-1) {
+
+ /* In the streaming mode leave any trailing whitespaces in the stream.
+ * This allows the user to validate any desired separation between
+ * values (such as newlines) using json_source_get/peek() with any
+ * remaining whitespaces ignored as leading when we parse the next
+ * value. */
+ if (!(json->flags & JSON_FLAG_STREAMING)) {
+ int c = next(json);
+ if (c != EOF) {
+ json_error(json, "expected end of text instead of byte '%c'", c);
+ return JSON_ERROR;
+ }
+ }
+
+ return JSON_DONE;
+ }
+ int c = next(json);
+ if (json->stack_top == (size_t)-1) {
+ if (c == EOF && (json->flags & JSON_FLAG_STREAMING))
+ return JSON_DONE;
+
+ return read_value(json, c);
+ }
+ if (json->stack[json->stack_top].type == JSON_ARRAY) {
+ if (json->stack[json->stack_top].count == 0) {
+ if (c == ']') {
+ return pop(json, c, JSON_ARRAY);
+ }
+ json->stack[json->stack_top].count++;
+ return read_value(json, c);
+ } else if (c == ',') {
+ json->stack[json->stack_top].count++;
+ return read_value(json, next(json));
+ } else if (c == ']') {
+ return pop(json, c, JSON_ARRAY);
+ } else {
+ if (c != EOF) {
+ json_error(json, "unexpected byte '%c'", c);
+ } else {
+ json_error(json, "%s", "unexpected end of text");
+ }
+ return JSON_ERROR;
+ }
+ } else if (json->stack[json->stack_top].type == JSON_OBJECT) {
+ if (json->stack[json->stack_top].count == 0) {
+ if (c == '}') {
+ return pop(json, c, JSON_OBJECT);
+ }
+
+ /* No member name/value pairs yet. */
+ enum json_type value = read_value(json, c);
+ if (value != JSON_STRING) {
+ if (value != JSON_ERROR)
+ json_error(json, "%s", "expected member name or '}'");
+ return JSON_ERROR;
+ } else {
+ json->stack[json->stack_top].count++;
+ return value;
+ }
+ } else if ((json->stack[json->stack_top].count % 2) == 0) {
+ /* Expecting comma followed by member name. */
+ if (c != ',' && c != '}') {
+ json_error(json, "%s", "expected ',' or '}' after member value");
+ return JSON_ERROR;
+ } else if (c == '}') {
+ return pop(json, c, JSON_OBJECT);
+ } else {
+ enum json_type value = read_value(json, next(json));
+ if (value != JSON_STRING) {
+ if (value != JSON_ERROR)
+ json_error(json, "%s", "expected member name");
+ return JSON_ERROR;
+ } else {
+ json->stack[json->stack_top].count++;
+ return value;
+ }
+ }
+ } else if ((json->stack[json->stack_top].count % 2) == 1) {
+ /* Expecting colon followed by value. */
+ if (c != ':') {
+ json_error(json, "%s", "expected ':' after member name");
+ return JSON_ERROR;
+ } else {
+ json->stack[json->stack_top].count++;
+ return read_value(json, next(json));
+ }
+ }
+ }
+ json_error(json, "%s", "invalid parser state");
+ return JSON_ERROR;
+}
+
+void json_reset(json_stream *json)
+{
+ json->stack_top = -1;
+ json->ntokens = 0;
+ json->flags &= ~JSON_FLAG_ERROR;
+ json->errmsg[0] = '\0';
+}
+
+enum json_type json_skip(json_stream *json)
+{
+ enum json_type type = json_next(json);
+ size_t cnt_arr = 0;
+ size_t cnt_obj = 0;
+
+ for (enum json_type skip = type; ; skip = json_next(json)) {
+ if (skip == JSON_ERROR || skip == JSON_DONE)
+ return skip;
+
+ if (skip == JSON_ARRAY) {
+ ++cnt_arr;
+ } else if (skip == JSON_ARRAY_END && cnt_arr > 0) {
+ --cnt_arr;
+ } else if (skip == JSON_OBJECT) {
+ ++cnt_obj;
+ } else if (skip == JSON_OBJECT_END && cnt_obj > 0) {
+ --cnt_obj;
+ }
+
+ if (!cnt_arr && !cnt_obj)
+ break;
+ }
+
+ return type;
+}
+
+enum json_type json_skip_until(json_stream *json, enum json_type type)
+{
+ while (1) {
+ enum json_type skip = json_skip(json);
+
+ if (skip == JSON_ERROR || skip == JSON_DONE)
+ return skip;
+
+ if (skip == type)
+ break;
+ }
+
+ return type;
+}
+
+const char *json_get_string(json_stream *json, size_t *length)
+{
+ if (length != NULL)
+ *length = json->data.string_fill;
+ if (json->data.string == NULL)
+ return "";
+ else
+ return json->data.string;
+}
+
+double json_get_number(json_stream *json)
+{
+ char *p = json->data.string;
+ return p == NULL ? 0 : strtod(p, NULL);
+}
+
+const char *json_get_error(json_stream *json)
+{
+ return json->flags & JSON_FLAG_ERROR ? json->errmsg : NULL;
+}
+
+size_t json_get_lineno(json_stream *json)
+{
+ return json->lineno;
+}
+
+size_t json_get_position(json_stream *json)
+{
+ return json->source.position;
+}
+
+size_t json_get_column(json_stream *json)
+{
+ return json->colno == 0
+ ? json->source.position == 0 ? 1 : json->source.position - json->linepos - json->lineadj
+ : json->colno;
+}
+
+size_t json_get_depth(json_stream *json)
+{
+ return json->stack_top + 1;
+}
+
+/* Return the current parsing context, that is, JSON_OBJECT if we are inside
+ an object, JSON_ARRAY if we are inside an array, and JSON_DONE if we are
+ not yet/anymore in either.
+
+ Additionally, for the first two cases, also return the number of parsing
+ events that have already been observed at this level with json_next/peek().
+ In particular, inside an object, an odd number would indicate that the just
+ observed JSON_STRING event is a member name.
+*/
+enum json_type json_get_context(json_stream *json, size_t *count)
+{
+ if (json->stack_top == (size_t)-1)
+ return JSON_DONE;
+
+ if (count != NULL)
+ *count = json->stack[json->stack_top].count;
+
+ return json->stack[json->stack_top].type;
+}
+
+int json_source_get(json_stream *json)
+{
+ /* If the caller reads a multi-byte UTF-8 sequence, we expect them to read
+ * it in its entirety. We also assume that any invalid bytes within such a
+ * sequence belong to the same column (as opposed to starting a new column
+ * or some such). */
+
+ int c = json->source.get(&json->source);
+ if (json->linecon > 0) {
+ /* Expecting a continuation byte within a multi-byte UTF-8 sequence. */
+ json->linecon--;
+ if (c != EOF)
+ json->lineadj++;
+ } else if (c == '\n')
+ newline(json);
+ else if (c >= 0xC2 && c <= 0xF4) /* First in multi-byte UTF-8 sequence. */
+ json->linecon = utf8_seq_length(c) - 1;
+
+ return c;
+}
+
+int json_source_peek(json_stream *json)
+{
+ return json->source.peek(&json->source);
+}
+
+void json_open_buffer(json_stream *json, const void *buffer, size_t size)
+{
+ init(json);
+ json->source.get = buffer_get;
+ json->source.peek = buffer_peek;
+ json->source.source.buffer.buffer = (const char *)buffer;
+ json->source.source.buffer.length = size;
+}
+
+void json_open_string(json_stream *json, const char *string)
+{
+ json_open_buffer(json, string, strlen(string));
+}
+
+void json_open_stream(json_stream *json, FILE * stream)
+{
+ init(json);
+ json->source.get = stream_get;
+ json->source.peek = stream_peek;
+ json->source.source.stream.stream = stream;
+}
+
+static int user_get(struct json_source *json)
+{
+ int c = json->source.user.get(json->source.user.ptr);
+ if (c != EOF)
+ json->position++;
+ return c;
+}
+
+static int user_peek(struct json_source *json)
+{
+ return json->source.user.peek(json->source.user.ptr);
+}
+
+void json_open_user(json_stream *json, json_user_io get, json_user_io peek, void *user)
+{
+ init(json);
+ json->source.get = user_get;
+ json->source.peek = user_peek;
+ json->source.source.user.ptr = user;
+ json->source.source.user.get = get;
+ json->source.source.user.peek = peek;
+}
+
+void json_set_allocator(json_stream *json, json_allocator *a)
+{
+ json->alloc = *a;
+}
+
+void json_set_streaming(json_stream *json, bool streaming)
+{
+ if (streaming)
+ json->flags |= JSON_FLAG_STREAMING;
+ else
+ json->flags &= ~JSON_FLAG_STREAMING;
+}
+
+void json_close(json_stream *json)
+{
+ json->alloc.free(json->stack);
+ json->alloc.free(json->data.string);
+}
diff --git a/libbutl/json/pdjson.h b/libbutl/json/pdjson.h
new file mode 100644
index 0000000..ac698e4
--- /dev/null
+++ b/libbutl/json/pdjson.h
@@ -0,0 +1,147 @@
+#ifndef PDJSON_H
+#define PDJSON_H
+
+#ifndef PDJSON_SYMEXPORT
+# define PDJSON_SYMEXPORT
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#else
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+ #include <stdbool.h>
+#else
+ #ifndef bool
+ #define bool int
+ #define true 1
+ #define false 0
+ #endif /* bool */
+#endif /* __STDC_VERSION__ */
+#endif /* __cplusplus */
+
+#include <stdio.h>
+
+enum json_type {
+ JSON_ERROR = 1, JSON_DONE,
+ JSON_OBJECT, JSON_OBJECT_END, JSON_ARRAY, JSON_ARRAY_END,
+ JSON_STRING, JSON_NUMBER, JSON_TRUE, JSON_FALSE, JSON_NULL
+};
+
+struct json_allocator {
+ void *(*malloc)(size_t);
+ void *(*realloc)(void *, size_t);
+ void (*free)(void *);
+};
+
+typedef int (*json_user_io)(void *user);
+
+typedef struct json_stream json_stream;
+typedef struct json_allocator json_allocator;
+
+PDJSON_SYMEXPORT void json_open_buffer(json_stream *json, const void *buffer, size_t size);
+PDJSON_SYMEXPORT void json_open_string(json_stream *json, const char *string);
+PDJSON_SYMEXPORT void json_open_stream(json_stream *json, FILE *stream);
+PDJSON_SYMEXPORT void json_open_user(json_stream *json, json_user_io get, json_user_io peek, void *user);
+PDJSON_SYMEXPORT void json_close(json_stream *json);
+
+PDJSON_SYMEXPORT void json_set_allocator(json_stream *json, json_allocator *a);
+PDJSON_SYMEXPORT void json_set_streaming(json_stream *json, bool mode);
+
+PDJSON_SYMEXPORT enum json_type json_next(json_stream *json);
+PDJSON_SYMEXPORT enum json_type json_peek(json_stream *json);
+PDJSON_SYMEXPORT void json_reset(json_stream *json);
+PDJSON_SYMEXPORT const char *json_get_string(json_stream *json, size_t *length);
+PDJSON_SYMEXPORT double json_get_number(json_stream *json);
+
+PDJSON_SYMEXPORT enum json_type json_skip(json_stream *json);
+PDJSON_SYMEXPORT enum json_type json_skip_until(json_stream *json, enum json_type type);
+
+PDJSON_SYMEXPORT size_t json_get_lineno(json_stream *json);
+PDJSON_SYMEXPORT size_t json_get_position(json_stream *json);
+PDJSON_SYMEXPORT size_t json_get_column(json_stream *json);
+PDJSON_SYMEXPORT size_t json_get_depth(json_stream *json);
+PDJSON_SYMEXPORT enum json_type json_get_context(json_stream *json, size_t *count);
+PDJSON_SYMEXPORT const char *json_get_error(json_stream *json);
+
+PDJSON_SYMEXPORT int json_source_get(json_stream *json);
+PDJSON_SYMEXPORT int json_source_peek(json_stream *json);
+PDJSON_SYMEXPORT bool json_isspace(int c);
+
+/* internal */
+
+struct json_source {
+ int (*get)(struct json_source *);
+ int (*peek)(struct json_source *);
+ size_t position;
+ union {
+ struct {
+ FILE *stream;
+ } stream;
+ struct {
+ const char *buffer;
+ size_t length;
+ } buffer;
+ struct {
+ void *ptr;
+ json_user_io get;
+ json_user_io peek;
+ } user;
+ } source;
+};
+
+struct json_stream {
+ size_t lineno;
+
+ /* While counting lines is straightforward, columns are tricky because we
+ * have to count codepoints, not bytes. We could have peppered the code
+ * with increments in all the relevant places but that seems inelegant.
+ * So instead we calculate the column dynamically, based on the current
+ * position.
+ *
+ * Specifically, we will remember the position at the beginning of each
+ * line (linepos) and, assuming only the ASCII characters on the line, the
+ * column will be the difference between the current position and linepos.
+ * Of course there could also be multi-byte UTF-8 sequences which we will
+ * handle by keeping an adjustment (lineadj) -- the number of continuation
+ * bytes encountered on this line so far. Finally, for json_source_get()
+ * we also have to keep the number of remaining continuation bytes in the
+ * current multi-byte UTF-8 sequence (linecon).
+ *
+ * This is not the end of the story, however: with only the just described
+ * approach we will always end up with the column of the latest character
+ * read which is not what we want when returning potentially multi-
+ * character value events (string, number, etc); in these cases we want to
+ * return the column of the first character (note that if the value itself
+ * is invalid and we are returning JSON_ERROR, we still want the current
+ * column). So to handle this we will cache the start column (colno) for
+ * such events.
+ */
+ size_t linepos; /* Position at the beginning of the current line. */
+ size_t lineadj; /* Adjustment for multi-byte UTF-8 sequences. */
+ size_t linecon; /* Number of remaining UTF-8 continuation bytes. */
+ size_t colno; /* Start column for value events or 0. */
+
+ struct json_stack *stack;
+ size_t stack_top;
+ size_t stack_size;
+ enum json_type next;
+ unsigned flags;
+
+ struct {
+ char *string;
+ size_t string_fill;
+ size_t string_size;
+ } data;
+
+ size_t ntokens;
+
+ struct json_source source;
+ struct json_allocator alloc;
+ char errmsg[128];
+};
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif
diff --git a/libbutl/json/serializer.cxx b/libbutl/json/serializer.cxx
new file mode 100644
index 0000000..fbd569a
--- /dev/null
+++ b/libbutl/json/serializer.cxx
@@ -0,0 +1,671 @@
+#include <cstdio> // snprintf
+#include <cstdarg> // va_list
+#include <cstring> // memcpy
+#include <ostream>
+
+#include <libbutl/json/serializer.hxx>
+
+using namespace std;
+
+namespace butl
+{
+ namespace json
+ {
+ using buffer = buffer_serializer::buffer;
+ using error_code = invalid_json_output::error_code;
+
+ template <typename T>
+ static void
+ dynarray_overflow (void* d, event, buffer& b, size_t ex)
+ {
+ T& v (*static_cast<T*> (d));
+ v.resize (b.capacity + ex);
+ v.resize (v.capacity ());
+ // const_cast is required for std::string pre C++17.
+ //
+ b.data = const_cast<typename T::value_type*> (v.data ());
+ b.capacity = v.size ();
+ }
+
+ template <typename T>
+ static void
+ dynarray_flush (void* d, event, buffer& b)
+ {
+ T& v (*static_cast<T*> (d));
+ v.resize (b.size);
+ b.data = const_cast<typename T::value_type*> (v.data ());
+ b.capacity = b.size;
+ }
+
+ buffer_serializer::
+ buffer_serializer (string& s, size_t i)
+ : buffer_serializer (const_cast<char*> (s.data ()), size_, s.size (),
+ dynarray_overflow<string>,
+ dynarray_flush<string>,
+ &s,
+ i)
+ {
+ size_ = s.size ();
+ }
+
+ buffer_serializer::
+ buffer_serializer (vector<char>& v, size_t i)
+ : buffer_serializer (v.data (), size_, v.size (),
+ dynarray_overflow<vector<char>>,
+ dynarray_flush<vector<char>>,
+ &v,
+ i)
+ {
+ size_ = v.size ();
+ }
+
+ static void
+ ostream_overflow (void* d, event e, buffer& b, size_t)
+ {
+ ostream& s (*static_cast<ostream*> (d));
+ s.write (static_cast<char*> (b.data), b.size);
+ if (s.fail ())
+ throw invalid_json_output (
+ e, error_code::buffer_overflow, "unable to write JSON output text");
+ b.size = 0;
+ }
+
+ static void
+ ostream_flush (void* d, event e, buffer& b)
+ {
+ ostream_overflow (d, e, b, 0);
+
+ ostream& s (*static_cast<ostream*> (d));
+ s.flush ();
+ if (s.fail ())
+ throw invalid_json_output (
+ e, error_code::buffer_overflow, "unable to write JSON output text");
+ }
+
+ stream_serializer::
+ stream_serializer (ostream& os, size_t i)
+ : buffer_serializer (tmp_, sizeof (tmp_),
+ ostream_overflow,
+ ostream_flush,
+ &os,
+ i)
+ {
+ }
+
+ bool buffer_serializer::
+ next (optional<event> e, pair<const char*, size_t> val, bool check)
+ {
+ if (absent_ == 2)
+ goto fail_complete;
+
+ if (e == nullopt)
+ {
+ if (!state_.empty ())
+ goto fail_incomplete;
+
+ absent_++;
+ return false;
+ }
+
+ absent_ = 0; // Clear inter-value absent event.
+
+ {
+ state* st (state_.empty () ? nullptr : &state_.back ());
+
+ auto name_expected = [] (const state& s)
+ {
+ return s.type == event::begin_object && s.count % 2 == 0;
+ };
+
+ auto make_str = [] (const char* s, size_t n)
+ {
+ return make_pair (s, n);
+ };
+
+ // When it comes to pretty-printing, the common way to do it is along
+ // these lines:
+ //
+ // {
+ // "str": "value",
+ // "obj": {
+ // "arr": [
+ // 1,
+ // 2,
+ // 3
+ // ]
+ // },
+ // "num": 123
+ // }
+ //
+ // Empty objects and arrays are printed without a newline:
+ //
+ // {
+ // "obj": {},
+ // "arr": []
+ // }
+ //
+ // There are two types of separators: between name and value, which is
+ // always ": ", and before/after value inside an object or array which
+ // is either newline followed by indentation, or comma followed by
+ // newline followed by indentation (we also have separation between
+ // top-level values but that's orthogonal to pretty-printing).
+ //
+ // Based on this observation, we are going to handle the latter case by
+ // starting with the ",\n" string (in this->sep_) and pushing/popping
+ // indentation spaces as we enter/leave objects and arrays. We handle
+ // the cases where we don't need the comma by simply skipping it in the
+ // C-string pointer.
+ //
+ bool pp (indent_ != 0);
+
+ pair<const char*, size_t> sep;
+ if (st != nullptr)
+ {
+ // The name-value separator.
+ //
+ if (st->type == event::begin_object && st->count % 2 == 1)
+ {
+ sep = !pp ? make_str (":", 1) : make_str (": ", 2);
+ }
+ // We don't need the comma if we are closing the object or array.
+ //
+ else if (e == event::end_array || e == event::end_object)
+ {
+ // But in this case we need to unindent one level prior to writing
+ // the brace. Also handle the empty object/array as a special case.
+ //
+ sep = !pp || st->count == 0
+ ? make_str (nullptr, 0)
+ : make_str (sep_.c_str () + 1, sep_.size () - 1 - indent_);
+ }
+ // Or if this is the first value (note: must come after end_*).
+ //
+ else if (st->count == 0)
+ {
+ sep = !pp
+ ? make_str (nullptr, 0)
+ : make_str (sep_.c_str () + 1, sep_.size () - 1);
+ }
+ else
+ {
+ sep = !pp
+ ? make_str (",", 1)
+ : make_str (sep_.c_str (), sep_.size ());
+ }
+ }
+ else if (values_ != 0) // Subsequent top-level value.
+ {
+ // Top-level value separation. For now we always separate them with
+ // newlines, which is the most common/sensible way.
+ //
+ sep = make_str ("\n", 1);
+ }
+
+ switch (*e)
+ {
+ case event::begin_array:
+ case event::begin_object:
+ {
+ if (st != nullptr && name_expected (*st))
+ goto fail_unexpected_event;
+
+ write (*e,
+ sep,
+ make_str (e == event::begin_array ? "[" : "{", 1),
+ false);
+
+ if (st != nullptr)
+ st->count++;
+
+ if (pp)
+ sep_.append (indent_, ' ');
+
+ state_.push_back (state {*e, 0});
+ break;
+ }
+ case event::end_array:
+ case event::end_object:
+ {
+ if (st == nullptr || (e == event::end_array
+ ? st->type != event::begin_array
+ : !name_expected (*st)))
+ goto fail_unexpected_event;
+
+ write (*e,
+ sep,
+ make_str (e == event::end_array ? "]" : "}", 1),
+ false);
+
+ if (pp)
+ sep_.erase (sep_.size () - indent_);
+
+ state_.pop_back ();
+ break;
+ }
+ case event::name:
+ case event::string:
+ {
+ if (e == event::name
+ ? (st == nullptr || !name_expected (*st))
+ : (st != nullptr && name_expected (*st)))
+ goto fail_unexpected_event;
+
+ write (*e, sep, val, check, '"');
+
+ if (st != nullptr)
+ st->count++;
+ break;
+ }
+ case event::null:
+ case event::boolean:
+ {
+ if (e == event::null && val.first == nullptr)
+ val = {"null", 4};
+ else if (check)
+ {
+ auto eq = [&val] (const char* v, size_t n)
+ {
+ return val.second == n && memcmp (val.first, v, n) == 0;
+ };
+
+ if (e == event::null)
+ {
+ if (!eq ("null", 4))
+ goto fail_null;
+ }
+ else
+ {
+ if (!eq ("true", 4) && !eq ("false", 5))
+ goto fail_bool;
+ }
+ }
+ }
+ // Fall through.
+ case event::number:
+ {
+ // Note: this event is also used by value_json_text().
+
+ if (st != nullptr && name_expected (*st))
+ goto fail_unexpected_event;
+
+ write (*e, sep, val, check);
+
+ if (st != nullptr)
+ st->count++;
+ break;
+ }
+ }
+ }
+
+ if (state_.empty ())
+ {
+ values_++;
+ if (flush_ != nullptr)
+ flush_ (data_, *e, buf_);
+
+ return false;
+ }
+
+ return true;
+
+ fail_complete:
+ throw invalid_json_output (
+ e, error_code::invalid_value, "value sequence is complete");
+ fail_incomplete:
+ throw invalid_json_output (
+ e, error_code::invalid_value, "value is incomplete");
+ fail_null:
+ throw invalid_json_output (
+ e, error_code::invalid_value, "invalid null value");
+ fail_bool:
+ throw invalid_json_output (
+ e, error_code::invalid_value, "invalid boolean value");
+ fail_unexpected_event:
+ throw invalid_json_output (
+ e, error_code::unexpected_event, "unexpected event");
+ }
+
+ // JSON escape sequences for control characters <= 0x1F.
+ //
+ static const char* json_escapes[] =
+ {"\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005",
+ "\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000B",
+ "\\f", "\\r", "\\u000E", "\\u000F", "\\u0010", "\\u0011",
+ "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
+ "\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D",
+ "\\u001E", "\\u001F"};
+
+ void buffer_serializer::
+ write (event e,
+ pair<const char*, size_t> sep,
+ pair<const char*, size_t> val,
+ bool check,
+ char q)
+ {
+ // Assumptions:
+ //
+ // 1. A call to overflow should be able to provide enough capacity to
+ // write the entire separator (in other words, we are not going to
+ // bother with chunking the separator).
+ //
+ // 2. Similarly, a call to overflow should be able to provide enough
+ // capacity to write an entire UTF-8 multi-byte sequence.
+ //
+ // 3. Performance-wise, we do not expect very long contiguous sequences
+ // of character that require escaping.
+
+ // Total number of bytes remaining to be written and the capacity
+ // currently available.
+ //
+ size_t size (sep.second + val.second + (q != '\0' ? 2 : 0));
+ size_t cap (buf_.capacity - buf_.size);
+
+ auto grow = [this, e, &size, &cap] (size_t min, size_t extra = 0)
+ {
+ if (overflow_ == nullptr)
+ return false;
+
+ extra += size;
+ extra -= cap;
+ overflow_ (data_, e, buf_, extra > min ? extra : min);
+ cap = buf_.capacity - buf_.size;
+
+ return cap >= min;
+ };
+
+ auto append = [this, &cap, &size] (const char* d, size_t s)
+ {
+ memcpy (static_cast<char*> (buf_.data) + buf_.size, d, s);
+ buf_.size += s;
+ cap -= s;
+ size -= s;
+ };
+
+ // Return the longest chunk of input that fits into the buffer and does
+ // not end in the middle of a multi-byte UTF-8 sequence. Assume value
+ // size and capacity are not 0. Return NULL in first if no chunk could
+ // be found that fits into the remaining space. In this case, second is
+ // the additional (to size) required space (used to handle escapes in
+ // the checked version).
+ //
+ // The basic idea is to seek in the input buffer to the capacity of the
+ // output buffer (unless the input is shorter than the output). If we
+ // ended up in the middle of a multi-byte UTF-8 sequence, then seek back
+ // until we end up at the UTF-8 sequence boundary. Note that this
+ // implementation assumes valid UTF-8.
+ //
+ auto chunk = [&cap, &val] () -> pair<const char*, size_t>
+ {
+ pair<const char*, size_t> r (nullptr, 0);
+
+ if (cap >= val.second)
+ r = val;
+ else
+ {
+ // Start from the character past capacity and search for a UTF-8
+ // sequence boundary.
+ //
+ for (const char* p (val.first + cap); p != val.first; --p)
+ {
+ const auto u (static_cast<uint8_t> (*p));
+ if (u < 0x80 || u > 0xBF) // Not a continuation byte
+ {
+ r = {val.first, p - val.first};
+ break;
+ }
+ }
+ }
+
+ val.first += r.second;
+ val.second -= r.second;
+
+ return r;
+ };
+
+ // Escaping and UTF-8-validating version of chunk().
+ //
+ // There are three classes of mandatory escapes in a JSON string:
+ //
+ // - \\ and \"
+ //
+ // - \b \f \n \r \t for popular control characters
+ //
+ // - \u00NN for other control characters <= 0x1F
+ //
+ // If the input begins with a character that must be escaped, return
+ // only its escape sequence. Otherwise validate and return everything up
+ // to the end of input or buffer capacity, but cutting it short before
+ // the next character that must be escaped or the first UTF-8 sequence
+ // that would not fit.
+ //
+ // Return string::npos in second in case of a stray continuation byte or
+ // any byte in an invalid UTF-8 range (for example, an "overlong" 2-byte
+ // encoding of a 7-bit/ASCII character or a 4-, 5-, or 6-byte sequence
+ // that would encode a codepoint beyond the U+10FFFF Unicode limit).
+ //
+ auto chunk_checked = [&cap, &size, &val] () -> pair<const char*, size_t>
+ {
+ pair<const char*, size_t> r (nullptr, 0);
+
+ // Check whether the first character needs to be escaped.
+ //
+ const uint8_t c (val.first[0]);
+ if (c == '"')
+ r = {"\\\"", 2};
+ else if (c == '\\')
+ r = {"\\\\", 2};
+ else if (c <= 0x1F)
+ {
+ auto s (json_escapes[c]);
+ r = {s, s[1] == 'u' ? 6 : 2};
+ }
+
+ if (r.first != nullptr)
+ {
+ // Return in second the additional (to size) space required.
+ //
+ if (r.second > cap)
+ return {nullptr, r.second - 1};
+
+ // If we had to escape the character then adjust size accordingly
+ // (see append() above).
+ //
+ size += r.second - 1;
+
+ val.first += 1;
+ val.second -= 1;
+ return r;
+ }
+
+ // First character doesn't need to be escaped. Return as much of the
+ // rest of the input as possible.
+ //
+ size_t i (0);
+ for (size_t n (min (cap, val.second)); i != n; i++)
+ {
+ const uint8_t c1 (val.first[i]);
+
+ if (c1 == '"' || c1 == '\\' || c1 <= 0x1F) // Needs to be escaped.
+ break;
+ else if (c1 >= 0x80) // Not ASCII, so validate as a UTF-8 sequence.
+ {
+ size_t i1 (i); // Position of the first byte.
+
+ // The control flow here is to continue if valid and to fall
+ // through to return on error.
+ //
+ if (c1 >= 0xC2 && c1 <= 0xDF) // 2-byte sequence.
+ {
+ if (i + 2 <= val.second) // Sequence is complete in JSON value.
+ {
+ if (i + 2 > cap) // Sequence won't fit.
+ break;
+
+ const uint8_t c2 (val.first[++i]);
+
+ if (c2 >= 0x80 && c2 <= 0xBF)
+ continue;
+ }
+ }
+ else if (c1 >= 0xE0 && c1 <= 0xEF) // 3-byte sequence.
+ {
+ if (i + 3 <= val.second)
+ {
+ if (i + 3 > cap)
+ break;
+
+ const uint8_t c2 (val.first[++i]), c3 (val.first[++i]);
+
+ if (c3 >= 0x80 && c3 <= 0xBF)
+ {
+ switch (c1)
+ {
+ case 0xE0: if (c2 >= 0xA0 && c2 <= 0xBF) continue; break;
+ case 0xED: if (c2 >= 0x80 && c2 <= 0x9F) continue; break;
+ default: if (c2 >= 0x80 && c2 <= 0xBF) continue; break;
+ }
+ }
+ }
+ }
+ else if (c1 >= 0xF0 && c1 <= 0xF4) // 4-byte sequence.
+ {
+ if (i + 4 <= val.second)
+ {
+ if (i + 4 > cap)
+ break;
+
+ const uint8_t c2 (val.first[++i]),
+ c3 (val.first[++i]),
+ c4 (val.first[++i]);
+
+ if (c3 >= 0x80 && c3 <= 0xBF &&
+ c4 >= 0x80 && c4 <= 0xBF)
+ {
+ switch (c1)
+ {
+ case 0xF0: if (c2 >= 0x90 && c2 <= 0xBF) continue; break;
+ case 0xF4: if (c2 >= 0x80 && c2 <= 0x8F) continue; break;
+ default: if (c2 >= 0x80 && c2 <= 0xBF) continue; break;
+ }
+ }
+ }
+ }
+
+ r = {val.first, string::npos};
+
+ // Update val to point to the beginning of the invalid sequence.
+ //
+ val.first += i1;
+ val.second -= i1;
+
+ return r;
+ }
+ }
+
+ if (i != 0) // We have a chunk.
+ {
+ r = {val.first, i};
+
+ val.first += i;
+ val.second -= i;
+ }
+
+ return r;
+ };
+
+ // Value's original size (used to calculate the offset of the errant
+ // character in case of a validation failure).
+ //
+ const size_t vn (val.second);
+
+ // Write the separator, if any.
+ //
+ if (sep.second != 0)
+ {
+ if (cap < sep.second && !grow (sep.second))
+ goto fail_nospace;
+
+ append (sep.first, sep.second);
+ }
+
+ // Write the value's opening quote, if requested.
+ //
+ if (q != '\0')
+ {
+ if (cap == 0 && !grow (1))
+ goto fail_nospace;
+
+ append ("\"", 1);
+ }
+
+ // Write the value, unless empty.
+ //
+ while (val.second != 0)
+ {
+ pair<const char*, size_t> ch (nullptr, 0);
+
+ if (cap != 0)
+ ch = check ? chunk_checked () : chunk ();
+
+ if (ch.first == nullptr)
+ {
+ // The minimum extra bytes we need the overflow function to be able
+ // to provide is based on these sequences that we do not break:
+ //
+ // - 4 bytes for a UTF-8 sequence
+ // - 6 bytes for an escaped Unicode sequence (\uXXXX).
+ //
+ if (!grow (6, ch.second))
+ goto fail_nospace;
+ }
+ else if (ch.second != string::npos)
+ append (ch.first, ch.second);
+ else
+ goto fail_utf8;
+ }
+
+ // Write the value's closing quote, if requested.
+ //
+ if (q != '\0')
+ {
+ if (cap == 0 && !grow (1))
+ goto fail_nospace;
+
+ append ("\"", 1);
+ }
+
+ return;
+
+ // Note: keep descriptions consistent with the parser.
+ //
+ fail_utf8:
+ throw invalid_json_output (e,
+ e == event::name ? error_code::invalid_name
+ : error_code::invalid_value,
+ "invalid UTF-8 text",
+ vn - val.second);
+
+ fail_nospace:
+ throw invalid_json_output (
+ e, error_code::buffer_overflow, "insufficient space in buffer");
+ }
+
+ size_t buffer_serializer::
+ to_chars_impl (char* b, size_t n, const char* f, ...)
+ {
+ va_list a;
+ va_start (a, f);
+ const int r (vsnprintf (b, n, f, a));
+ va_end (a);
+
+ if (r < 0 || r >= static_cast<int> (n))
+ {
+ throw invalid_json_output (event::number,
+ error_code::invalid_value,
+ "unable to convert number to string");
+ }
+
+ return static_cast<size_t> (r);
+ }
+ }
+}
diff --git a/libbutl/json/serializer.hxx b/libbutl/json/serializer.hxx
new file mode 100644
index 0000000..5192cb4
--- /dev/null
+++ b/libbutl/json/serializer.hxx
@@ -0,0 +1,413 @@
+#pragma once
+
+#ifdef BUILD2_BOOTSTRAP
+# error JSON serializer not available during bootstrap
+#endif
+
+#include <array>
+#include <iosfwd>
+#include <string>
+#include <vector>
+#include <cstddef> // size_t, nullptr_t
+#include <utility> // pair
+#include <stdexcept> // invalid_argument
+#include <type_traits> // enable_if, is_*
+
+#include <libbutl/optional.hxx> // butl::optional is std::optional or similar.
+
+#include <libbutl/json/event.hxx>
+
+#include <libbutl/export.hxx>
+
+namespace butl
+{
+ // Using the RFC8259 terminology: JSON (output) text, JSON value, object
+ // member.
+ //
+ namespace json
+ {
+ class invalid_json_output: public std::invalid_argument
+ {
+ public:
+ using event_type = json::event;
+
+ enum class error_code
+ {
+ buffer_overflow,
+ unexpected_event,
+ invalid_name,
+ invalid_value
+ };
+
+ invalid_json_output (optional<event_type> event,
+ error_code code,
+ const char* description,
+ std::size_t offset = std::string::npos);
+
+ invalid_json_output (optional<event_type> event,
+ error_code code,
+ const std::string& description,
+ std::size_t offset = std::string::npos);
+
+ // Event that triggered the error. If the error is in the value, then
+ // offset points to the offending byte (for example, the beginning of an
+ // invalid UTF-8 byte sequence). Otherwise, offset is string::npos.
+ //
+ optional<event_type> event;
+ error_code code;
+ std::size_t offset;
+ };
+
+ // The serializer makes sure the resulting JSON is syntactically but not
+ // necessarily semantically correct. For example, it's possible to
+ // serialize a number event with non-numeric data.
+ //
+ // Note that unlike the parser, the serializer is always in the multi-
+ // value mode allowing the serialization of zero or more values. Note also
+ // that while values are separated with newlines, there is no trailing
+ // newline after the last (or only) value and the user is expected to add
+ // it manually if needed.
+ //
+ // Also note that while RFC8259 recommends object members to have unique
+ // names, the serializer does not enforce this.
+ //
+ class LIBBUTL_SYMEXPORT buffer_serializer
+ {
+ public:
+ // Serialize to string growing it as necessary.
+ //
+ // The indentation argument specifies the number of indentation spaces
+ // that should be used for pretty-printing. If 0 is passed, no
+ // pretty-printing is performed.
+ //
+ explicit
+ buffer_serializer (std::string&, std::size_t indentation = 2);
+
+ // Serialize to vector of characters growing it as necessary.
+ //
+ explicit
+ buffer_serializer (std::vector<char>&, std::size_t indentation = 2);
+
+ // Serialize to a fixed array.
+ //
+ // The length of the output text written is tracked in the size
+ // argument.
+ //
+ // If the array is not big enough to store the entire output text, the
+ // next() call that reaches the limit will throw invalid_json_output.
+ //
+ template <std::size_t N>
+ buffer_serializer (std::array<char, N>&, std::size_t& size,
+ std::size_t indentation = 2);
+
+ // Serialize to a fixed buffer.
+ //
+ // The length of the output text written is tracked in the size
+ // argument.
+ //
+ // If the buffer is not big enough to store the entire output text, the
+ // next() call that reaches the limit will throw invalid_json_output.
+ //
+ buffer_serializer (void* buf, std::size_t& size, std::size_t capacity,
+ std::size_t indentation = 2);
+
+ // The overflow function is called when the output buffer is out of
+ // space. The extra argument is a hint indicating the extra space likely
+ // to be required.
+ //
+ // Possible strategies include re-allocating a larger buffer or flushing
+ // the contents of the original buffer to the output destination. In
+ // case of a reallocation, the implementation is responsible for copying
+ // the contents of the original buffer over.
+ //
+ // The flush function is called when the complete JSON value has been
+ // serialized to the buffer. It can be used to write the contents of the
+ // buffer to the output destination. Note that flush is not called after
+ // the second absent (nullopt) event (or the only absent event; see
+ // next() for details).
+ //
+ // Both functions are passed the original buffer, its size (the amount
+ // of output text), and its capacity. They return (by modifying the
+ // argument) the replacement buffer and its size and capacity (these may
+ // refer to the original buffer). If space cannot be made available, the
+ // implementation can throw an appropriate exception (for example,
+ // std::bad_alloc or std::ios_base::failure). Any exceptions thrown is
+ // propagated to the user.
+ //
+ struct buffer
+ {
+ void* data;
+ std::size_t& size;
+ std::size_t capacity;
+ };
+
+ using overflow_function = void (void* data,
+ event,
+ buffer&,
+ std::size_t extra);
+ using flush_function = void (void* data, event, buffer&);
+
+ // Serialize using a custom buffer and overflow/flush functions (both
+ // are optional).
+ //
+ buffer_serializer (void* buf, std::size_t capacity,
+ overflow_function*,
+ flush_function*,
+ void* data,
+ std::size_t indentation = 2);
+
+ // As above but the length of the output text written is tracked in the
+ // size argument.
+ //
+ buffer_serializer (void* buf, std::size_t& size, std::size_t capacity,
+ overflow_function*,
+ flush_function*,
+ void* data,
+ std::size_t indentation = 2);
+
+ // Begin/end an object.
+ //
+ // The member_begin_object() version is a shortcut for:
+ //
+ // member_name (name, check);
+ // begin_object ();
+ //
+ void
+ begin_object ();
+
+ void
+ member_begin_object (const char*, bool check = true);
+
+ void
+ member_begin_object (const std::string&, bool check = true);
+
+ void
+ end_object ();
+
+ // Serialize an object member (name and value).
+ //
+ // If check is false, then don't check whether the name (or value, if
+ // it's a string) is valid UTF-8 and don't escape any characters.
+ //
+ template <typename T>
+ void
+ member (const char* name, const T& value, bool check = true);
+
+ template <typename T>
+ void
+ member (const std::string& name, const T& value, bool check = true);
+
+ // Serialize an object member name.
+ //
+ // If check is false, then don't check whether the name is valid UTF-8
+ // and don't escape any characters.
+ //
+ void
+ member_name (const char*, bool check = true);
+
+ void
+ member_name (const std::string&, bool check = true);
+
+ // Begin/end an array.
+ //
+ // The member_begin_array() version is a shortcut for:
+ //
+ // member_name (name, check);
+ // begin_array ();
+ //
+ void
+ begin_array ();
+
+ void
+ member_begin_array (const char*, bool check = true);
+
+ void
+ member_begin_array (const std::string&, bool check = true);
+
+ void
+ end_array ();
+
+ // Serialize a string.
+ //
+ // If check is false, then don't check whether the value is valid UTF-8
+ // and don't escape any characters.
+ //
+ // Note that a NULL C-string pointer is serialized as a null value.
+ //
+ void
+ value (const char*, bool check = true);
+
+ void
+ value (const std::string&, bool check = true);
+
+ // Serialize a number.
+ //
+ template <typename T>
+ typename std::enable_if<std::is_integral<T>::value ||
+ std::is_floating_point<T>::value>::type
+ value (T);
+
+ // Serialize a boolean value.
+ //
+ void
+ value (bool);
+
+ // Serialize a null value.
+ //
+ void
+ value (std::nullptr_t);
+
+ // Serialize value as a pre-serialized JSON value.
+ //
+ // Note that the value is expected to be a valid (and suitable) UTF-8-
+ // encoded JSON text. Note also that if pretty-printing is enabled,
+ // the resulting output may not be correctly indented.
+ //
+ void
+ value_json_text (const char*);
+
+ void
+ value_json_text (const std::string&);
+
+ // Serialize next JSON event.
+ //
+ // If check is false, then don't check whether the value is valid UTF-8
+ // and don't escape any characters.
+ //
+ // Return true if more events are required to complete the (top-level)
+ // value (that is, it is currently incomplete) and false otherwise.
+ // Throw invalid_json_output exception in case of an invalid event or
+ // value.
+ //
+ // At the end of the value an optional absent (nullopt) event can be
+ // serialized to verify the value is complete. If it is incomplete an
+ // invalid_json_output exception is thrown. An optional followup absent
+ // event can be serialized to indicate the completion of a multi-value
+ // sequence (one and only absent event indicates a zero value sequence).
+ // If anything is serialized to a complete value sequence an
+ // invalid_json_output exception is thrown.
+ //
+ // Note that this function was designed to be easily invoked with the
+ // output from parser::next() and parser::data(). For example, for a
+ // single-value mode:
+ //
+ // optional<event> e;
+ // do
+ // {
+ // e = p.next ();
+ // s.next (e, p.data ());
+ // }
+ // while (e);
+ //
+ // For a multi-value mode:
+ //
+ // while (p.peek ())
+ // {
+ // optional<event> e;
+ // do
+ // {
+ // e = p.next ();
+ // s.next (e, p.data ());
+ // }
+ // while (e);
+ // }
+ // s.next (nullopt); // End of value sequence.
+ //
+ bool
+ next (optional<event> event,
+ std::pair<const char*, std::size_t> value = {},
+ bool check = true);
+
+ private:
+ void
+ write (event,
+ std::pair<const char*, std::size_t> sep,
+ std::pair<const char*, std::size_t> val,
+ bool check, char quote = '\0');
+
+ // Forward a value(v, check) call to value(v) ignoring the check
+ // argument. Used in the member() implementation.
+ //
+ template <typename T>
+ void
+ value (const T& v, bool /*check*/)
+ {
+ value (v);
+ }
+
+ // Convert numbers to string.
+ //
+ static std::size_t to_chars (char*, std::size_t, int);
+ static std::size_t to_chars (char*, std::size_t, long);
+ static std::size_t to_chars (char*, std::size_t, long long);
+ static std::size_t to_chars (char*, std::size_t, unsigned int);
+ static std::size_t to_chars (char*, std::size_t, unsigned long);
+ static std::size_t to_chars (char*, std::size_t, unsigned long long);
+ static std::size_t to_chars (char*, std::size_t, double);
+ static std::size_t to_chars (char*, std::size_t, long double);
+
+ static std::size_t to_chars_impl (char*, size_t, const char* fmt, ...);
+
+ buffer buf_;
+ std::size_t size_;
+ overflow_function* overflow_;
+ flush_function* flush_;
+ void* data_;
+
+ // State of a "structured type" (array or object; as per the RFC
+ // terminology).
+ //
+ struct state
+ {
+ const event type; // Type kind (begin_array or begin_object).
+ std::size_t count; // Number of events serialized inside this type.
+ };
+
+ // Stack of nested structured type states.
+ //
+ // @@ TODO: would have been nice to use small_vector.
+ //
+ std::vector<state> state_;
+
+ // The number of consecutive absent events (nullopt) serialized thus
+ // far.
+ //
+ // Note: initialized to 1 to naturally handle a single absent event
+ // (declares an empty value sequence complete).
+ //
+ std::size_t absent_ = 1;
+
+ // The number of spaces with which to indent (once for each level of
+ // nesting). If zero, pretty-printing is disabled.
+ //
+ std::size_t indent_;
+
+ // Separator and indentation before/after value inside an object or
+ // array (see pretty-printing implementation for details).
+ //
+ std::string sep_;
+
+ // The number of complete top-level values serialized thus far.
+ //
+ std::size_t values_ = 0;
+ };
+
+ class LIBBUTL_SYMEXPORT stream_serializer: public buffer_serializer
+ {
+ public:
+ // Serialize to std::ostream.
+ //
+ // If stream exceptions are enabled then the std::ios_base::failure
+ // exception is used to report input/output errors (badbit and failbit).
+ // Otherwise, those are reported as the invalid_json_output exception.
+ //
+ explicit
+ stream_serializer (std::ostream&, std::size_t indentation = 2);
+
+ protected:
+ char tmp_[4096];
+ };
+ }
+}
+
+#include <libbutl/json/serializer.ixx>
diff --git a/libbutl/json/serializer.ixx b/libbutl/json/serializer.ixx
new file mode 100644
index 0000000..a719ef6
--- /dev/null
+++ b/libbutl/json/serializer.ixx
@@ -0,0 +1,247 @@
+#include <cstring> // strlen()
+
+namespace butl
+{
+ namespace json
+ {
+ inline invalid_json_output::
+ invalid_json_output (optional<event_type> e,
+ error_code c,
+ const char* d,
+ std::size_t o)
+ : std::invalid_argument (d), event (e), code (c), offset (o)
+ {
+ }
+
+ inline invalid_json_output::
+ invalid_json_output (optional<event_type> e,
+ error_code c,
+ const std::string& d,
+ std::size_t o)
+ : invalid_json_output (e, c, d.c_str (), o)
+ {
+ }
+
+ inline buffer_serializer::
+ buffer_serializer (void* b, std::size_t& s, std::size_t c,
+ overflow_function* o, flush_function* f, void* d,
+ std::size_t i)
+ : buf_ {b, s, c},
+ overflow_ (o),
+ flush_ (f),
+ data_ (d),
+ indent_ (i),
+ sep_ (indent_ != 0 ? ",\n" : "")
+ {
+ }
+
+ template <std::size_t N>
+ inline buffer_serializer::
+ buffer_serializer (std::array<char, N>& a, std::size_t& s, std::size_t i)
+ : buffer_serializer (a.data (), s, a.size (),
+ nullptr, nullptr, nullptr,
+ i)
+ {
+ }
+
+ inline buffer_serializer::
+ buffer_serializer (void* b, std::size_t& s, std::size_t c, std::size_t i)
+ : buffer_serializer (b, s, c, nullptr, nullptr, nullptr, i)
+ {
+ }
+
+ inline buffer_serializer::
+ buffer_serializer (void* b, std::size_t c,
+ overflow_function* o, flush_function* f, void* d,
+ std::size_t i)
+ : buffer_serializer (b, size_, c, o, f, d, i)
+ {
+ size_ = 0;
+ }
+
+ inline void buffer_serializer::
+ begin_object ()
+ {
+ next (event::begin_object);
+ }
+
+ inline void buffer_serializer::
+ end_object ()
+ {
+ next (event::end_object);
+ }
+
+ inline void buffer_serializer::
+ member_name (const char* n, bool c)
+ {
+ next (event::name, {n, n != nullptr ? std::strlen (n) : 0}, c);
+ }
+
+ inline void buffer_serializer::
+ member_name (const std::string& n, bool c)
+ {
+ next (event::name, {n.c_str (), n.size ()}, c);
+ }
+
+ inline void buffer_serializer::
+ member_begin_object (const char* n, bool c)
+ {
+ member_name (n, c);
+ begin_object ();
+ }
+
+ inline void buffer_serializer::
+ member_begin_object (const std::string& n, bool c)
+ {
+ member_name (n, c);
+ begin_object ();
+ }
+
+ template <typename T>
+ inline void buffer_serializer::
+ member (const char* n, const T& v, bool c)
+ {
+ member_name (n, c);
+ value (v, c);
+ }
+
+ template <typename T>
+ inline void buffer_serializer::
+ member (const std::string& n, const T& v, bool c)
+ {
+ member_name (n, c);
+ value (v, c);
+ }
+
+ inline void buffer_serializer::
+ begin_array ()
+ {
+ next (event::begin_array);
+ }
+
+ inline void buffer_serializer::
+ member_begin_array (const char* n, bool c)
+ {
+ member_name (n, c);
+ begin_array ();
+ }
+
+ inline void buffer_serializer::
+ member_begin_array (const std::string& n, bool c)
+ {
+ member_name (n, c);
+ begin_array ();
+ }
+
+ inline void buffer_serializer::
+ end_array ()
+ {
+ next (event::end_array);
+ }
+
+ inline void buffer_serializer::
+ value (const char* v, bool c)
+ {
+ if (v != nullptr)
+ next (event::string, {v, std::strlen (v)}, c);
+ else
+ next (event::null);
+ }
+
+ inline void buffer_serializer::
+ value (const std::string& v, bool c)
+ {
+ next (event::string, {v.c_str (), v.size ()}, c);
+ }
+
+ template <typename T>
+ typename std::enable_if<std::is_integral<T>::value ||
+ std::is_floating_point<T>::value>::type
+ buffer_serializer::
+ value (T v)
+ {
+ // The largest 128-bit integer has 39 digits, and long floating point
+ // numbers will fit because they are output in scientific notation.
+ //
+ char b[40];
+ const std::size_t n (to_chars (b, sizeof (b), v));
+ next (event::number, {b, n});
+ }
+
+ inline void buffer_serializer::
+ value (bool b)
+ {
+ next (event::boolean,
+ b ? std::make_pair ("true", 4) : std::make_pair ("false", 5));
+ }
+
+ inline void buffer_serializer::
+ value (std::nullptr_t)
+ {
+ next (event::null);
+ }
+
+ inline void buffer_serializer::
+ value_json_text (const char* v)
+ {
+ // Use event::number (which doesn't involve any quoting) with a disabled
+ // check.
+ //
+ next (event::number, {v, std::strlen (v)}, false /* check */);
+ }
+
+ inline void buffer_serializer::
+ value_json_text (const std::string& v)
+ {
+ next (event::number, {v.c_str (), v.size ()}, false /* check */);
+ }
+
+ inline size_t buffer_serializer::
+ to_chars (char* b, size_t s, int v)
+ {
+ return to_chars_impl (b, s, "%d", v);
+ }
+
+ inline size_t buffer_serializer::
+ to_chars (char* b, size_t s, long v)
+ {
+ return to_chars_impl (b, s, "%ld", v);
+ }
+
+ inline size_t buffer_serializer::
+ to_chars (char* b, size_t s, long long v)
+ {
+ return to_chars_impl (b, s, "%lld", v);
+ }
+
+ inline size_t buffer_serializer::
+ to_chars (char* b, size_t s, unsigned v)
+ {
+ return to_chars_impl (b, s, "%u", v);
+ }
+
+ inline size_t buffer_serializer::
+ to_chars (char* b, size_t s, unsigned long v)
+ {
+ return to_chars_impl (b, s, "%lu", v);
+ }
+
+ inline size_t buffer_serializer::
+ to_chars (char* b, size_t s, unsigned long long v)
+ {
+ return to_chars_impl (b, s, "%llu", v);
+ }
+
+ inline size_t buffer_serializer::
+ to_chars (char* b, size_t s, double v)
+ {
+ return to_chars_impl (b, s, "%.10g", v);
+ }
+
+ inline size_t buffer_serializer::
+ to_chars (char* b, size_t s, long double v)
+ {
+ return to_chars_impl (b, s, "%.10Lg", v);
+ }
+ }
+}
diff --git a/libbutl/lz4-stream.cxx b/libbutl/lz4-stream.cxx
index 9d0ac99..8001770 100644
--- a/libbutl/lz4-stream.cxx
+++ b/libbutl/lz4-stream.cxx
@@ -6,7 +6,7 @@
#include <cstring> // memcpy()
#include <stdexcept> // invalid_argument
-#include <libbutl/utility.mxx> // eof()
+#include <libbutl/utility.hxx> // eof()
using namespace std;
diff --git a/libbutl/lz4-stream.hxx b/libbutl/lz4-stream.hxx
index 5a0e3ba..b11c0a2 100644
--- a/libbutl/lz4-stream.hxx
+++ b/libbutl/lz4-stream.hxx
@@ -12,7 +12,7 @@
#include <cassert>
#include <libbutl/lz4.hxx>
-#include <libbutl/optional.mxx>
+#include <libbutl/optional.hxx>
#include <libbutl/bufstreambuf.hxx>
#include <libbutl/export.hxx>
@@ -75,7 +75,7 @@ namespace butl
//
// try
// {
- // ifdstream ifs (..., ifdstream::badbit);
+ // ifdstream ifs (..., fdopen_mode::binary, ifdstream::badbit);
// lz4::istream izs (ifs, true /* end */);
// ... // Read from izs.
// }
@@ -206,7 +206,7 @@ namespace butl
//
// try
// {
- // ofdstream ofs (...);
+ // ofdstream ofs (..., fdopen_mode::binary);
// lz4::ostream ozs (ofs, 9, 4 /* 64KB */, nullopt /* content_size */);
//
// ... // Write to ozs.
diff --git a/libbutl/lz4.c b/libbutl/lz4.c
index eac0541..3f0e430 100644
--- a/libbutl/lz4.c
+++ b/libbutl/lz4.c
@@ -343,14 +343,14 @@ static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; }
/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */
/* currently only defined for gcc and icc */
-typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) unalign;
+typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) LZ4_unalign;
-static U16 LZ4_read16(const void* ptr) { return ((const unalign*)ptr)->u16; }
-static U32 LZ4_read32(const void* ptr) { return ((const unalign*)ptr)->u32; }
-static reg_t LZ4_read_ARCH(const void* ptr) { return ((const unalign*)ptr)->uArch; }
+static U16 LZ4_read16(const void* ptr) { return ((const LZ4_unalign*)ptr)->u16; }
+static U32 LZ4_read32(const void* ptr) { return ((const LZ4_unalign*)ptr)->u32; }
+static reg_t LZ4_read_ARCH(const void* ptr) { return ((const LZ4_unalign*)ptr)->uArch; }
-static void LZ4_write16(void* memPtr, U16 value) { ((unalign*)memPtr)->u16 = value; }
-static void LZ4_write32(void* memPtr, U32 value) { ((unalign*)memPtr)->u32 = value; }
+static void LZ4_write16(void* memPtr, U16 value) { ((LZ4_unalign*)memPtr)->u16 = value; }
+static void LZ4_write32(void* memPtr, U32 value) { ((LZ4_unalign*)memPtr)->u32 = value; }
#else /* safe and portable access using memcpy() */
diff --git a/libbutl/lz4.cxx b/libbutl/lz4.cxx
index a627b06..2db7af2 100644
--- a/libbutl/lz4.cxx
+++ b/libbutl/lz4.cxx
@@ -26,7 +26,7 @@
#include <cassert>
#include <stdexcept> // invalid_argument, logic_error
-#include <libbutl/utility.mxx> // eos()
+#include <libbutl/utility.hxx> // eos()
#if 0
#include <libbutl/lz4-stream.hxx>
diff --git a/libbutl/lz4.hxx b/libbutl/lz4.hxx
index cfe9967..7886788 100644
--- a/libbutl/lz4.hxx
+++ b/libbutl/lz4.hxx
@@ -6,8 +6,8 @@
#include <cstdint>
#include <cstddef>
-#include <libbutl/optional.mxx>
-#include <libbutl/fdstream.mxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/fdstream.hxx>
#include <libbutl/export.hxx>
diff --git a/libbutl/lz4frame.c b/libbutl/lz4frame.c
index ec02c92..0db8c1e 100644
--- a/libbutl/lz4frame.c
+++ b/libbutl/lz4frame.c
@@ -904,8 +904,8 @@ size_t LZ4F_compressUpdate(LZ4F_cctx* cctxPtr,
}
/* keep tmpIn within limits */
- if ((cctxPtr->tmpIn + blockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize) /* necessarily LZ4F_blockLinked && lastBlockCompressed==fromTmpBuffer */
- && !(cctxPtr->prefs.autoFlush))
+ if (!(cctxPtr->prefs.autoFlush) &&
+ (cctxPtr->tmpIn + blockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize)) /* necessarily LZ4F_blockLinked && lastBlockCompressed==fromTmpBuffer */
{
int const realDictSize = LZ4F_localSaveDict(cctxPtr);
cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize;
diff --git a/libbutl/manifest-parser.cxx b/libbutl/manifest-parser.cxx
index 9514bbd..904910a 100644
--- a/libbutl/manifest-parser.cxx
+++ b/libbutl/manifest-parser.cxx
@@ -1,39 +1,10 @@
// file : libbutl/manifest-parser.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/manifest-parser.mxx>
-#endif
+#include <libbutl/manifest-parser.hxx>
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
-#include <vector>
-#include <cstdint>
-#include <utility>
-#include <stdexcept>
-
-#include <sstream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.manifest_parser;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.optional;
-import butl.char_scanner;
-import butl.manifest_types;
-#endif
-
-#endif
+#include <cassert>
using namespace std;
@@ -177,41 +148,136 @@ namespace butl
{
using iterator = string::const_iterator;
- auto space = [] (char c) -> bool {return c == ' ' || c == '\t';};
+ // Parse the value differently depending on whether it is multi-line or
+ // not.
+ //
+ if (v.find ('\n') == string::npos) // Single-line.
+ {
+ auto space = [] (char c) {return c == ' ' || c == '\t';};
- iterator i (v.begin ());
- iterator e (v.end ());
+ iterator i (v.begin ());
+ iterator e (v.end ());
- string r;
- size_t n (0);
- for (char c; i != e && (c = *i) != ';'; ++i)
- {
- // Unescape ';' character.
+ string r;
+ size_t n (0);
+ for (char c; i != e && (c = *i) != ';'; ++i)
+ {
+ // Unescape ';' and '\' characters.
+ //
+ if (c == '\\' && i + 1 != e && (*(i + 1) == ';' || *(i + 1) == '\\'))
+ c = *++i;
+
+ r += c;
+
+ if (!space (c))
+ n = r.size ();
+ }
+
+ // Strip the value trailing spaces.
//
- if (c == '\\' && i + 1 != e && *(i + 1) == ';')
- c = *++i;
+ if (r.size () != n)
+ r.resize (n);
- r += c;
+ // Find beginning of a comment (i).
+ //
+ if (i != e)
+ {
+ // Skip spaces.
+ //
+ for (++i; i != e && space (*i); ++i);
+ }
- if (!space (c))
- n = r.size ();
+ return make_pair (move (r), string (i, e));
}
+ else // Multi-line.
+ {
+ string r;
+ string c;
- // Strip the value trailing spaces.
- //
- if (r.size () != n)
- r.resize (n);
+ // Parse the value lines until the comment separator is encountered or
+ // the end of the value is reached. Add these lines to the resulting
+ // value, unescaping them if required.
+ //
+ // Note that we only need to unescape lines which have the '\+;' form.
+ //
+ auto i (v.begin ());
+ auto e (v.end ());
- // Find beginning of a comment (i).
- //
- if (i != e)
- {
- // Skip spaces.
+ while (i != e)
+ {
+ // Find the end of the line and while at it the first non-backslash
+ // character.
+ //
+ auto le (i);
+ auto nb (e);
+ for (; le != e && *le != '\n'; ++le)
+ {
+ if (nb == e && *le != '\\')
+ nb = le;
+ }
+
+ // If the value end is not reached then position to the beginning of
+ // the next line and to the end of the value otherwise.
+ //
+ auto next = [&i, &le, &e] () {i = (le != e ? le + 1 : e);};
+
+ // If the first non-backslash character is ';' and it is the last
+ // character on the line, then this is either the comment separator or
+ // an escape sequence.
+ //
+ if (nb != e && *nb == ';' && nb + 1 == le)
+ {
+ // If ';' is the first (and thus the only) character on the line,
+ // then this is the comment separator and we bail out from this
+ // loop. Note that in this case we need to trim the trailing newline
+ // (but only one) from the resulting value since it is considered as
+ // a part of the separator.
+ //
+ if (nb == i)
+ {
+ if (!r.empty ())
+ {
+ assert (r.back () == '\n');
+ r.pop_back ();
+ }
+
+ next ();
+ break;
+ }
+ //
+ // Otherwise, this is an escape sequence, so unescape it. For that
+ // just take the rightmost half of the string:
+ //
+ // \; -> ;
+ // \\; -> \;
+ // \\\; -> \;
+ // \\\\; -> \\;
+ // \\\\\; -> \\;
+ //
+ else
+ i += (le - i) / 2;
+ }
+
+ // Add the line to the resulting value together with the trailing
+ // newline, if present.
+ //
+ r.append (i, le);
+
+ if (le != e)
+ r += '\n';
+
+ next ();
+ }
+
+ // If we haven't reached the end of the value then it means we've
+ // encountered the comment separator. In this case save the remaining
+ // value part as a comment.
//
- for (++i; i != e && space (*i); ++i);
- }
+ if (i != e)
+ c = string (i, e);
- return make_pair (move (r), string (i, e));
+ return make_pair (move (r), move (c));
+ }
}
void manifest_parser::
@@ -251,7 +317,8 @@ namespace butl
string& v (r.value);
string::size_type n (0); // Size of last non-space character (simple mode).
- // Detect the multi-line mode introductor.
+ // Detect the old-fashioned multi-line mode introducer (like in
+ // 'foo:\<newline>').
//
bool ml (false);
if (c == '\\')
@@ -266,11 +333,46 @@ namespace butl
ml = true;
}
else if (eos (p))
+ {
+ c = p; // Set to EOF.
ml = true;
+ }
else
unget (c);
}
+ // Detect the new-fashioned multi-line mode introducer (like in
+ // 'foo:<newline>\<newline>').
+ //
+ if (!ml && c == '\n')
+ {
+ get ();
+ xchar p1 (peek ());
+
+ if (p1 == '\\')
+ {
+ get ();
+ xchar p2 (peek ());
+
+ if (p2 == '\n')
+ {
+ get (); // Newline is not part of the value so skip it.
+ c = peek ();
+ ml = true;
+ }
+ else if (eos (p2))
+ {
+ c = p2; // Set to EOF.
+ ml = true;
+ }
+ else
+ unget (p1); // Unget '\\'. Note: '\n' will be ungot below.
+ }
+
+ if (!ml)
+ unget (c); // Unget '\n'.
+ }
+
// Multi-line value starts from the line that follows the name.
//
if (ml)
@@ -281,7 +383,7 @@ namespace butl
// The nl flag signals that the preceding character was a "special
// newline", that is, a newline that was part of the milti-line mode
- // introductor or an escape sequence.
+ // introducer or an escape sequence.
//
for (bool nl (ml); !eos (c); c = peek ())
{
@@ -299,7 +401,7 @@ namespace butl
//
// The first block handles the special sequence that starts with
// a special newline. In multi-line mode, this is an "immediate
- // termination" where we "use" the newline from the introductor.
+ // termination" where we "use" the newline from the introducer.
// Note also that in the simple mode the special sequence can
// only start with a special (i.e., escaped) newline.
//
@@ -472,11 +574,21 @@ namespace butl
static inline string
format (const string& n, uint64_t l, uint64_t c, const string& d)
{
- ostringstream os;
+ using std::to_string;
+
+ string r;
if (!n.empty ())
- os << n << ':';
- os << l << ':' << c << ": error: " << d;
- return os.str ();
+ {
+ r += n;
+ r += ':';
+ }
+
+ r += to_string (l);
+ r += ':';
+ r += to_string (c);
+ r += ": error: ";
+ r += d;
+ return r;
}
manifest_parsing::
diff --git a/libbutl/manifest-parser.mxx b/libbutl/manifest-parser.hxx
index 77addff..601fb2d 100644
--- a/libbutl/manifest-parser.mxx
+++ b/libbutl/manifest-parser.hxx
@@ -1,13 +1,8 @@
-// file : libbutl/manifest-parser.mxx -*- C++ -*-
+// file : libbutl/manifest-parser.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <iosfwd>
@@ -15,30 +10,15 @@
#include <utility> // pair, move()
#include <stdexcept> // runtime_error
#include <functional>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.manifest_parser;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.utf8;
-import butl.optional;
-import butl.char_scanner;
-import butl.manifest_types;
-#else
-#include <libbutl/utf8.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/char-scanner.mxx>
-#include <libbutl/manifest-types.mxx>
-#endif
+
+#include <libbutl/utf8.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/char-scanner.hxx>
+#include <libbutl/manifest-types.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
class LIBBUTL_SYMEXPORT manifest_parsing: public std::runtime_error
{
@@ -57,7 +37,7 @@ LIBBUTL_MODEXPORT namespace butl
};
class LIBBUTL_SYMEXPORT manifest_parser:
- protected char_scanner<utf8_validator>
+ protected char_scanner<utf8_validator, 2>
{
public:
// The filter, if specified, is called by next() prior to returning the
@@ -103,7 +83,7 @@ LIBBUTL_MODEXPORT namespace butl
split_comment (const std::string&);
private:
- using base = char_scanner<utf8_validator>;
+ using base = char_scanner<utf8_validator, 2>;
void
parse_next (manifest_name_value&);
diff --git a/libbutl/manifest-rewriter.cxx b/libbutl/manifest-rewriter.cxx
index 46bf239..1232e9c 100644
--- a/libbutl/manifest-rewriter.cxx
+++ b/libbutl/manifest-rewriter.cxx
@@ -1,41 +1,15 @@
// file : libbutl/manifest-rewriter.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/manifest-rewriter.mxx>
-#endif
+#include <libbutl/manifest-rewriter.hxx>
-#include <cassert>
-
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
+#include <cassert>
#include <cstdint> // uint64_t
#include <cstddef> // size_t
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.manifest_rewriter;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.fdstream;
-import butl.manifest_types;
-#endif
-
-import butl.utility; // utf8_length()
-import butl.manifest_serializer;
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/manifest-serializer.mxx>
-#endif
+
+#include <libbutl/utility.hxx> // utf8_length()
+#include <libbutl/manifest-serializer.hxx>
using namespace std;
@@ -99,8 +73,6 @@ namespace butl
if (!nv.value.empty ())
{
- os << ' ';
-
manifest_serializer s (os, path_.string (), long_lines_);
// Note that the name can be surrounded with the ASCII whitespace
@@ -112,7 +84,7 @@ namespace butl
//
s.write_value (nv.value,
static_cast<size_t> (nv.colon_pos - nv.start_pos) -
- (nv.name.size () - utf8_length (nv.name)) + 2);
+ (nv.name.size () - utf8_length (nv.name)) + 1);
}
os << suffix;
@@ -144,15 +116,13 @@ namespace butl
if (!nv.value.empty ())
{
- os << ' ';
-
// Note that the name can be surrounded with the ASCII whitespace
// characters and the start_pos refers to the first character in the
// line.
//
s.write_value (nv.value,
static_cast<size_t> (nv.colon_pos - nv.start_pos) -
- (nv.name.size () - n) + 2);
+ (nv.name.size () - n) + 1);
}
os << suffix;
diff --git a/libbutl/manifest-rewriter.mxx b/libbutl/manifest-rewriter.hxx
index 907c990..02a533a 100644
--- a/libbutl/manifest-rewriter.mxx
+++ b/libbutl/manifest-rewriter.hxx
@@ -1,33 +1,15 @@
-// file : libbutl/manifest-rewriter.mxx -*- C++ -*-
+// file : libbutl/manifest-rewriter.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.manifest_rewriter;
-#ifdef __cpp_lib_modules_ts
-#endif
-import butl.path;
-import butl.fdstream;
-import butl.manifest_types;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/manifest-types.mxx>
-#endif
+#include <libbutl/path.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/manifest-types.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Rewrite a hand-written manifest file preserving formatting, comments,
// etc., of the unaffected parts. The general workflow is as follows:
diff --git a/libbutl/manifest-serializer.cxx b/libbutl/manifest-serializer.cxx
index 6a26a15..26699e0 100644
--- a/libbutl/manifest-serializer.cxx
+++ b/libbutl/manifest-serializer.cxx
@@ -1,41 +1,13 @@
// file : libbutl/manifest-serializer.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/manifest-serializer.mxx>
-#endif
-
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <vector>
-#include <cstddef>
-#include <stdexcept>
+#include <libbutl/manifest-serializer.hxx>
#include <ostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.manifest_serializer;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.manifest_types;
-#endif
+#include <cassert>
-import butl.utf8;
-import butl.utility;
-#else
-#include <libbutl/utf8.mxx>
-#include <libbutl/utility.mxx>
-#endif
+#include <libbutl/utf8.hxx>
+#include <libbutl/utility.hxx>
using namespace std;
@@ -95,10 +67,7 @@ namespace butl
os_ << ':';
if (!v.empty ())
- {
- os_ << ' ';
- write_value (v, l + 2);
- }
+ write_value (v, l + 1);
os_ << endl;
break;
@@ -132,22 +101,89 @@ namespace butl
merge_comment (const string& value, const string& comment)
{
string r;
- for (char c: value)
+
+ // Merge the value and comment differently depending on whether any of
+ // them is multi-line or not.
+ //
+ if (value.find ('\n') == string::npos && // Single-line.
+ comment.find ('\n') == string::npos)
{
- // Escape ';' character.
- //
- if (c == ';')
- r += '\\';
+ for (char c: value)
+ {
+ // Escape ';' and '\' characters.
+ //
+ if (c == ';' || c == '\\')
+ r += '\\';
- r += c;
- }
+ r += c;
+ }
- // Add the comment.
- //
- if (!comment.empty ())
+ // Add the comment.
+ //
+ if (!comment.empty ())
+ {
+ r += "; ";
+ r += comment;
+ }
+ }
+ else // Multi-line.
{
- r += "; ";
- r += comment;
+ // Parse the value lines and add them to the resulting value, escaping
+ // them if required.
+ //
+ // Note that we only need to escape lines which have the '\*;' form.
+ //
+ for (auto i (value.begin ()), e (value.end ()); i != e; )
+ {
+ // Find the end of the line and while at it the first non-backslash
+ // character.
+ //
+ auto le (i);
+ auto nb (e);
+ for (; le != e && *le != '\n'; ++le)
+ {
+ if (nb == e && *le != '\\')
+ nb = le;
+ }
+
+ // If the first non-backslash character is ';' and it is the last
+ // character on the line, then we need to escape the line characters.
+ // Note that we only escape ';' if it is the only character on the
+ // line. Otherwise, we only escape backslashes doubling the number of
+ // them from the left:
+ //
+ // ; -> \;
+ // \; -> \\;
+ // \\; -> \\\\;
+ // \\\; -> \\\\\\;
+ //
+ if (nb != e && *nb == ';' && nb + 1 == le)
+ r.append (nb == i ? 1 : nb - i, '\\');
+
+ // Add the line to the resulting value together with the trailing
+ // newline, if present.
+ //
+ r.append (i, le);
+
+ if (le != e)
+ r += '\n';
+
+ // If the value end is not reached then position to the beginning of
+ // the next line and to the end of the value otherwise.
+ //
+ i = (le != e ? le + 1 : e);
+ }
+
+ // Append the comment, if present.
+ //
+ if (!comment.empty ())
+ {
+ if (!r.empty ())
+ r += '\n';
+
+ r += ";\n";
+ r += comment;
+ }
}
return r;
@@ -301,6 +337,8 @@ namespace butl
void manifest_serializer::
write_value (const string& v, size_t cl)
{
+ assert (!v.empty ());
+
// Consider both \r and \n characters as line separators, and the
// \r\n characters sequence as a single line separator.
//
@@ -319,11 +357,17 @@ namespace butl
// readability, still allowing the user to easily copy the value which
// seems to be the main reason for using the flag.
//
- if (cl > 39 || nl () != string::npos ||
- v.front () == ' ' || v.front () == '\t' ||
- v.back () == ' ' || v.back () == '\t')
+ if (cl + 1 > 39 || // '+ 1' for the space after the colon.
+ nl () != string::npos ||
+ v.front () == ' ' ||
+ v.front () == '\t' ||
+ v.back () == ' ' ||
+ v.back () == '\t')
{
- os_ << "\\" << endl; // Multi-line mode introductor.
+ if (multiline_v2_)
+ os_ << endl;
+
+ os_ << "\\" << endl; // Multi-line mode introducer.
// Chunk the value into fragments separated by newlines.
//
@@ -346,7 +390,10 @@ namespace butl
os_ << endl << "\\"; // Multi-line mode terminator.
}
else
- write_value (v.c_str (), v.size (), cl);
+ {
+ os_ << ' ';
+ write_value (v.c_str (), v.size (), cl + 1);
+ }
}
// manifest_serialization
diff --git a/libbutl/manifest-serializer.mxx b/libbutl/manifest-serializer.hxx
index b73c255..2159901 100644
--- a/libbutl/manifest-serializer.mxx
+++ b/libbutl/manifest-serializer.hxx
@@ -1,37 +1,20 @@
-// file : libbutl/manifest-serializer.mxx -*- C++ -*-
+// file : libbutl/manifest-serializer.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <iosfwd>
#include <cstddef> // size_t
#include <stdexcept> // runtime_error
#include <functional>
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-export module butl.manifest_serializer;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.manifest_types;
-#else
-#include <libbutl/manifest-types.mxx>
-#endif
+#include <libbutl/manifest-types.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
class LIBBUTL_SYMEXPORT manifest_serialization: public std::runtime_error
{
@@ -62,14 +45,19 @@ LIBBUTL_MODEXPORT namespace butl
// Unless long_lines is true, break lines in values (including multi-line)
// so that their length does not exceed 78 codepoints (including '\n').
//
+ // Note that the multiline_v2 flag is temporary and should not be used
+ // except by the implementation for testing.
+ //
manifest_serializer (std::ostream& os,
const std::string& name,
bool long_lines = false,
- std::function<filter_function> filter = {})
+ std::function<filter_function> filter = {},
+ bool multiline_v2 = false)
: os_ (os),
name_ (name),
long_lines_ (long_lines),
- filter_ (std::move (filter))
+ filter_ (std::move (filter)),
+ multiline_v2_ (multiline_v2)
{
}
@@ -113,10 +101,12 @@ LIBBUTL_MODEXPORT namespace butl
size_t
write_name (const std::string&);
- // Write a value assuming the current line already has the specified
- // codepoint offset. If the resulting line length would be too large then
- // the multi-line representation will be used. It is assumed that the
- // name, followed by the colon, is already written.
+ // Write a non-empty value assuming the current line already has the
+ // specified codepoint offset. If the resulting line length would be too
+ // large then the multi-line representation will be used. For the
+ // single-line representation the space character is written before the
+ // value. It is assumed that the name, followed by the colon, is already
+ // written.
//
void
write_value (const std::string&, std::size_t offset);
@@ -138,6 +128,7 @@ LIBBUTL_MODEXPORT namespace butl
const std::string name_;
bool long_lines_;
const std::function<filter_function> filter_;
+ bool multiline_v2_;
};
// Serialize a manifest to a stream adding the leading format version pair
diff --git a/libbutl/manifest-types.mxx b/libbutl/manifest-types.hxx
index 93f6fc6..23318f0 100644
--- a/libbutl/manifest-types.mxx
+++ b/libbutl/manifest-types.hxx
@@ -1,30 +1,14 @@
-// file : libbutl/manifest-types.mxx -*- C++ -*-
+// file : libbutl/manifest-types.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
-#include <cstdint> // uint64_t
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.manifest_types;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#else
-#endif
+#include <cstdint> // uint64_t
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
class manifest_name_value
{
diff --git a/libbutl/mingw-condition_variable.hxx b/libbutl/mingw-condition_variable.hxx
new file mode 100644
index 0000000..965f533
--- /dev/null
+++ b/libbutl/mingw-condition_variable.hxx
@@ -0,0 +1,275 @@
+/**
+* std::condition_variable implementation for MinGW-w64
+*
+* Copyright (c) 2013-2016 by Mega Limited, Auckland, New Zealand
+* Copyright (c) 2022 the build2 authors
+*
+* Licensed under the simplified (2-clause) BSD License.
+* You should have received a copy of the license along with this
+* program.
+*
+* This code is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#ifndef LIBBUTL_MINGW_CONDITION_VARIABLE_HXX
+#define LIBBUTL_MINGW_CONDITION_VARIABLE_HXX
+
+#if !defined(__cplusplus) || (__cplusplus < 201402L)
+# error C++14 compiler required
+#endif
+
+#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0601
+# error _WIN32_WINNT should be 0x0601 (Windows 7) or greater
+#endif
+
+#include <condition_variable> // Use std::cv_status, if available.
+
+#include <cassert>
+#include <chrono>
+#include <system_error>
+
+#include <synchapi.h>
+
+#include <libbutl/mingw-mutex.hxx>
+#include <libbutl/mingw-shared_mutex.hxx>
+
+namespace mingw_stdthread
+{
+#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)
+ enum class cv_status { no_timeout, timeout };
+#else
+ using std::cv_status;
+#endif
+
+ // Native condition variable-based implementation.
+ //
+ class condition_variable
+ {
+ static constexpr DWORD kInfinite = 0xffffffffl;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
+ CONDITION_VARIABLE cvariable_ = CONDITION_VARIABLE_INIT;
+#pragma GCC diagnostic pop
+
+ friend class condition_variable_any;
+
+ bool wait_unique (mutex * pmutex, DWORD time)
+ {
+ BOOL success = SleepConditionVariableSRW(native_handle(),
+ pmutex->native_handle(),
+ time,
+// CONDITION_VARIABLE_LOCKMODE_SHARED has a value not specified by
+// Microsoft's Dev Center, but is known to be (convertible to) a ULONG. To
+// ensure that the value passed to this function is not equal to Microsoft's
+// constant, we can either use a static_assert, or simply generate an
+// appropriate value.
+ !CONDITION_VARIABLE_LOCKMODE_SHARED);
+ return success;
+ }
+ bool wait_impl (unique_lock<mutex> & lock, DWORD time)
+ {
+ mutex * pmutex = lock.release();
+ bool success = wait_unique(pmutex, time);
+ lock = unique_lock<mutex>(*pmutex, adopt_lock);
+ return success;
+ }
+public:
+ using native_handle_type = PCONDITION_VARIABLE;
+ native_handle_type native_handle ()
+ {
+ return &cvariable_;
+ }
+
+ condition_variable () = default;
+ ~condition_variable () = default;
+
+ condition_variable (const condition_variable &) = delete;
+ condition_variable & operator= (const condition_variable &) = delete;
+
+ void notify_one () noexcept
+ {
+ WakeConditionVariable(&cvariable_);
+ }
+
+ void notify_all () noexcept
+ {
+ WakeAllConditionVariable(&cvariable_);
+ }
+
+ void wait (unique_lock<mutex> & lock)
+ {
+ wait_impl(lock, kInfinite);
+ }
+
+ template<class Predicate>
+ void wait (unique_lock<mutex> & lock, Predicate pred)
+ {
+ while (!pred())
+ wait(lock);
+ }
+
+ template <class Rep, class Period>
+ cv_status wait_for(unique_lock<mutex>& lock,
+ const std::chrono::duration<Rep, Period>& rel_time)
+ {
+ using namespace std::chrono;
+ auto timeout = duration_cast<milliseconds>(rel_time).count();
+ DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (kInfinite - 1);
+ bool result = wait_impl(lock, waittime) || (timeout >= kInfinite);
+ return result ? cv_status::no_timeout : cv_status::timeout;
+ }
+
+ template <class Rep, class Period, class Predicate>
+ bool wait_for(unique_lock<mutex>& lock,
+ const std::chrono::duration<Rep, Period>& rel_time,
+ Predicate pred)
+ {
+#if __cplusplus >= 201703L
+ using steady_duration = typename std::chrono::steady_clock::duration;
+ return wait_until(lock,
+ std::chrono::steady_clock::now() +
+ std::chrono::ceil<steady_duration> (rel_time),
+ std::move(pred));
+#else
+ return wait_until(lock,
+ std::chrono::steady_clock::now() + rel_time,
+ std::move(pred));
+#endif
+ }
+ template <class Clock, class Duration>
+ cv_status wait_until (unique_lock<mutex>& lock,
+ const std::chrono::time_point<Clock,Duration>& abs_time)
+ {
+ return wait_for(lock, abs_time - Clock::now());
+ }
+ template <class Clock, class Duration, class Predicate>
+ bool wait_until (unique_lock<mutex>& lock,
+ const std::chrono::time_point<Clock, Duration>& abs_time,
+ Predicate pred)
+ {
+ while (!pred())
+ {
+ if (wait_until(lock, abs_time) == cv_status::timeout)
+ {
+ return pred();
+ }
+ }
+ return true;
+ }
+ };
+
+ class condition_variable_any
+ {
+ static constexpr DWORD kInfinite = 0xffffffffl;
+
+ condition_variable internal_cv_ {};
+ mutex internal_mutex_ {};
+
+ template<class L>
+ bool wait_impl (L & lock, DWORD time)
+ {
+ unique_lock<decltype(internal_mutex_)> internal_lock(internal_mutex_);
+ lock.unlock();
+ bool success = internal_cv_.wait_impl(internal_lock, time);
+ lock.lock();
+ return success;
+ }
+ // If the lock happens to be called on a native Windows mutex, skip any
+ // extra contention.
+ inline bool wait_impl (unique_lock<mutex> & lock, DWORD time)
+ {
+ return internal_cv_.wait_impl(lock, time);
+ }
+ bool wait_impl (unique_lock<shared_mutex> & lock, DWORD time)
+ {
+ shared_mutex * pmutex = lock.release();
+ bool success = internal_cv_.wait_unique(pmutex, time);
+ lock = unique_lock<shared_mutex>(*pmutex, adopt_lock);
+ return success;
+ }
+ bool wait_impl (shared_lock<shared_mutex> & lock, DWORD time)
+ {
+ shared_mutex * pmutex = lock.release();
+ BOOL success = SleepConditionVariableSRW(native_handle(),
+ pmutex->native_handle(), time,
+ CONDITION_VARIABLE_LOCKMODE_SHARED);
+ lock = shared_lock<shared_mutex>(*pmutex, adopt_lock);
+ return success;
+ }
+ public:
+ using native_handle_type = typename condition_variable::native_handle_type;
+
+ native_handle_type native_handle ()
+ {
+ return internal_cv_.native_handle();
+ }
+
+ void notify_one () noexcept
+ {
+ internal_cv_.notify_one();
+ }
+
+ void notify_all () noexcept
+ {
+ internal_cv_.notify_all();
+ }
+
+ condition_variable_any () = default;
+ ~condition_variable_any () = default;
+
+ template<class L>
+ void wait (L & lock)
+ {
+ wait_impl(lock, kInfinite);
+ }
+
+ template<class L, class Predicate>
+ void wait (L & lock, Predicate pred)
+ {
+ while (!pred())
+ wait(lock);
+ }
+
+ template <class L, class Rep, class Period>
+ cv_status wait_for(L& lock, const std::chrono::duration<Rep,Period>& period)
+ {
+ using namespace std::chrono;
+ auto timeout = duration_cast<milliseconds>(period).count();
+ DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (kInfinite - 1);
+ bool result = wait_impl(lock, waittime) || (timeout >= kInfinite);
+ return result ? cv_status::no_timeout : cv_status::timeout;
+ }
+
+ template <class L, class Rep, class Period, class Predicate>
+ bool wait_for(L& lock, const std::chrono::duration<Rep, Period>& period,
+ Predicate pred)
+ {
+ return wait_until(lock, std::chrono::steady_clock::now() + period,
+ std::move(pred));
+ }
+ template <class L, class Clock, class Duration>
+ cv_status wait_until (L& lock,
+ const std::chrono::time_point<Clock,Duration>& abs_time)
+ {
+ return wait_for(lock, abs_time - Clock::now());
+ }
+ template <class L, class Clock, class Duration, class Predicate>
+ bool wait_until (L& lock,
+ const std::chrono::time_point<Clock, Duration>& abs_time,
+ Predicate pred)
+ {
+ while (!pred())
+ {
+ if (wait_until(lock, abs_time) == cv_status::timeout)
+ {
+ return pred();
+ }
+ }
+ return true;
+ }
+ };
+}
+
+#endif // LIBBUTL_MINGW_CONDITION_VARIABLE_HXX
diff --git a/libbutl/mingw-invoke.hxx b/libbutl/mingw-invoke.hxx
new file mode 100644
index 0000000..65810e7
--- /dev/null
+++ b/libbutl/mingw-invoke.hxx
@@ -0,0 +1,109 @@
+/**
+* Lightweight std::invoke() implementation for C++11 and C++14
+*
+* Copyright (c) 2018-2019 by Nathaniel J. McClatchey, San Jose, CA, United States
+* Copyright (c) 2022 the build2 authors
+*
+* Licensed under the simplified (2-clause) BSD License.
+* You should have received a copy of the license along with this
+* program.
+*
+* This code is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#ifndef LIBBUTL_MINGW_INVOKE_HXX
+#define LIBBUTL_MINGW_INVOKE_HXX
+
+#include <type_traits> // For std::result_of, etc.
+#include <utility> // For std::forward
+#include <functional> // For std::reference_wrapper
+
+namespace mingw_stdthread
+{
+ namespace detail
+ {
+ // For compatibility, implement std::invoke for C++11 and C++14.
+ //
+ template<bool PMemFunc, bool PMemData>
+ struct Invoker
+ {
+ template<class F, class... Args>
+ inline static typename std::result_of<F(Args...)>::type invoke (F&& f, Args&&... args)
+ {
+ return std::forward<F>(f)(std::forward<Args>(args)...);
+ }
+ };
+ template<bool>
+ struct InvokerHelper;
+
+ template<>
+ struct InvokerHelper<false>
+ {
+ template<class T1>
+ inline static auto get (T1&& t1) -> decltype(*std::forward<T1>(t1))
+ {
+ return *std::forward<T1>(t1);
+ }
+
+ template<class T1>
+ inline static auto get (const std::reference_wrapper<T1>& t1) -> decltype(t1.get())
+ {
+ return t1.get();
+ }
+ };
+
+ template<>
+ struct InvokerHelper<true>
+ {
+ template<class T1>
+ inline static auto get (T1&& t1) -> decltype(std::forward<T1>(t1))
+ {
+ return std::forward<T1>(t1);
+ }
+ };
+
+ template<>
+ struct Invoker<true, false>
+ {
+ template<class T, class F, class T1, class... Args>
+ inline static auto invoke (F T::* f, T1&& t1, Args&&... args) -> \
+ decltype((InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...))
+ {
+ return (InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
+ }
+ };
+
+ template<>
+ struct Invoker<false, true>
+ {
+ template<class T, class F, class T1, class... Args>
+ inline static auto invoke (F T::* f, T1&& t1, Args&&... args) -> \
+ decltype(InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f)
+ {
+ return InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f;
+ }
+ };
+
+ template<class F, class... Args>
+ struct InvokeResult
+ {
+ typedef Invoker<std::is_member_function_pointer<typename std::remove_reference<F>::type>::value,
+ std::is_member_object_pointer<typename std::remove_reference<F>::type>::value &&
+ (sizeof...(Args) == 1)> invoker;
+ inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...))
+ {
+ return invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...);
+ }
+ };
+
+ template<class F, class...Args>
+ auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...))
+ {
+ return InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...);
+ }
+ }
+}
+
+#endif // LIBBUTL_MINGW_INVOKE_HXX
diff --git a/libbutl/mingw-mutex.hxx b/libbutl/mingw-mutex.hxx
new file mode 100644
index 0000000..d297786
--- /dev/null
+++ b/libbutl/mingw-mutex.hxx
@@ -0,0 +1,210 @@
+/**
+* std::mutex et al implementation for MinGW-w64
+*
+* Copyright (c) 2013-2016 by Mega Limited, Auckland, New Zealand
+* Copyright (c) 2022 the build2 authors
+*
+* Licensed under the simplified (2-clause) BSD License.
+* You should have received a copy of the license along with this
+* program.
+*
+* This code is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#ifndef LIBBUTL_MINGW_MUTEX_HXX
+#define LIBBUTL_MINGW_MUTEX_HXX
+
+#if !defined(__cplusplus) || (__cplusplus < 201402L)
+# error C++14 compiler required
+#endif
+
+#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0601
+# error _WIN32_WINNT should be 0x0601 (Windows 7) or greater
+#endif
+
+#include <chrono>
+#include <system_error>
+#include <atomic>
+
+#include <mutex>
+
+#include <synchapi.h> // For InitializeCriticalSection, etc.
+#include <errhandlingapi.h> // For GetLastError
+#include <handleapi.h>
+
+namespace mingw_stdthread
+{
+ // To make this namespace equivalent to the thread-related subset of std,
+ // pull in the classes and class templates supplied by std but not by this
+ // implementation.
+ //
+ using std::lock_guard;
+ using std::unique_lock;
+ using std::adopt_lock_t;
+ using std::defer_lock_t;
+ using std::try_to_lock_t;
+ using std::adopt_lock;
+ using std::defer_lock;
+ using std::try_to_lock;
+
+ class recursive_mutex
+ {
+ CRITICAL_SECTION mHandle;
+ public:
+ typedef LPCRITICAL_SECTION native_handle_type;
+ native_handle_type native_handle() {return &mHandle;}
+ recursive_mutex() noexcept : mHandle()
+ {
+ InitializeCriticalSection(&mHandle);
+ }
+ recursive_mutex (const recursive_mutex&) = delete;
+ recursive_mutex& operator=(const recursive_mutex&) = delete;
+ ~recursive_mutex() noexcept
+ {
+ DeleteCriticalSection(&mHandle);
+ }
+ void lock()
+ {
+ EnterCriticalSection(&mHandle);
+ }
+ void unlock()
+ {
+ LeaveCriticalSection(&mHandle);
+ }
+ bool try_lock()
+ {
+ return (TryEnterCriticalSection(&mHandle)!=0);
+ }
+ };
+
+ // Slim Reader-Writer (SRW)-based implementation that requires Windows 7.
+ //
+ class mutex
+ {
+ protected:
+ SRWLOCK mHandle;
+ public:
+ typedef PSRWLOCK native_handle_type;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
+ constexpr mutex () noexcept : mHandle(SRWLOCK_INIT) { }
+#pragma GCC diagnostic pop
+ mutex (const mutex&) = delete;
+ mutex & operator= (const mutex&) = delete;
+ void lock ()
+ {
+ AcquireSRWLockExclusive(&mHandle);
+ }
+ void unlock ()
+ {
+ ReleaseSRWLockExclusive(&mHandle);
+ }
+ // TryAcquireSRW functions are a Windows 7 feature.
+ bool try_lock ()
+ {
+ BOOL ret = TryAcquireSRWLockExclusive(&mHandle);
+ return ret;
+ }
+ native_handle_type native_handle ()
+ {
+ return &mHandle;
+ }
+ };
+
+ class recursive_timed_mutex
+ {
+ static constexpr DWORD kWaitAbandoned = 0x00000080l;
+ static constexpr DWORD kWaitObject0 = 0x00000000l;
+ static constexpr DWORD kInfinite = 0xffffffffl;
+ inline bool try_lock_internal (DWORD ms) noexcept
+ {
+ DWORD ret = WaitForSingleObject(mHandle, ms);
+
+ /*
+ @@ TODO
+#ifndef NDEBUG
+ if (ret == kWaitAbandoned)
+ {
+ using namespace std;
+ fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
+ terminate();
+ }
+#endif
+ */
+
+ return (ret == kWaitObject0) || (ret == kWaitAbandoned);
+ }
+ protected:
+ HANDLE mHandle;
+ public:
+ typedef HANDLE native_handle_type;
+ native_handle_type native_handle() const {return mHandle;}
+ recursive_timed_mutex(const recursive_timed_mutex&) = delete;
+ recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete;
+ recursive_timed_mutex(): mHandle(CreateMutex(NULL, FALSE, NULL)) {}
+ ~recursive_timed_mutex()
+ {
+ CloseHandle(mHandle);
+ }
+ void lock()
+ {
+ DWORD ret = WaitForSingleObject(mHandle, kInfinite);
+
+ /*
+ @@ TODO
+
+// If (ret == WAIT_ABANDONED), then the thread that held ownership was
+// terminated. Behavior is undefined, but Windows will pass ownership to this
+// thread.
+#ifndef NDEBUG
+ if (ret == kWaitAbandoned)
+ {
+ using namespace std;
+ fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
+ terminate();
+ }
+#endif
+ */
+
+ if ((ret != kWaitObject0) && (ret != kWaitAbandoned))
+ {
+ throw std::system_error(GetLastError(), std::system_category());
+ }
+ }
+ void unlock()
+ {
+ if (!ReleaseMutex(mHandle))
+ throw std::system_error(GetLastError(), std::system_category());
+ }
+ bool try_lock()
+ {
+ return try_lock_internal(0);
+ }
+ template <class Rep, class Period>
+ bool try_lock_for(const std::chrono::duration<Rep,Period>& dur)
+ {
+ using namespace std::chrono;
+ auto timeout = duration_cast<milliseconds>(dur).count();
+ while (timeout > 0)
+ {
+ constexpr auto kMaxStep = static_cast<decltype(timeout)>(kInfinite-1);
+ auto step = (timeout < kMaxStep) ? timeout : kMaxStep;
+ if (try_lock_internal(static_cast<DWORD>(step)))
+ return true;
+ timeout -= step;
+ }
+ return false;
+ }
+ template <class Clock, class Duration>
+ bool try_lock_until(const std::chrono::time_point<Clock,Duration>& timeout_time)
+ {
+ return try_lock_for(timeout_time - Clock::now());
+ }
+ };
+
+ typedef recursive_timed_mutex timed_mutex;
+}
+
+#endif // LIBBUTL_MINGW_MUTEX_HXX
diff --git a/libbutl/mingw-shared_mutex.hxx b/libbutl/mingw-shared_mutex.hxx
new file mode 100644
index 0000000..aacbaf8
--- /dev/null
+++ b/libbutl/mingw-shared_mutex.hxx
@@ -0,0 +1,124 @@
+/**
+* std::shared_mutex et al implementation for MinGW-w64
+*
+* Copyright (c) 2017 by Nathaniel J. McClatchey, Athens OH, United States
+* Copyright (c) 2022 the build2 authors
+*
+* Licensed under the simplified (2-clause) BSD License.
+* You should have received a copy of the license along with this
+* program.
+*
+* This code is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#ifndef LIBBUTL_MINGW_SHARED_MUTEX_HXX
+#define LIBBUTL_MINGW_SHARED_MUTEX_HXX
+
+#if !defined(__cplusplus) || (__cplusplus < 201402L)
+# error C++14 compiler required
+#endif
+
+#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0601
+# error _WIN32_WINNT should be 0x0601 (Windows 7) or greater
+#endif
+
+#include <cassert>
+// For descriptive errors.
+#include <system_error>
+// For timing in shared_timed_mutex.
+#include <chrono>
+#include <limits>
+
+#include <shared_mutex> // shared_lock
+
+// For defer_lock_t, adopt_lock_t, and try_to_lock_t
+#include <libbutl/mingw-mutex.hxx>
+
+#include <synchapi.h>
+
+namespace mingw_stdthread
+{
+ using std::shared_lock;
+
+ class condition_variable_any;
+
+ // Slim Reader-Writer (SRW)-based implementation that requires Windows 7.
+ //
+ class shared_mutex : mutex
+ {
+ friend class condition_variable_any;
+ public:
+ using mutex::native_handle_type;
+ using mutex::lock;
+ using mutex::try_lock;
+ using mutex::unlock;
+ using mutex::native_handle;
+
+ void lock_shared ()
+ {
+ AcquireSRWLockShared(&mHandle);
+ }
+
+ void unlock_shared ()
+ {
+ ReleaseSRWLockShared(&mHandle);
+ }
+
+ bool try_lock_shared ()
+ {
+ return TryAcquireSRWLockShared(&mHandle) != 0;
+ }
+ };
+
+ class shared_timed_mutex : shared_mutex
+ {
+ typedef shared_mutex Base;
+ public:
+ using Base::lock;
+ using Base::try_lock;
+ using Base::unlock;
+ using Base::lock_shared;
+ using Base::try_lock_shared;
+ using Base::unlock_shared;
+
+ template< class Clock, class Duration >
+ bool try_lock_until ( const std::chrono::time_point<Clock,Duration>& cutoff )
+ {
+ do
+ {
+ if (try_lock())
+ return true;
+ }
+ while (std::chrono::steady_clock::now() < cutoff);
+ return false;
+ }
+
+ template< class Rep, class Period >
+ bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time)
+ {
+ return try_lock_until(std::chrono::steady_clock::now() + rel_time);
+ }
+
+ template< class Clock, class Duration >
+ bool try_lock_shared_until ( const std::chrono::time_point<Clock,Duration>& cutoff )
+ {
+ do
+ {
+ if (try_lock_shared())
+ return true;
+ }
+ while (std::chrono::steady_clock::now() < cutoff);
+ return false;
+ }
+
+ template< class Rep, class Period >
+ bool try_lock_shared_for (const std::chrono::duration<Rep,Period>& rel_time)
+ {
+ return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time);
+ }
+ };
+}
+
+#endif // LIBBUTL_MINGW_SHARED_MUTEX_HXX
diff --git a/libbutl/mingw-thread.hxx b/libbutl/mingw-thread.hxx
new file mode 100644
index 0000000..66f98aa
--- /dev/null
+++ b/libbutl/mingw-thread.hxx
@@ -0,0 +1,330 @@
+/**
+* std::thread implementation for MinGW-w64
+*
+* Copyright (c) 2013-2016 by Mega Limited, Auckland, New Zealand
+* Copyright (c) 2022 the build2 authors
+*
+* Licensed under the simplified (2-clause) BSD License.
+* You should have received a copy of the license along with this
+* program.
+*
+* This code is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#ifndef LIBBUTL_MINGW_THREAD_HXX
+#define LIBBUTL_MINGW_THREAD_HXX
+
+#if !defined(__cplusplus) || (__cplusplus < 201402L)
+# error C++14 compiler required
+#endif
+
+#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0601
+# error _WIN32_WINNT should be 0x0601 (Windows 7) or greater
+#endif
+
+#include <cstddef> // For std::size_t
+#include <cerrno> // Detect error type.
+#include <exception> // For std::terminate
+#include <system_error> // For std::system_error
+#include <functional> // For std::hash, std::invoke (C++17)
+#include <tuple> // For std::tuple
+#include <chrono> // For sleep timing.
+#include <memory> // For std::unique_ptr
+#include <iosfwd> // Stream output for thread ids.
+#include <utility> // For std::swap, std::forward
+
+#include <synchapi.h> // For WaitForSingleObject
+#include <handleapi.h> // For CloseHandle, etc.
+#include <sysinfoapi.h> // For GetNativeSystemInfo
+#include <processthreadsapi.h> // For GetCurrentThreadId
+
+#include <process.h> // For _beginthreadex
+
+#if __cplusplus < 201703L
+# include <libbutl/mingw-invoke.hxx>
+#endif
+
+namespace mingw_stdthread
+{
+ // @@ I think can get rid of this in C++14.
+ //
+ namespace detail
+ {
+ template<std::size_t...>
+ struct IntSeq {};
+
+ template<std::size_t N, std::size_t... S>
+ struct GenIntSeq : GenIntSeq<N-1, N-1, S...> { };
+
+ template<std::size_t... S>
+ struct GenIntSeq<0, S...> { typedef IntSeq<S...> type; };
+
+// Use a template specialization to avoid relying on compiler optimization
+// when determining the parameter integer sequence.
+ template<class Func, class T, typename... Args>
+ class ThreadFuncCall;
+// We can't define the Call struct in the function - the standard forbids template methods in that case
+ template<class Func, std::size_t... S, typename... Args>
+ class ThreadFuncCall<Func, detail::IntSeq<S...>, Args...>
+ {
+ static_assert(sizeof...(S) == sizeof...(Args), "Args must match.");
+ using Tuple = std::tuple<typename std::decay<Args>::type...>;
+ typename std::decay<Func>::type mFunc;
+ Tuple mArgs;
+
+ public:
+ ThreadFuncCall(Func&& aFunc, Args&&... aArgs)
+ : mFunc(std::forward<Func>(aFunc)),
+ mArgs(std::forward<Args>(aArgs)...)
+ {
+ }
+
+ void callFunc()
+ {
+#if __cplusplus < 201703L
+ detail::invoke(std::move(mFunc), std::move(std::get<S>(mArgs)) ...);
+#else
+ std::invoke (std::move(mFunc), std::move(std::get<S>(mArgs)) ...);
+#endif
+ }
+ };
+
+ // Allow construction of threads without exposing implementation.
+ class ThreadIdTool;
+ }
+
+ class thread
+ {
+ public:
+ class id
+ {
+ DWORD mId = 0;
+ friend class thread;
+ friend class std::hash<id>;
+ friend class detail::ThreadIdTool;
+ explicit id(DWORD aId) noexcept : mId(aId){}
+ public:
+ id () noexcept = default;
+ friend bool operator==(id x, id y) noexcept {return x.mId == y.mId; }
+ friend bool operator!=(id x, id y) noexcept {return x.mId != y.mId; }
+ friend bool operator< (id x, id y) noexcept {return x.mId < y.mId; }
+ friend bool operator<=(id x, id y) noexcept {return x.mId <= y.mId; }
+ friend bool operator> (id x, id y) noexcept {return x.mId > y.mId; }
+ friend bool operator>=(id x, id y) noexcept {return x.mId >= y.mId; }
+
+ template<class _CharT, class _Traits>
+ friend std::basic_ostream<_CharT, _Traits>&
+ operator<<(std::basic_ostream<_CharT, _Traits>& __out, id __id)
+ {
+ if (__id.mId == 0)
+ {
+ return __out << "<invalid std::thread::id>";
+ }
+ else
+ {
+ return __out << __id.mId;
+ }
+ }
+ };
+ private:
+ static constexpr HANDLE kInvalidHandle = nullptr;
+ static constexpr DWORD kInfinite = 0xffffffffl;
+ HANDLE mHandle;
+ id mThreadId;
+
+ template <class Call>
+ static unsigned __stdcall threadfunc(void* arg)
+ {
+ std::unique_ptr<Call> call(static_cast<Call*>(arg));
+ call->callFunc();
+ return 0;
+ }
+
+ static unsigned int _hardware_concurrency_helper() noexcept
+ {
+ SYSTEM_INFO sysinfo;
+ ::GetNativeSystemInfo(&sysinfo);
+ return sysinfo.dwNumberOfProcessors;
+ }
+ public:
+ typedef HANDLE native_handle_type;
+ id get_id() const noexcept {return mThreadId;}
+ native_handle_type native_handle() const {return mHandle;}
+ thread(): mHandle(kInvalidHandle), mThreadId(){}
+
+ thread(thread&& other) noexcept
+ :mHandle(other.mHandle), mThreadId(other.mThreadId)
+ {
+ other.mHandle = kInvalidHandle;
+ other.mThreadId = id{};
+ }
+
+ thread(const thread &other) = delete;
+
+ template<class Func, typename... Args>
+ explicit thread(Func&& func, Args&&... args) : mHandle(), mThreadId()
+ {
+ // Instead of INVALID_HANDLE_VALUE, _beginthreadex returns 0.
+
+ using ArgSequence = typename detail::GenIntSeq<sizeof...(Args)>::type;
+ using Call = detail::ThreadFuncCall<Func, ArgSequence, Args...>;
+ auto call = new Call(std::forward<Func>(func), std::forward<Args>(args)...);
+ unsigned int id_receiver;
+ auto int_handle = _beginthreadex(NULL, 0, threadfunc<Call>,
+ static_cast<LPVOID>(call), 0, &id_receiver);
+ if (int_handle == 0)
+ {
+ mHandle = kInvalidHandle;
+ int errnum = errno;
+ delete call;
+ // Note: Should only throw EINVAL, EAGAIN, EACCES
+ throw std::system_error(errnum, std::generic_category());
+ } else {
+ mThreadId.mId = id_receiver;
+ mHandle = reinterpret_cast<HANDLE>(int_handle);
+ }
+ }
+
+ bool joinable() const {return mHandle != kInvalidHandle;}
+
+ // Note: Due to lack of synchronization, this function has a race
+ // condition if called concurrently, which leads to undefined
+ // behavior. The same applies to all other member functions of this
+ // class, but this one is mentioned explicitly.
+ void join()
+ {
+ using namespace std;
+ if (get_id() == id(GetCurrentThreadId()))
+ throw system_error(make_error_code(errc::resource_deadlock_would_occur));
+ if (mHandle == kInvalidHandle)
+ throw system_error(make_error_code(errc::no_such_process));
+ if (!joinable())
+ throw system_error(make_error_code(errc::invalid_argument));
+ WaitForSingleObject(mHandle, kInfinite);
+ CloseHandle(mHandle);
+ mHandle = kInvalidHandle;
+ mThreadId = id{};
+ }
+
+ ~thread()
+ {
+ if (joinable())
+ {
+ // @@ TODO
+ /*
+#ifndef NDEBUG
+ std::printf("Error: Must join() or detach() a thread before \
+destroying it.\n");
+#endif
+ */
+ std::terminate();
+ }
+ }
+ thread& operator=(const thread&) = delete;
+ thread& operator=(thread&& other) noexcept
+ {
+ if (joinable())
+ {
+ // @@ TODO
+ /*
+#ifndef NDEBUG
+ std::printf("Error: Must join() or detach() a thread before \
+moving another thread to it.\n");
+#endif
+ */
+ std::terminate();
+ }
+ swap(other);
+ return *this;
+ }
+ void swap(thread& other) noexcept
+ {
+ std::swap(mHandle, other.mHandle);
+ std::swap(mThreadId.mId, other.mThreadId.mId);
+ }
+
+ static unsigned int hardware_concurrency() noexcept
+ {
+ // @@ TODO: this seems like a bad idea.
+ //
+ /*static*/ unsigned int cached = _hardware_concurrency_helper();
+ return cached;
+ }
+
+ void detach()
+ {
+ if (!joinable())
+ {
+ using namespace std;
+ throw system_error(make_error_code(errc::invalid_argument));
+ }
+ if (mHandle != kInvalidHandle)
+ {
+ CloseHandle(mHandle);
+ mHandle = kInvalidHandle;
+ }
+ mThreadId = id{};
+ }
+ };
+
+ namespace detail
+ {
+ class ThreadIdTool
+ {
+ public:
+ static thread::id make_id (DWORD base_id) noexcept
+ {
+ return thread::id(base_id);
+ }
+ };
+ }
+
+ namespace this_thread
+ {
+ inline thread::id get_id() noexcept
+ {
+ return detail::ThreadIdTool::make_id(GetCurrentThreadId());
+ }
+ inline void yield() noexcept {Sleep(0);}
+ template< class Rep, class Period >
+ void sleep_for( const std::chrono::duration<Rep,Period>& sleep_duration)
+ {
+ static constexpr DWORD kInfinite = 0xffffffffl;
+ using namespace std::chrono;
+ using rep = milliseconds::rep;
+ rep ms = duration_cast<milliseconds>(sleep_duration).count();
+ while (ms > 0)
+ {
+ constexpr rep kMaxRep = static_cast<rep>(kInfinite - 1);
+ auto sleepTime = (ms < kMaxRep) ? ms : kMaxRep;
+ Sleep(static_cast<DWORD>(sleepTime));
+ ms -= sleepTime;
+ }
+ }
+ template <class Clock, class Duration>
+ void sleep_until(const std::chrono::time_point<Clock,Duration>& sleep_time)
+ {
+ sleep_for(sleep_time-Clock::now());
+ }
+ }
+}
+
+namespace std
+{
+ // Specialize hash for this implementation's thread::id, even if the
+ // std::thread::id already has a hash.
+ template<>
+ struct hash<mingw_stdthread::thread::id>
+ {
+ typedef mingw_stdthread::thread::id argument_type;
+ typedef size_t result_type;
+ size_t operator() (const argument_type & i) const noexcept
+ {
+ return i.mId;
+ }
+ };
+}
+
+#endif // LIBBUTL_MINGW_THREAD_HXX
diff --git a/libbutl/move-only-function.hxx b/libbutl/move-only-function.hxx
new file mode 100644
index 0000000..e5cfe51
--- /dev/null
+++ b/libbutl/move-only-function.hxx
@@ -0,0 +1,177 @@
+// file : libbutl/move-only-function.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#pragma once
+
+#include <utility>
+#include <functional>
+#include <type_traits>
+
+namespace butl
+{
+ // This is a move-only std::function version which is implemented in terms
+ // of std::function. It is similar to C++23 std::move_only_function but
+ // still provides target() (but not target_type()).
+ //
+ template <typename>
+ class move_only_function_ex;
+
+ // Alias butl::move_only_function to std::move_only_function if available
+ // and to move_only_function_ex otherwise.
+ //
+#ifdef __cpp_lib_move_only_function
+ using std::move_only_function;
+#else
+ template <typename F>
+ using move_only_function = move_only_function_ex<F>;
+#endif
+
+ template <typename R, typename... A>
+ class move_only_function_ex<R (A...)>
+ {
+ public:
+ using result_type = R;
+
+ move_only_function_ex () = default;
+ move_only_function_ex (std::nullptr_t) noexcept {}
+
+ // Note: according to the spec we should also disable these if F is not
+ // callable, but that is not easy to do in C++14. Maybe we should do
+ // something for C++17 and later (without this the diagnostics is quite
+ // hairy).
+ //
+ template <typename F>
+ move_only_function_ex (F&& f, typename std::enable_if<!std::is_same<typename std::remove_reference<F>::type, move_only_function_ex>::value>::type* = 0)
+ {
+ using FV = typename std::decay<F>::type;
+
+ if (!null (f))
+ f_ = wrapper<FV> (std::forward<F> (f));
+ }
+
+ template <typename F>
+ typename std::enable_if<!std::is_same<typename std::remove_reference<F>::type, move_only_function_ex>::value, move_only_function_ex>::type&
+ operator= (F&& f)
+ {
+ move_only_function_ex (std::forward<F> (f)).swap (*this);
+ return *this;
+ }
+
+ move_only_function_ex&
+ operator= (std::nullptr_t) noexcept
+ {
+ f_ = nullptr;
+ return *this;
+ }
+
+ void swap (move_only_function_ex& f) noexcept
+ {
+ f_.swap (f.f_);
+ }
+
+ R operator() (A... args) const
+ {
+ return f_ (std::forward<A> (args)...);
+ }
+
+ explicit operator bool () const noexcept
+ {
+ return static_cast<bool> (f_);
+ }
+
+ template <typename T>
+ T* target() noexcept
+ {
+ wrapper<T>* r (f_.template target<wrapper<T>> ());
+ return r != nullptr ? &r->f : nullptr;
+ }
+
+ template <typename T>
+ const T* target() const noexcept
+ {
+ const wrapper<T>* r (f_.template target<wrapper<T>> ());
+ return r != nullptr ? &r->f : nullptr;
+ }
+
+ move_only_function_ex (move_only_function_ex&&) = default;
+ move_only_function_ex& operator= (move_only_function_ex&&) = default;
+
+ move_only_function_ex (const move_only_function_ex&) = delete;
+ move_only_function_ex& operator= (const move_only_function_ex&) = delete;
+
+ private:
+ template <typename F>
+ struct wrapper
+ {
+ struct empty {};
+
+ union
+ {
+ F f;
+ empty e;
+ };
+
+ explicit wrapper (F&& f_): f (std::move (f_)) {}
+ explicit wrapper (const F& f_): f (f_) {}
+
+ R operator() (A... args)
+ {
+ return f (std::forward<A> (args)...);
+ }
+
+ R operator() (A... args) const
+ {
+ return f (std::forward<A> (args)...);
+ }
+
+ wrapper (wrapper&& w)
+ noexcept (std::is_nothrow_move_constructible<F>::value)
+ : f (std::move (w.f)) {}
+
+ wrapper& operator= (wrapper&&) = delete; // Shouldn't be needed.
+
+ ~wrapper () {f.~F ();}
+
+ // These shouldn't be called.
+ //
+ wrapper (const wrapper&) {}
+ wrapper& operator= (const wrapper&) {return *this;}
+ };
+
+ template <typename F> static bool null (const F&) {return false;}
+ template <typename R1, typename... A1> static bool null (R1 (*p) (A1...)) {return p == nullptr;}
+ template <typename R1, typename... A1> static bool null (const move_only_function_ex<R1 (A1...)>& f) {return !f;}
+ template <typename R1, typename C, typename... A1> static bool null (R1 (C::*p) (A1...)) {return p == nullptr;}
+ template <typename R1, typename C, typename... A1> static bool null (R1 (C::*p) (A1...) const) {return p == nullptr;}
+
+ std::function<R (A...)> f_;
+ };
+
+ template <typename R, typename... A>
+ inline bool
+ operator== (const move_only_function_ex<R (A...)>& f, std::nullptr_t) noexcept
+ {
+ return !f;
+ }
+
+ template <typename R, typename... A>
+ inline bool
+ operator== (std::nullptr_t, const move_only_function_ex<R (A...)>& f) noexcept
+ {
+ return !f;
+ }
+
+ template <typename R, typename... A>
+ inline bool
+ operator!= (const move_only_function_ex<R (A...)>& f, std::nullptr_t) noexcept
+ {
+ return static_cast<bool> (f);
+ }
+
+ template <typename R, typename... A>
+ inline bool
+ operator!= (std::nullptr_t, const move_only_function_ex<R (A...)>& f) noexcept
+ {
+ return static_cast<bool> (f);
+ }
+}
diff --git a/libbutl/multi-index.mxx b/libbutl/multi-index.hxx
index d51bdfc..a6754cd 100644
--- a/libbutl/multi-index.mxx
+++ b/libbutl/multi-index.hxx
@@ -1,29 +1,14 @@
-// file : libbutl/multi-index.mxx -*- C++ -*-
+// file : libbutl/multi-index.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <utility> // declval()
#include <functional> // hash
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.multi_index;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Google the "Emulating Boost.MultiIndex with Standard Containers" blog
// post for details.
@@ -57,7 +42,7 @@ LIBBUTL_MODEXPORT namespace butl
};
}
-LIBBUTL_MODEXPORT namespace std
+namespace std
{
template <typename T>
struct hash<butl::map_key<T>>: hash<T>
diff --git a/libbutl/openssl.cxx b/libbutl/openssl.cxx
index 8741b35..f9df2e7 100644
--- a/libbutl/openssl.cxx
+++ b/libbutl/openssl.cxx
@@ -1,35 +1,10 @@
// file : libbutl/openssl.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/openssl.mxx>
-#endif
+#include <libbutl/openssl.hxx>
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-
#include <utility> // move()
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.openssl;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.process;
-import butl.fdstream;
-import butl.small_vector;
-#endif
-
-#endif
using namespace std;
diff --git a/libbutl/openssl.mxx b/libbutl/openssl.hxx
index 6a0907e..b340f5c 100644
--- a/libbutl/openssl.mxx
+++ b/libbutl/openssl.hxx
@@ -1,41 +1,21 @@
-// file : libbutl/openssl.mxx -*- C++ -*-
+// file : libbutl/openssl.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <type_traits>
-#include <cstddef> // size_t
-#include <utility> // move(), forward()
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.openssl;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.process; //@@ MOD TODO: should we re-export?
-import butl.fdstream;
-import butl.small_vector;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/process.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/small-vector.mxx>
-#endif
+#include <libbutl/path.hxx>
+#include <libbutl/process.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/small-vector.hxx>
+#include <libbutl/semantic-version.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Perform a crypto operation using the openssl(1) program. Throw
// process_error and io_error (both derive from system_error) in case of
@@ -100,6 +80,23 @@ LIBBUTL_MODEXPORT namespace butl
// department (that were apparently fixed in 1.0.2). To work around these
// bugs pass user-supplied options first.
//
+ struct openssl_info
+ {
+ // Note that the program name can be used by the caller to properly
+ // interpret the version.
+ //
+ // The name/version examples:
+ //
+ // OpenSSL 3.0.0
+ // OpenSSL 1.1.1l
+ // LibreSSL 2.8.3
+ //
+ // The `l` component above ends up in semantic_version::build.
+ //
+ std::string name;
+ semantic_version version;
+ };
+
class LIBBUTL_SYMEXPORT openssl: public process
{
public:
@@ -133,6 +130,22 @@ LIBBUTL_MODEXPORT namespace butl
const std::string& command,
A&&... options);
+ // Run `openssl version` command and try to parse and return the
+ // information it prints to stdout. Return nullopt if the process hasn't
+ // terminated successfully or stdout parsing has failed. Throw
+ // process_error and io_error in case of errors.
+ //
+ template <typename E>
+ static optional<openssl_info>
+ info (E&& err, const process_env&);
+
+ template <typename C,
+ typename E>
+ static optional<openssl_info>
+ info (const C&,
+ E&& err,
+ const process_env&);
+
private:
template <typename T>
struct is_other
diff --git a/libbutl/openssl.ixx b/libbutl/openssl.ixx
index c685b65..db2fbcd 100644
--- a/libbutl/openssl.ixx
+++ b/libbutl/openssl.ixx
@@ -1,7 +1,10 @@
// file : libbutl/openssl.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+#include <cstddef> // size_t
+#include <utility> // forward()
+
+namespace butl
{
template <typename I,
typename O,
@@ -23,4 +26,13 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
std::forward<A> (options)...)
{
}
+
+ template <typename E>
+ inline optional<openssl_info> openssl::
+ info (E&& err, const process_env& env)
+ {
+ return info ([] (const char* [], std::size_t) {},
+ std::forward<E> (err),
+ env);
+ }
}
diff --git a/libbutl/openssl.txx b/libbutl/openssl.txx
index 3a2c579..f55432d 100644
--- a/libbutl/openssl.txx
+++ b/libbutl/openssl.txx
@@ -1,7 +1,10 @@
// file : libbutl/openssl.txx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+#include <cstddef> // size_t
+#include <utility> // forward()
+
+namespace butl
{
template <typename I>
typename std::enable_if<openssl::is_other<I>::value, I>::type openssl::
@@ -47,4 +50,67 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
// Note: leaving this scope closes any open ends of the pipes in io_data.
}
+
+ template <typename C,
+ typename E>
+ optional<openssl_info> openssl::
+ info (const C& cmdc, E&& err, const process_env& env)
+ {
+ using namespace std;
+
+ // Run the `openssl version` command.
+ //
+ openssl os (cmdc,
+ nullfd, fdstream_mode::text, forward<E> (err),
+ env,
+ "version");
+
+ // Read the command's stdout and wait for its completion. Bail out if the
+ // command didn't terminate successfully or stdout contains no data.
+ //
+ string s;
+ if (!getline (os.in, s))
+ return nullopt;
+
+ os.in.close ();
+
+ if (!os.wait ())
+ return nullopt;
+
+ // Parse the version string.
+ //
+ // Note that there is some variety in the version representations:
+ //
+ // OpenSSL 3.0.0 7 sep 2021 (Library: OpenSSL 3.0.0 7 sep 2021)
+ // OpenSSL 1.1.1l FIPS 24 Aug 2021
+ // LibreSSL 2.8.3
+ //
+ // We will only consider the first two space separated components as the
+ // program name and version. We will also assume that there are no leading
+ // spaces and the version is delimited from the program name with a single
+ // space character.
+ //
+ size_t e (s.find (' '));
+
+ // Bail out if there is no version present in the string or the program
+ // name is empty.
+ //
+ if (e == string::npos || e == 0)
+ return nullopt;
+
+ string nm (s, 0, e);
+
+ size_t b (e + 1); // The beginning of the version.
+ e = s.find (' ', b); // The end of the version.
+
+ optional<semantic_version> ver (
+ parse_semantic_version (string (s, b, e != string::npos ? e - b : e),
+ semantic_version::allow_build,
+ "" /* build_separators */));
+
+ if (!ver)
+ return nullopt;
+
+ return openssl_info {move (nm), move (*ver)};
+ }
}
diff --git a/libbutl/optional.mxx b/libbutl/optional.hxx
index d32e14b..f22189b 100644
--- a/libbutl/optional.mxx
+++ b/libbutl/optional.hxx
@@ -1,11 +1,7 @@
-// file : libbutl/optional.mxx -*- C++ -*-
+// file : libbutl/optional.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-
-// C includes.
// Note: the Clang check must come before GCC since it also defines __GNUC__.
//
@@ -54,7 +50,6 @@
# endif
#endif
-#ifndef __cpp_lib_modules_ts
#ifdef LIBBUTL_STD_OPTIONAL
# include <optional>
#else
@@ -62,31 +57,19 @@
# include <functional> // hash
# include <type_traits> // is_*
#endif
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.optional;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
#include <libbutl/export.hxx>
#ifdef LIBBUTL_STD_OPTIONAL
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
- template <typename T>
- using optional = std::optional<T>;
-
+ using std::optional;
using std::nullopt_t;
using std::nullopt;
}
#else
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Simple optional class template while waiting for std::optional.
//
@@ -125,10 +108,16 @@ LIBBUTL_MODEXPORT namespace butl
#if (!defined(_MSC_VER) || _MSC_VER > 1900) && \
(!defined(__GNUC__) || __GNUC__ > 4 || defined(__clang__))
constexpr optional_data (const optional_data& o): v_ (o.v_) {if (v_) new (&d_) T (o.d_);}
- constexpr optional_data (optional_data&& o): v_ (o.v_) {if (v_) new (&d_) T (std::move (o.d_));}
+
+ constexpr optional_data (optional_data&& o)
+ noexcept (std::is_nothrow_move_constructible<T>::value)
+ : v_ (o.v_) {if (v_) new (&d_) T (std::move (o.d_));}
#else
optional_data (const optional_data& o): v_ (o.v_) {if (v_) new (&d_) T (o.d_);}
- optional_data (optional_data&& o): v_ (o.v_) {if (v_) new (&d_) T (std::move (o.d_));}
+
+ optional_data (optional_data&& o)
+ noexcept (std::is_nothrow_move_constructible<T>::value)
+ : v_ (o.v_) {if (v_) new (&d_) T (std::move (o.d_));}
#endif
optional_data& operator= (nullopt_t);
@@ -136,7 +125,11 @@ LIBBUTL_MODEXPORT namespace butl
optional_data& operator= (T&&);
optional_data& operator= (const optional_data&);
- optional_data& operator= (optional_data&&);
+
+ optional_data& operator= (optional_data&&)
+ noexcept (std::is_nothrow_move_constructible<T>::value &&
+ std::is_nothrow_move_assignable<T>::value &&
+ std::is_nothrow_destructible<T>::value);
~optional_data ();
};
@@ -168,10 +161,16 @@ LIBBUTL_MODEXPORT namespace butl
#if (!defined(_MSC_VER) || _MSC_VER > 1900) && \
(!defined(__GNUC__) || __GNUC__ > 4 || defined(__clang__))
constexpr optional_data (const optional_data& o): v_ (o.v_) {if (v_) new (&d_) T (o.d_);}
- constexpr optional_data (optional_data&& o): v_ (o.v_) {if (v_) new (&d_) T (std::move (o.d_));}
+
+ constexpr optional_data (optional_data&& o)
+ noexcept (std::is_nothrow_move_constructible<T>::value)
+ : v_ (o.v_) {if (v_) new (&d_) T (std::move (o.d_));}
#else
optional_data (const optional_data& o): v_ (o.v_) {if (v_) new (&d_) T (o.d_);}
- optional_data (optional_data&& o): v_ (o.v_) {if (v_) new (&d_) T (std::move (o.d_));}
+
+ optional_data (optional_data&& o)
+ noexcept (std::is_nothrow_move_constructible<T>::value)
+ : v_ (o.v_) {if (v_) new (&d_) T (std::move (o.d_));}
#endif
optional_data& operator= (nullopt_t);
@@ -179,7 +178,12 @@ LIBBUTL_MODEXPORT namespace butl
optional_data& operator= (T&&);
optional_data& operator= (const optional_data&);
- optional_data& operator= (optional_data&&);
+
+ // Note: it is trivially destructible and thus is no-throw destructible.
+ //
+ optional_data& operator= (optional_data&&)
+ noexcept (std::is_nothrow_move_constructible<T>::value &&
+ std::is_nothrow_move_assignable<T>::value);
};
template <typename T,
@@ -306,6 +310,8 @@ LIBBUTL_MODEXPORT namespace butl
explicit operator bool () const {return this->v_;}
};
+ // optional ? optional
+ //
template <typename T>
inline auto
operator== (const optional<T>& x, const optional<T>& y)
@@ -335,6 +341,131 @@ LIBBUTL_MODEXPORT namespace butl
{
return y < x;
}
+
+ // optional ? nullopt
+ // nullopt ? optional
+ //
+ template <typename T>
+ inline auto
+ operator== (const optional<T>& x, nullopt_t)
+ {
+ bool px (x);
+ return !px;
+ }
+
+ template <typename T>
+ inline auto
+ operator== (nullopt_t, const optional<T>& y)
+ {
+ bool py (y);
+ return !py;
+ }
+
+ template <typename T>
+ inline auto
+ operator!= (const optional<T>& x, nullopt_t y)
+ {
+ return !(x == y);
+ }
+
+ template <typename T>
+ inline auto
+ operator!= (nullopt_t x, const optional<T>& y)
+ {
+ return !(x == y);
+ }
+
+ template <typename T>
+ inline auto
+ operator< (const optional<T>&, nullopt_t)
+ {
+ return false;
+ }
+
+ template <typename T>
+ inline auto
+ operator< (nullopt_t, const optional<T>& y)
+ {
+ bool py (y);
+ return py;
+ }
+
+ template <typename T>
+ inline auto
+ operator> (const optional<T>& x, nullopt_t y)
+ {
+ return y < x;
+ }
+
+ template <typename T>
+ inline auto
+ operator> (nullopt_t x, const optional<T>& y)
+ {
+ return y < x;
+ }
+
+ // optional ? T
+ // T ? optional
+ //
+ template <typename T>
+ inline auto
+ operator== (const optional<T>& x, const T& y)
+ {
+ bool px (x);
+ return px && *x == y;
+ }
+
+ template <typename T>
+ inline auto
+ operator== (const T& x, const optional<T>& y)
+ {
+ bool py (y);
+ return py && x == *y;
+ }
+
+ template <typename T>
+ inline auto
+ operator!= (const optional<T>& x, const T& y)
+ {
+ return !(x == y);
+ }
+
+ template <typename T>
+ inline auto
+ operator!= (const T& x, const optional<T>& y)
+ {
+ return !(x == y);
+ }
+
+ template <typename T>
+ inline auto
+ operator< (const optional<T>& x, const T& y)
+ {
+ bool px (x);
+ return !px || *x < y;
+ }
+
+ template <typename T>
+ inline auto
+ operator< (const T& x, const optional<T>& y)
+ {
+ bool py (y);
+ return py && x < *y;
+ }
+
+ template <typename T>
+ inline auto
+ operator> (const optional<T>& x, const T& y)
+ {
+ return y < x;
+ }
+
+ template <typename T>
+ inline auto
+ operator> (const T& x, const optional<T>& y)
+ {
+ return y < x;
+ }
}
namespace std
diff --git a/libbutl/optional.ixx b/libbutl/optional.ixx
index e2b552f..fdd0ac5 100644
--- a/libbutl/optional.ixx
+++ b/libbutl/optional.ixx
@@ -77,6 +77,9 @@ namespace butl
template <typename T>
inline optional_data<T, false>& optional_data<T, false>::
operator= (optional_data&& o)
+ noexcept (std::is_nothrow_move_constructible<T>::value &&
+ std::is_nothrow_move_assignable<T>::value &&
+ std::is_nothrow_destructible<T>::value)
{
if (o.v_)
{
@@ -171,6 +174,8 @@ namespace butl
template <typename T>
inline optional_data<T, true>& optional_data<T, true>::
operator= (optional_data&& o)
+ noexcept (std::is_nothrow_move_constructible<T>::value &&
+ std::is_nothrow_move_assignable<T>::value)
{
if (o.v_)
{
diff --git a/libbutl/pager.cxx b/libbutl/pager.cxx
index 44aa83e..e647948 100644
--- a/libbutl/pager.cxx
+++ b/libbutl/pager.cxx
@@ -1,9 +1,7 @@
// file : libbutl/pager.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/pager.mxx>
-#endif
+#include <libbutl/pager.hxx>
#include <errno.h> // E*
@@ -14,46 +12,20 @@
# include <libbutl/win32-utility.hxx>
#endif
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
-#include <iostream>
-
+#include <cstddef> // size_t
#include <cstring> // strchr()
#include <utility> // move()
+
#ifndef _WIN32
# include <chrono>
# include <thread> // this_thread::sleep_for()
#endif
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.pager;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.process;
-import butl.fdstream;
-#endif
-#ifndef _WIN32
-import std.threading;
-#endif
-
-import butl.utility; // operator<<(ostream, exception), throw_generic_error()
-import butl.optional;
-import butl.fdstream; // fdclose()
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/fdstream.mxx>
-#endif
+#include <libbutl/utility.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/fdstream.hxx>
using namespace std;
diff --git a/libbutl/pager.mxx b/libbutl/pager.hxx
index a1f640f..12a6670 100644
--- a/libbutl/pager.mxx
+++ b/libbutl/pager.hxx
@@ -1,36 +1,18 @@
-// file : libbutl/pager.mxx -*- C++ -*-
+// file : libbutl/pager.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <iostream>
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-export module butl.pager;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.process;
-import butl.fdstream;
-#else
-#include <libbutl/process.mxx>
-#include <libbutl/fdstream.mxx>
-#endif
+#include <libbutl/process.hxx>
+#include <libbutl/fdstream.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Try to run the output through a pager program, such as more or less (no
// pun intended, less is used by default). If the default pager program is
diff --git a/libbutl/path-io.mxx b/libbutl/path-io.hxx
index 6b6dbcf..a60527d 100644
--- a/libbutl/path-io.mxx
+++ b/libbutl/path-io.hxx
@@ -1,34 +1,16 @@
-// file : libbutl/path-io.mxx -*- C++ -*-
+// file : libbutl/path-io.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-
-// C includes.
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <ostream>
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-export module butl.path_io;
-#ifdef __cpp_lib_modules_ts
-import std.core; //@@ MOD TMP (should not be needed).
-import std.io;
-#endif
-import butl.path;
-#else
-#include <libbutl/path.mxx>
-#endif
+#include <libbutl/path.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// This is the default path IO implementation. It is separate to allow
// custom implementations. For example, we may want to print paths as
diff --git a/libbutl/path-map.mxx b/libbutl/path-map.hxx
index daaf0a4..e3d776a 100644
--- a/libbutl/path-map.mxx
+++ b/libbutl/path-map.hxx
@@ -1,33 +1,16 @@
-// file : libbutl/path-map.mxx -*- C++ -*-
+// file : libbutl/path-map.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <algorithm> // min()
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.path_map;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.prefix_map;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/prefix-map.mxx>
-#endif
+
+#include <libbutl/path.hxx>
+#include <libbutl/prefix-map.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// prefix_map for filesystem paths
//
@@ -142,4 +125,12 @@ LIBBUTL_MODEXPORT namespace butl
template <typename T>
using dir_path_map =
prefix_map<dir_path, T, dir_path::traits_type::directory_separator>;
+
+ template <typename T>
+ using path_multimap =
+ prefix_multimap<path, T, path::traits_type::directory_separator>;
+
+ template <typename T>
+ using dir_path_multimap =
+ prefix_multimap<dir_path, T, dir_path::traits_type::directory_separator>;
}
diff --git a/libbutl/path-pattern.cxx b/libbutl/path-pattern.cxx
index cea5aa7..ed36eb5 100644
--- a/libbutl/path-pattern.cxx
+++ b/libbutl/path-pattern.cxx
@@ -1,41 +1,14 @@
// file : libbutl/path-pattern.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/path-pattern.mxx>
-#endif
+#include <libbutl/path-pattern.hxx>
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <cstdint>
-#include <cstddef>
#include <iterator> // reverse_iterator
-
#include <algorithm> // find()
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.path_pattern;
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.optional;
-#endif
-
-import butl.utility; // lcase()[_WIN32]
-import butl.filesystem; // path_search()
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/filesystem.mxx>
-#endif
+#include <libbutl/utility.hxx> // lcase()[_WIN32]
+#include <libbutl/filesystem.hxx> // path_search()
using namespace std;
diff --git a/libbutl/path-pattern.mxx b/libbutl/path-pattern.hxx
index 6d9684a..f6e01be 100644
--- a/libbutl/path-pattern.mxx
+++ b/libbutl/path-pattern.hxx
@@ -1,37 +1,20 @@
-// file : libbutl/path-pattern.mxx -*- C++ -*-
+// file : libbutl/path-pattern.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
+#include <cassert>
#include <cstdint> // uint16_t
#include <cstddef> // ptrdiff_t, size_t
#include <iterator> // input_iterator_tag
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-export module butl.path_pattern;
-
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.optional;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/optional.mxx>
-#endif
+#include <libbutl/path.hxx>
+#include <libbutl/optional.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Wildcard pattern match (aka glob).
//
diff --git a/libbutl/path-pattern.ixx b/libbutl/path-pattern.ixx
index 71f125c..6fee31e 100644
--- a/libbutl/path-pattern.ixx
+++ b/libbutl/path-pattern.ixx
@@ -3,6 +3,32 @@
namespace butl
{
+ // path_match_flags
+ //
+ inline path_match_flags operator& (path_match_flags x, path_match_flags y)
+ {
+ return x &= y;
+ }
+
+ inline path_match_flags operator| (path_match_flags x, path_match_flags y)
+ {
+ return x |= y;
+ }
+
+ inline path_match_flags operator&= (path_match_flags& x, path_match_flags y)
+ {
+ return x = static_cast<path_match_flags> (
+ static_cast<std::uint16_t> (x) &
+ static_cast<std::uint16_t> (y));
+ }
+
+ inline path_match_flags operator|= (path_match_flags& x, path_match_flags y)
+ {
+ return x = static_cast<path_match_flags> (
+ static_cast<std::uint16_t> (x) |
+ static_cast<std::uint16_t> (y));
+ }
+
// path_pattern_iterator
//
inline path_pattern_iterator::
diff --git a/libbutl/path.cxx b/libbutl/path.cxx
index 3b04730..bd66f13 100644
--- a/libbutl/path.cxx
+++ b/libbutl/path.cxx
@@ -1,9 +1,7 @@
// file : libbutl/path.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/path.mxx>
-#endif
+#include <libbutl/path.hxx>
#ifdef _WIN32
# include <libbutl/win32-utility.hxx>
@@ -25,32 +23,11 @@
#endif
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <cstddef>
-#include <utility>
-
#include <atomic>
#include <cstring> // strcpy()
-#endif
-
-#ifdef __cpp_modules_ts
-module butl.path;
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
-
-import butl.utility; // throw_*_error()
-import butl.process; // process::current_id()
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/process.mxx>
-#endif
+#include <libbutl/utility.hxx> // throw_*_error()
+#include <libbutl/process.hxx> // process::current_id()
#include <libbutl/export.hxx>
@@ -207,8 +184,8 @@ namespace butl
using std::to_string;
return prefix
- + "-" + to_string (process::current_id ())
- + "-" + to_string (temp_name_count++);
+ + '-' + to_string (process::current_id ())
+ + '-' + to_string (temp_name_count++);
}
template <>
diff --git a/libbutl/path.mxx b/libbutl/path.hxx
index 5a41ddc..b10022a 100644
--- a/libbutl/path.mxx
+++ b/libbutl/path.hxx
@@ -1,13 +1,8 @@
-// file : libbutl/path.mxx -*- C++ -*-
+// file : libbutl/path.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-
-#include <cassert>
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <ostream>
#include <cstddef> // ptrdiff_t
@@ -21,31 +16,17 @@
#ifdef _WIN32
#include <algorithm> // replace()
#endif
-#endif
-// Other includes.
+#include <libbutl/optional.hxx>
+#include <libbutl/small-vector.hxx>
-#ifdef __cpp_modules_ts
-export module butl.path;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.optional;
-import butl.small_vector;
-#ifdef _WIN32
-import butl.utility;
-#endif
-#else
-#include <libbutl/optional.mxx>
-#include <libbutl/small-vector.mxx>
#ifdef _WIN32
-#include <libbutl/utility.mxx> // *case*()
-#endif
+#include <libbutl/utility.hxx> // *case*()
#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Wish list/ideas for improvements.
//
@@ -78,7 +59,7 @@ LIBBUTL_MODEXPORT namespace butl
string_type path;
explicit
- invalid_basic_path (const string_type& p): path (p) {}
+ invalid_basic_path (string_type p): path (std::move (p)) {}
explicit
invalid_basic_path (const C* p): path (p) {}
invalid_basic_path (const C* p, size_type n): path (p, n) {}
@@ -392,6 +373,22 @@ LIBBUTL_MODEXPORT namespace butl
: (p = rfind_separator (s, n - 1)) == nullptr ? s : ++p;
}
+ // Return true if sb is a sub-path of sp (i.e., sp is a prefix). Expects
+ // both paths to be normalized. Note that this function returns true if
+ // the paths are equal. Empty path is considered a prefix of any path.
+ //
+ static bool
+ sub (const C* sb, size_type nb,
+ const C* sp, size_type np);
+
+ // Return true if sp is a super-path of sb (i.e., sb is a suffix). Expects
+ // both paths to be normalized. Note that this function returns true if
+ // the paths are equal. Empty path is considered a prefix of any path.
+ //
+ static bool
+ sup (const C* sp, size_type np,
+ const C* sb, size_type nb);
+
static int
compare (string_type const& l,
string_type const& r,
@@ -615,18 +612,18 @@ LIBBUTL_MODEXPORT namespace butl
// Constructors.
//
- path_data ()
+ path_data () noexcept
: tsep_ (0) {}
- path_data (string_type&& p, difference_type ts)
+ path_data (string_type&& p, difference_type ts) noexcept
: path_ (std::move (p)), tsep_ (path_.empty () ? 0 : ts) {}
explicit
- path_data (string_type&& p)
+ path_data (string_type&& p) noexcept
: path_ (std::move (p)) { _init (); }
void
- _init ()
+ _init () noexcept
{
size_type n (path_.size ()), i;
@@ -654,7 +651,8 @@ LIBBUTL_MODEXPORT namespace butl
using path_data<C>::path_data;
base_type () = default;
- base_type (path_data<C>&& d): path_data<C> (std::move (d)) {}
+ base_type (path_data<C>&& d) noexcept
+ : path_data<C> (std::move (d)) {}
};
using dir_type = basic_path<C, dir_path_kind<C>>;
@@ -1281,7 +1279,8 @@ LIBBUTL_MODEXPORT namespace butl
// Direct initialization without init()/cast().
//
explicit
- basic_path (data_type&& d): base_type (std::move (d)) {}
+ basic_path (data_type&& d) noexcept
+ : base_type (std::move (d)) {}
using base_type::_size;
using base_type::_init;
@@ -1480,9 +1479,9 @@ LIBBUTL_MODEXPORT namespace butl
basic_path_name (): // Create empty/NULL path name.
base (nullptr, &name) {}
- basic_path_name (basic_path_name&&);
+ basic_path_name (basic_path_name&&) noexcept;
basic_path_name (const basic_path_name&);
- basic_path_name& operator= (basic_path_name&&);
+ basic_path_name& operator= (basic_path_name&&) noexcept;
basic_path_name& operator= (const basic_path_name&);
};
@@ -1509,14 +1508,14 @@ LIBBUTL_MODEXPORT namespace butl
basic_path_name_value (): base (&path) {} // Create empty/NULL path name.
- basic_path_name_value (basic_path_name_value&&);
+ basic_path_name_value (basic_path_name_value&&) noexcept;
basic_path_name_value (const basic_path_name_value&);
- basic_path_name_value& operator= (basic_path_name_value&&);
+ basic_path_name_value& operator= (basic_path_name_value&&) noexcept;
basic_path_name_value& operator= (const basic_path_name_value&);
};
}
-LIBBUTL_MODEXPORT namespace std
+namespace std
{
template <typename C, typename K>
struct hash<butl::basic_path<C, K>>: hash<basic_string<C>>
diff --git a/libbutl/path.ixx b/libbutl/path.ixx
index 9c96cfc..b2fdb6f 100644
--- a/libbutl/path.ixx
+++ b/libbutl/path.ixx
@@ -1,7 +1,7 @@
// file : libbutl/path.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+namespace butl
{
// path_abnormality
//
@@ -117,6 +117,45 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
return r;
}
+ template <typename C>
+ inline bool path_traits<C>::
+ sub (const C* s, size_type n,
+ const C* ps, size_type pn)
+ {
+ // The thinking here is that we can use the full string representations
+ // (including the trailing slash in "/").
+ //
+ if (pn == 0)
+ return true;
+
+ // The second condition guards against the /foo-bar vs /foo case.
+ //
+ return n >= pn &&
+ compare (s, pn, ps, pn) == 0 &&
+ (is_separator (ps[pn - 1]) || // p ends with a separator
+ n == pn || // *this == p
+ is_separator (s[pn])); // next char is a separator
+ }
+
+ template <typename C>
+ inline bool path_traits<C>::
+ sup (const C* s, size_type n,
+ const C* ps, size_type pn)
+ {
+ // The thinking here is that we can use the full string representations
+ // (including the trailing slash in "/").
+ //
+ if (pn == 0)
+ return true;
+
+ // The second condition guards against the /foo-bar vs bar case.
+ //
+ return n >= pn &&
+ compare (s + n - pn, pn, ps, pn) == 0 &&
+ (n == pn || // *this == p
+ is_separator (s[n - pn - 1])); // Previous char is a separator.
+ }
+
#ifdef _WIN32
template <>
inline char path_traits<char>::
@@ -230,52 +269,16 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
inline bool basic_path<C, K>::
sub (const basic_path& p) const
{
- // The thinking here is that we can use the full string representations
- // (including the trailing slash in "/").
- //
- const string_type& ps (p.path_);
- size_type pn (ps.size ());
-
- if (pn == 0)
- return true;
-
- const string_type& s (this->path_);
- size_type n (s.size ());
-
- // The second condition guards against the /foo-bar vs /foo case.
- //
- return n >= pn &&
- traits_type::compare (s.c_str (), pn, ps.c_str (), pn) == 0 &&
- (traits_type::is_separator (ps.back ()) || // p ends with a separator
- n == pn || // *this == p
- traits_type::is_separator (s[pn])); // next char is a separator
+ return traits_type::sub (this->path_.c_str (), this->path_.size (),
+ p.path_.c_str (), p.path_.size ());
}
template <typename C, typename K>
inline bool basic_path<C, K>::
sup (const basic_path& p) const
{
- // The thinking here is that we can use the full string representations
- // (including the trailing slash in "/").
- //
- const string_type& ps (p.path_);
- size_type pn (ps.size ());
-
- if (pn == 0)
- return true;
-
- const string_type& s (this->path_);
- size_type n (s.size ());
-
- // The second condition guards against the /foo-bar vs bar case.
- //
- return n >= pn &&
- traits_type::compare (s.c_str () + n - pn, pn, ps.c_str (), pn) == 0 &&
- (n == pn || // *this == p
- //
- // Previous char is a separator.
- //
- traits_type::is_separator (s[n - pn - 1]));
+ return traits_type::sup (this->path_.c_str (), this->path_.size (),
+ p.path_.c_str (), p.path_.size ());
}
template <typename C, typename K>
@@ -779,7 +782,7 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
//
template <typename P>
inline basic_path_name<P>::
- basic_path_name (basic_path_name&& p)
+ basic_path_name (basic_path_name&& p) noexcept
: basic_path_name (p.path, std::move (p.name))
{
}
@@ -793,7 +796,7 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
template <typename P>
inline basic_path_name<P>& basic_path_name<P>::
- operator= (basic_path_name&& p)
+ operator= (basic_path_name&& p) noexcept
{
if (this != &p)
{
@@ -821,7 +824,7 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
//
template <typename P>
inline basic_path_name_value<P>::
- basic_path_name_value (basic_path_name_value&& p)
+ basic_path_name_value (basic_path_name_value&& p) noexcept
: basic_path_name_value (std::move (p.path), std::move (p.name))
{
}
@@ -835,7 +838,7 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
template <typename P>
inline basic_path_name_value<P>& basic_path_name_value<P>::
- operator= (basic_path_name_value&& p)
+ operator= (basic_path_name_value&& p) noexcept
{
if (this != &p)
{
diff --git a/libbutl/path.txx b/libbutl/path.txx
index 84fc038..60e0f1a 100644
--- a/libbutl/path.txx
+++ b/libbutl/path.txx
@@ -1,7 +1,7 @@
// file : libbutl/path.txx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+namespace butl
{
template <typename C, typename K>
basic_path<C, K> basic_path<C, K>::
diff --git a/libbutl/prefix-map.mxx b/libbutl/prefix-map.hxx
index 634b8da..0895d96 100644
--- a/libbutl/prefix-map.mxx
+++ b/libbutl/prefix-map.hxx
@@ -1,31 +1,16 @@
-// file : libbutl/prefix-map.mxx -*- C++ -*-
+// file : libbutl/prefix-map.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <map>
#include <string>
#include <utility> // move()
#include <algorithm> // min()
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.prefix_map;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// A map of hierarchical "paths", e.g., 'foo.bar' or 'foo/bar' with the
// ability to retrieve a range of entries that have a specific prefix as
@@ -150,7 +135,6 @@ LIBBUTL_MODEXPORT namespace butl
const_iterator
find_sup (const key_type&) const;
-
// As above but additionally evaluate a predicate on each matching entry
// returning the one for which it returns true.
//
@@ -163,6 +147,26 @@ LIBBUTL_MODEXPORT namespace butl
find_sup_if (const key_type&, P) const;
};
+ template <typename M>
+ struct prefix_multimap_common: prefix_map_common<M>
+ {
+ typedef M map_type;
+ typedef typename map_type::key_type key_type;
+ typedef typename map_type::iterator iterator;
+ typedef typename map_type::const_iterator const_iterator;
+
+ using prefix_map_common<M>::prefix_map_common;
+
+ // Find the most qualified entries that are super-prefixes of the
+ // specified prefix.
+ //
+ std::pair<iterator, iterator>
+ sup_range (const key_type&);
+
+ std::pair<const_iterator, const_iterator>
+ sup_range (const key_type&) const;
+ };
+
template <typename M, typename prefix_map_common<M>::delimiter_type D>
struct prefix_map_impl: prefix_map_common<M>
{
@@ -173,6 +177,16 @@ LIBBUTL_MODEXPORT namespace butl
: prefix_map_common<M> (std::move (i), D) {}
};
+ template <typename M, typename prefix_map_common<M>::delimiter_type D>
+ struct prefix_multimap_impl: prefix_multimap_common<M>
+ {
+ typedef typename prefix_multimap_common<M>::value_type value_type;
+
+ prefix_multimap_impl (): prefix_multimap_common<M> (D) {}
+ prefix_multimap_impl (std::initializer_list<value_type> i)
+ : prefix_multimap_common<M> (std::move (i), D) {}
+ };
+
template <typename K,
typename T,
typename compare_prefix<K>::delimiter_type D>
@@ -182,7 +196,7 @@ LIBBUTL_MODEXPORT namespace butl
typename T,
typename compare_prefix<K>::delimiter_type D>
using prefix_multimap =
- prefix_map_impl<std::multimap<K, T, compare_prefix<K>>, D>;
+ prefix_multimap_impl<std::multimap<K, T, compare_prefix<K>>, D>;
}
#include <libbutl/prefix-map.txx>
diff --git a/libbutl/prefix-map.txx b/libbutl/prefix-map.txx
index edab8e1..80664bf 100644
--- a/libbutl/prefix-map.txx
+++ b/libbutl/prefix-map.txx
@@ -1,7 +1,7 @@
// file : libbutl/prefix-map.txx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+namespace butl
{
template <typename M>
auto prefix_map_common<M>::
@@ -197,4 +197,58 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
return i;
#endif
}
+
+ template <typename M>
+ auto prefix_multimap_common<M>::
+ sup_range (const key_type& k) -> std::pair<iterator, iterator>
+ {
+#if 0
+ // TODO (see above).
+#else
+ // First look for the exact match before making any copies.
+ //
+ auto r (this->equal_range (k));
+
+ if (r.first == r.second)
+ {
+ const auto& c (this->key_comp ());
+
+ for (key_type p (k); c.prefix (p); )
+ {
+ r = this->equal_range (p);
+ if (r.first != r.second)
+ break;
+ }
+ }
+
+ return r;
+#endif
+ }
+
+ template <typename M>
+ auto prefix_multimap_common<M>::
+ sup_range (const key_type& k) const -> std::pair<const_iterator, const_iterator>
+ {
+#if 0
+ // TODO (see above).
+#else
+ // First look for the exact match before making any copies.
+ //
+ auto r (this->equal_range (k));
+
+ if (r.first == r.second)
+ {
+ const auto& c (this->key_comp ());
+
+ for (key_type p (k); c.prefix (p); )
+ {
+ r = this->equal_range (p);
+ if (r.first != r.second)
+ break;
+ }
+ }
+
+ return r;
+#endif
+ }
}
diff --git a/libbutl/process-details.hxx b/libbutl/process-details.hxx
index cf7624d..10d5241 100644
--- a/libbutl/process-details.hxx
+++ b/libbutl/process-details.hxx
@@ -3,17 +3,25 @@
#pragma once
-#include <libbutl/ft/shared_mutex.hxx>
+#ifdef LIBBUTL_MINGW_STDTHREAD
-#ifdef __cpp_lib_modules_ts
-import std.core; //@@ MOD TMP (dummy std.threading).
-import std.threading;
-#else
-#include <mutex>
-#if defined(__cpp_lib_shared_mutex) || defined(__cpp_lib_shared_timed_mutex)
-# include <shared_mutex>
-#endif
-#endif
+# include <libbutl/mingw-shared_mutex.hxx>
+
+namespace butl
+{
+ using shared_mutex = mingw_stdthread::shared_mutex;
+ using ulock = mingw_stdthread::unique_lock<shared_mutex>;
+ using slock = mingw_stdthread::shared_lock<shared_mutex>;
+}
+
+#else // LIBBUTL_MINGW_STDTHREADS
+
+# include <libbutl/ft/shared_mutex.hxx>
+
+# include <mutex>
+# if defined(__cpp_lib_shared_mutex) || defined(__cpp_lib_shared_timed_mutex)
+# include <shared_mutex>
+# endif
namespace butl
{
@@ -41,7 +49,11 @@ namespace butl
using ulock = std::unique_lock<shared_mutex>;
using slock = ulock;
#endif
+}
+#endif // LIBBUTL_MINGW_STDTHREADS
+namespace butl
+{
// Mutex that is acquired to make a sequence of operations atomic in regards
// to child process spawning. Must be aquired for exclusive access for child
// process startup, and for shared access otherwise. Defined in process.cxx.
diff --git a/libbutl/process-io.cxx b/libbutl/process-io.cxx
index c29bbc0..0be3a77 100644
--- a/libbutl/process-io.cxx
+++ b/libbutl/process-io.cxx
@@ -1,36 +1,11 @@
// file : libbutl/process-io.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/process-io.mxx>
-#endif
-
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
-#include <ostream>
+#include <libbutl/process-io.hxx>
#include <cstring> // strchr()
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.process_io;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.process;
-#endif
-import butl.path-io;
-#else
-#include <libbutl/path-io.mxx>
-#endif
+#include <libbutl/path-io.hxx>
using namespace std;
diff --git a/libbutl/process-io.mxx b/libbutl/process-io.hxx
index d07a212..29d6d8b 100644
--- a/libbutl/process-io.mxx
+++ b/libbutl/process-io.hxx
@@ -1,32 +1,15 @@
-// file : libbutl/process-io.mxx -*- C++ -*-
+// file : libbutl/process-io.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <ostream>
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-export module butl.process_io;
-#ifdef __cpp_lib_modules_ts
-import std.core; //@@ MOD TMP (should not be needed).
-import std.io;
-#endif
-import butl.process;
-#else
-#include <libbutl/process.mxx>
-#endif
+#include <libbutl/process.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
inline std::ostream&
operator<< (std::ostream& o, const process_path& p)
diff --git a/libbutl/process-run.cxx b/libbutl/process-run.cxx
index c26c20d..b044ea1 100644
--- a/libbutl/process-run.cxx
+++ b/libbutl/process-run.cxx
@@ -1,35 +1,12 @@
// file : libbutl/process-run.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/process.mxx>
-#endif
+#include <libbutl/process.hxx>
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <cstdlib> // exit()
#include <iostream> // cerr
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.process;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-#endif
-import butl.utility; // operator<<(ostream,exception)
-#else
-#include <libbutl/utility.mxx>
-#endif
+#include <libbutl/utility.hxx> // operator<<(ostream,exception)
using namespace std;
@@ -47,7 +24,7 @@ namespace butl
try
{
return process (pp, cmd,
- in, out, err,
+ move (in), move (out), move (err),
cwd != nullptr ? cwd->string ().c_str () : nullptr,
envvars);
}
diff --git a/libbutl/process-run.txx b/libbutl/process-run.txx
index aa1e381..6c903a8 100644
--- a/libbutl/process-run.txx
+++ b/libbutl/process-run.txx
@@ -1,7 +1,9 @@
// file : libbutl/process-run.txx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+#include <utility> // forward(), index_sequence
+
+namespace butl
{
template <typename V>
void process_env::
@@ -85,21 +87,21 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
// valid file descriptor.
//
inline process::pipe
- process_stdin (const process::pipe& v)
+ process_stdin (process::pipe v)
{
assert (v.in >= 0);
return v;
}
inline process::pipe
- process_stdout (const process::pipe& v)
+ process_stdout (process::pipe v)
{
assert (v.out >= 0);
return v;
}
inline process::pipe
- process_stderr (const process::pipe& v)
+ process_stderr (process::pipe v)
{
assert (v.out >= 0);
return v;
@@ -129,13 +131,13 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
typename... A,
typename std::size_t... index>
process
- process_start (std::index_sequence<index...>,
- const C& cmdc,
- I&& in,
- O&& out,
- E&& err,
- const process_env& env,
- A&&... args)
+ process_start_impl (std::index_sequence<index...>,
+ const C& cmdc,
+ I&& in,
+ O&& out,
+ E&& err,
+ const process_env& env,
+ A&&... args)
{
// Map stdin/stdout/stderr arguments to their integer values, as expected
// by the process constructor.
@@ -168,7 +170,9 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
return process_start (env.cwd,
*env.path, cmd.data (),
env.vars,
- in_i, out_i, err_i);
+ std::move (in_i),
+ std::move (out_i),
+ std::move (err_i));
}
template <typename C,
@@ -184,13 +188,13 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
const process_env& env,
A&&... args)
{
- return process_start (std::index_sequence_for<A...> (),
- cmdc,
- std::forward<I> (in),
- std::forward<O> (out),
- std::forward<E> (err),
- env,
- std::forward<A> (args)...);
+ return process_start_impl (std::index_sequence_for<A...> (),
+ cmdc,
+ std::forward<I> (in),
+ std::forward<O> (out),
+ std::forward<E> (err),
+ env,
+ std::forward<A> (args)...);
}
template <typename I,
@@ -255,4 +259,45 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
env,
std::forward<A> (args)...);
}
+
+ template <typename C,
+ typename... A,
+ typename std::size_t... index>
+ void
+ process_print_impl (std::index_sequence<index...>,
+ const C& cmdc,
+ const process_env& env,
+ A&&... args)
+ {
+ // Construct the command line array.
+ //
+ const std::size_t args_size (sizeof... (args));
+
+ small_vector<const char*, args_size + 2> cmd;
+
+ assert (env.path != nullptr);
+ cmd.push_back (env.path->recall_string ());
+
+ std::string storage[args_size != 0 ? args_size : 1];
+
+ const char* dummy[] = {
+ nullptr, process_args_as_wrapper (cmd, args, storage[index])... };
+
+ cmd.push_back (dummy[0]); // NULL (and get rid of unused warning).
+
+ cmdc (cmd.data (), cmd.size ());
+ }
+
+ template <typename C,
+ typename... A>
+ inline void
+ process_print_callback (const C& cmdc,
+ const process_env& env,
+ A&&... args)
+ {
+ process_print_impl (std::index_sequence_for<A...> (),
+ cmdc,
+ env,
+ std::forward<A> (args)...);
+ }
}
diff --git a/libbutl/process.cxx b/libbutl/process.cxx
index f6433fb..e416807 100644
--- a/libbutl/process.cxx
+++ b/libbutl/process.cxx
@@ -1,9 +1,7 @@
// file : libbutl/process.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/process.mxx>
-#endif
+#include <libbutl/process.hxx>
#include <errno.h>
@@ -49,6 +47,14 @@
# elif defined(__NetBSD__) && __NetBSD__ >= 6
# define LIBBUTL_POSIX_SPAWN
//
+// On OpenBSD posix_spawn() appeared in 5.2 (see the man page for details).
+//
+# elif defined(__OpenBSD__)
+# include <sys/param.h> // OpenBSD (yyyymm)
+# if OpenBSD >= 201211 // 5.2 released on 1 Nov 2012.
+# define LIBBUTL_POSIX_SPAWN
+# endif
+//
// posix_spawn() appeared in Version 3 of the Single UNIX Specification that
// was implemented in MacOS 10.5 (see the man page for details).
//
@@ -87,29 +93,20 @@
# endif // _MSC_VER
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <vector>
-#include <chrono>
-#include <cstdint>
-#include <cstddef>
-#include <system_error>
-
#include <ios> // ios_base::failure
+#include <memory> // unique_ptr
#include <cstring> // strlen(), strchr(), strpbrk(), strncmp()
#include <utility> // move()
#include <ostream>
+#include <cassert>
#ifndef _WIN32
-#include <thread> // this_thread::sleep_for()
+# include <thread> // this_thread::sleep_for()
#else
-#include <map>
-#include <ratio> // milli
-#include <cstdlib> // __argv[]
-#include <algorithm> // find()
-#endif
+# include <map>
+# include <ratio> // milli
+# include <cstdlib> // __argv[]
+# include <algorithm> // find()
#endif
#include <libbutl/process-details.hxx>
@@ -119,32 +116,8 @@ namespace butl
shared_mutex process_spawn_mutex; // Out of module purview.
}
-#ifdef __cpp_modules_ts
-module butl.process;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-import std.threading; // Clang wants it in purview (see process-details.hxx).
-#endif
-import butl.path;
-import butl.fdstream;
-import butl.vector_view;
-import butl.small_vector;
-#endif
-
-#ifndef _WIN32
-import std.threading;
-#endif
-
-import butl.utility; // icasecmp()
-import butl.fdstream; // fdopen_null()
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/fdstream.mxx>
-#endif
+#include <libbutl/utility.hxx> // icasecmp()
+#include <libbutl/fdstream.hxx> // fdopen_null()
using namespace std;
@@ -217,7 +190,7 @@ namespace butl
}
void process::
- print (ostream& o, const char* const args[], size_t n)
+ print (ostream& o, const char* const* args, size_t n)
{
size_t m (0);
const char* const* p (args);
@@ -410,7 +383,7 @@ namespace butl
}
process::
- process (const process_path& pp, const char* args[],
+ process (const process_path& pp, const char* const* args,
pipe pin, pipe pout, pipe perr,
const char* cwd,
const char* const* evars)
@@ -670,6 +643,10 @@ namespace butl
{
// Child.
//
+ // NOTE: make sure not to call anything that may acquire a mutex that
+ // could be already acquired in another thread, most notably
+ // malloc(). @@ What about exceptions (all the fail() calls)?
+
// Duplicate the user-supplied (fd > -1) or the created pipe descriptor
// to the standard stream descriptor (read end for STDIN_FILENO, write
// end otherwise). Close the pipe afterwards.
@@ -729,6 +706,9 @@ namespace butl
try
{
+ // @@ TODO: redo without allocation (PATH_MAX?) Maybe
+ // also using C API to avoid exceptions.
+ //
if (e != nullptr)
setenv (string (v, e - v), e + 1);
else
@@ -736,6 +716,8 @@ namespace butl
}
catch (const system_error& e)
{
+ // @@ Should we assume this cannot throw?
+ //
throw process_child_error (e.code ().value ());
}
}
@@ -776,6 +758,13 @@ namespace butl
{
if (handle != 0)
{
+ // First close any open pipe ends for good measure but ignore any
+ // errors.
+ //
+ out_fd.reset ();
+ in_ofd.reset ();
+ in_efd.reset ();
+
int es;
int r (waitpid (handle, &es, 0));
handle = 0; // We have tried.
@@ -857,6 +846,12 @@ namespace butl
return getpid ();
}
+ process::handle_type process::
+ current_handle ()
+ {
+ return getpid ();
+ }
+
// process_exit
//
process_exit::
@@ -1383,7 +1378,7 @@ namespace butl
static map<string, bool> detect_msys_cache_;
process::
- process (const process_path& pp, const char* args[],
+ process (const process_path& pp, const char* const* args,
pipe pin, pipe pout, pipe perr,
const char* cwd,
const char* const* evars)
@@ -1812,7 +1807,6 @@ namespace butl
using namespace chrono;
-
// Retry for about 1 hour.
//
system_clock::duration timeout (1h);
@@ -1898,7 +1892,7 @@ namespace butl
return PeekNamedPipe (h, &c, 1, &n, nullptr, nullptr) && n == 1;
};
- // Hidden by butl::duration that is introduced via fdstream.mxx.
+ // Hidden by butl::duration that is introduced via fdstream.hxx.
//
using milli_duration = chrono::duration<DWORD, milli>;
@@ -1979,6 +1973,10 @@ namespace butl
{
if (handle != 0)
{
+ out_fd.reset ();
+ in_ofd.reset ();
+ in_efd.reset ();
+
DWORD es;
DWORD e (NO_ERROR);
if (WaitForSingleObject (handle, INFINITE) != WAIT_OBJECT_0 ||
@@ -2086,6 +2084,15 @@ namespace butl
return GetCurrentProcessId ();
}
+ process::handle_type process::
+ current_handle ()
+ {
+ // Note that the returned handle is a pseudo handle (-1) that does not
+ // need to be closed.
+ //
+ return GetCurrentProcess ();
+ }
+
// process_exit
//
process_exit::
diff --git a/libbutl/process.mxx b/libbutl/process.hxx
index 0677525..bbb7c89 100644
--- a/libbutl/process.mxx
+++ b/libbutl/process.hxx
@@ -1,17 +1,12 @@
-// file : libbutl/process.mxx -*- C++ -*-
+// file : libbutl/process.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
#ifndef _WIN32
# include <sys/types.h> // pid_t
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <chrono>
@@ -20,33 +15,15 @@
#include <cstdint> // uint32_t
#include <system_error>
-#include <utility> // move(), forward(), index_sequence
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.process;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.optional;
-import butl.fdstream; // auto_fd, fdpipe
-import butl.vector_view;
-import butl.small_vector;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/vector-view.mxx>
-#include <libbutl/small-vector.mxx>
-#endif
+#include <libbutl/path.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/fdstream.hxx> // auto_fd, fdpipe
+#include <libbutl/vector-view.hxx>
+#include <libbutl/small-vector.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
struct process_error: std::system_error
{
@@ -140,8 +117,8 @@ LIBBUTL_MODEXPORT namespace butl
// Moveable-only type.
//
- process_path (process_path&&);
- process_path& operator= (process_path&&);
+ process_path (process_path&&) noexcept;
+ process_path& operator= (process_path&&) noexcept;
process_path (const process_path&) = delete;
process_path& operator= (const process_path&) = delete;
@@ -292,7 +269,30 @@ LIBBUTL_MODEXPORT namespace butl
// the parent. So you should do this yourself, if required. For example,
// to redirect the child process stdout to stderr, you can do:
//
- // process p (..., 0, 2);
+ // process pr (..., 0, 2);
+ //
+ // Note also that the somewhat roundabout setup with -1 as a redirect
+ // "instruction" and out_fd/in_ofd/in_efd data members for the result
+ // helps to make sure the stream instances are destroyed before the
+ // process instance. For example:
+ //
+ // process pr (..., 0, -1, 2);
+ // ifdstream is (move (pr.in_ofd));
+ //
+ // This is important in case an exception is thrown where we want to make
+ // sure all our pipe ends are closed before we wait for the process exit
+ // (which happens in the process destructor).
+ //
+ // And speaking of the destruction order, another thing to keep in mind is
+ // that only one stream can use the skip mode (fdstream_mode::skip;
+ // because skipping is performed in the blocking mode) and the stream that
+ // skips should come first so that all other streams are destroyed/closed
+ // before it (failed that, we may end up in a deadlock). For example:
+ //
+ // process pr (..., -1, -1, -1);
+ // ifdstream is (move (pr.in_ofd), fdstream_mode::skip); // Must be first.
+ // ifdstream es (move (pr.in_efd));
+ // ofdstream os (move (pr.out_fd));
//
// The cwd argument allows to change the current working directory of the
// child process. NULL and empty arguments are ignored.
@@ -310,39 +310,104 @@ LIBBUTL_MODEXPORT namespace butl
// Note that the versions without the the process_path argument may
// temporarily change args[0] (see path_search() for details).
//
- process (const char* [],
+ process (const char**,
+ int in = 0, int out = 1, int err = 2,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (const process_path&, const char* const*,
+ int in = 0, int out = 1, int err = 2,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (std::vector<const char*>&,
int in = 0, int out = 1, int err = 2,
const char* cwd = nullptr,
const char* const* envvars = nullptr);
- process (const process_path&, const char* [],
+ process (const process_path&, const std::vector<const char*>&,
int in = 0, int out = 1, int err = 2,
const char* cwd = nullptr,
const char* const* envvars = nullptr);
// If the descriptors are pipes that you have created, then you should use
- // this constructor instead to communicate this information.
+ // this constructor instead to communicate this information (the parent
+ // end may need to be "probed" on Windows).
//
// For generality, if the "other" end of the pipe is -1, then assume this
// is not a pipe.
//
struct pipe
{
- int in = -1;
- int out = -1;
-
pipe () = default;
pipe (int i, int o): in (i), out (o) {}
explicit
pipe (const fdpipe& p): in (p.in.get ()), out (p.out.get ()) {}
+
+ // Transfer ownership to one end of the pipe.
+ //
+ pipe (auto_fd i, int o): in (i.release ()), out (o), own_in (true) {}
+ pipe (int i, auto_fd o): in (i), out (o.release ()), own_out (true) {}
+
+ // Moveable-only type.
+ //
+ pipe (pipe&&) noexcept;
+ pipe& operator= (pipe&&) noexcept;
+
+ pipe (const pipe&) = delete;
+ pipe& operator= (const pipe&) = delete;
+
+ ~pipe ();
+
+ public:
+ int in = -1;
+ int out = -1;
+
+ bool own_in = false;
+ bool own_out = false;
};
- process (const process_path&, const char* [],
+ process (const char**,
pipe in, pipe out, pipe err,
const char* cwd = nullptr,
const char* const* envvars = nullptr);
+ process (const char**,
+ int in, int out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (const process_path&, const char* const*,
+ pipe in, pipe out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (const process_path&, const char* const*,
+ int in, int out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (std::vector<const char*>&,
+ pipe in, pipe out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (std::vector<const char*>&,
+ int in, int out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (const process_path&, const std::vector<const char*>&,
+ pipe in, pipe out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (const process_path&, const std::vector<const char*>&,
+ int in, int out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
// The "piping" constructor, for example:
//
// process lhs (..., 0, -1); // Redirect stdout to a pipe.
@@ -351,16 +416,36 @@ LIBBUTL_MODEXPORT namespace butl
// rhs.wait (); // Wait for last first.
// lhs.wait ();
//
- process (const char* [],
+ process (const char**,
process&, int out = 1, int err = 2,
const char* cwd = nullptr,
const char* const* envvars = nullptr);
- process (const process_path&, const char* [],
+ process (const process_path&, const char* const*,
process&, int out = 1, int err = 2,
const char* cwd = nullptr,
const char* const* envvars = nullptr);
+ process (const char**,
+ process&, pipe out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (const char**,
+ process&, int out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (const process_path&, const char* const*,
+ process&, pipe out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
+ process (const process_path&, const char* const*,
+ process&, int out, pipe err,
+ const char* cwd = nullptr,
+ const char* const* envvars = nullptr);
+
// Wait for the process to terminate. Return true if the process
// terminated normally and with the zero exit code. Unless ignore_error
// is true, throw process_error if anything goes wrong. This function can
@@ -387,7 +472,7 @@ LIBBUTL_MODEXPORT namespace butl
// Note that the destructor will wait for the process but will ignore
// any errors and the exit status.
//
- ~process () {if (handle != 0) wait (true);}
+ ~process () { if (handle != 0) wait (true); }
// Process termination.
//
@@ -414,8 +499,8 @@ LIBBUTL_MODEXPORT namespace butl
// Moveable-only type.
//
- process (process&&);
- process& operator= (process&&);
+ process (process&&) noexcept;
+ process& operator= (process&&) noexcept (false); // Note: calls wait().
process (const process&) = delete;
process& operator= (const process&) = delete;
@@ -437,7 +522,7 @@ LIBBUTL_MODEXPORT namespace butl
//
// ... // E.g., print args[0].
//
- // process p (pp, args);
+ // process pr (pp, args);
//
// You can also specify the fallback directory which will be tried last.
// This, for example, can be used to implement the Windows "search in the
@@ -521,7 +606,7 @@ LIBBUTL_MODEXPORT namespace butl
// nameN arg arg ... nullptr nullptr
//
static void
- print (std::ostream&, const char* const args[], size_t n = 0);
+ print (std::ostream&, const char* const* args, size_t n = 0);
// Quote and escape the specified command line argument. If batch is true
// then also quote the equal (`=`), comma (`,`) and semicolon (`;`)
@@ -544,13 +629,16 @@ LIBBUTL_MODEXPORT namespace butl
public:
handle_type handle;
+ static handle_type
+ current_handle ();
+
// Absence means that the exit information is not (yet) known. This can be
// because you haven't called wait() yet or because wait() failed.
//
optional<process_exit> exit;
- // Use the following file descriptors to communicate with the new process's
- // standard streams.
+ // Use the following file descriptors to communicate with the new
+ // process's standard streams (if redirected to pipes; see above).
//
auto_fd out_fd; // Write to it to send to stdin.
auto_fd in_ofd; // Read from it to receive from stdout.
@@ -664,8 +752,8 @@ LIBBUTL_MODEXPORT namespace butl
// Moveable-only type.
//
- process_env (process_env&&);
- process_env& operator= (process_env&&);
+ process_env (process_env&&) noexcept;
+ process_env& operator= (process_env&&) noexcept;
process_env (const process_env&) = delete;
process_env& operator= (const process_env&) = delete;
@@ -701,7 +789,7 @@ LIBBUTL_MODEXPORT namespace butl
// command line or similar. It should be callable with the following
// signature:
//
- // void (const char*[], std::size_t)
+ // void (const char* const*, std::size_t)
//
template <typename C,
typename I,
@@ -742,6 +830,15 @@ LIBBUTL_MODEXPORT namespace butl
const process_env&,
A&&... args);
+ // Call the callback without actually running/starting anything.
+ //
+ template <typename C,
+ typename... A>
+ void
+ process_print_callback (const C&,
+ const process_env&,
+ A&&... args);
+
// Conversion of types to their C string representations. Can be overloaded
// (including via ADL) for custom types. The default implementation calls
// to_string() which covers all the numeric values via std::to_string () and
diff --git a/libbutl/process.ixx b/libbutl/process.ixx
index 7676ce3..e4db474 100644
--- a/libbutl/process.ixx
+++ b/libbutl/process.ixx
@@ -1,6 +1,9 @@
// file : libbutl/process.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
+#include <cassert>
+#include <utility> // move()
+
namespace butl
{
// process_path
@@ -32,7 +35,7 @@ namespace butl
args0_ (nullptr) {}
inline process_path::
- process_path (process_path&& p)
+ process_path (process_path&& p) noexcept
: effect (std::move (p.effect)),
args0_ (p.args0_)
{
@@ -45,7 +48,7 @@ namespace butl
}
inline process_path& process_path::
- operator= (process_path&& p)
+ operator= (process_path&& p) noexcept
{
if (this != &p)
{
@@ -121,6 +124,42 @@ namespace butl
}
#endif
+ // process::pipe
+ //
+ inline process::pipe::
+ pipe (pipe&& p) noexcept
+ : in (p.in), out (p.out), own_in (p.own_in), own_out (p.own_out)
+ {
+ p.in = p.out = -1;
+ }
+
+ inline process::pipe& process::pipe::
+ operator= (pipe&& p) noexcept
+ {
+ if (this != &p)
+ {
+ int d (own_in ? in : own_out ? out : -1);
+ if (d != -1)
+ fdclose (d);
+
+ in = p.in;
+ out = p.out;
+ own_in = p.own_in;
+ own_out = p.own_out;
+
+ p.in = p.out = -1;
+ }
+ return *this;
+ }
+
+ inline process::pipe::
+ ~pipe ()
+ {
+ int d (own_in ? in : own_out ? out : -1);
+ if (d != -1)
+ fdclose (d);
+ }
+
// process
//
#ifndef _WIN32
@@ -175,21 +214,37 @@ namespace butl
inline process::
process (optional<process_exit> e)
- : handle (0),
- exit (std::move (e)),
- out_fd (-1),
- in_ofd (-1),
- in_efd (-1)
+ : handle (0), exit (std::move (e))
+ {
+ }
+
+ inline process::
+ process (const process_path& pp, const char* const* args,
+ int in, int out, int err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (pp, args,
+ pipe (in, -1), pipe (-1, out), pipe (-1, err),
+ cwd,
+ envvars)
+ {
+ }
+
+ inline process::
+ process (const char** args,
+ int in, int out, int err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (path_search (args[0]), args, in, out, err, cwd, envvars)
{
}
inline process::
- process (const process_path& pp, const char* args[],
+ process (const process_path& pp, const std::vector<const char*>& args,
int in, int out, int err,
const char* cwd,
const char* const* envvars)
- : process (pp,
- args,
+ : process (pp, args.data (),
pipe (in, -1), pipe (-1, out), pipe (-1, err),
cwd,
envvars)
@@ -197,32 +252,166 @@ namespace butl
}
inline process::
- process (const char* args[],
+ process (std::vector<const char*>& args,
int in, int out, int err,
const char* cwd,
const char* const* envvars)
- : process (path_search (args[0]), args, in, out, err, cwd, envvars) {}
+ : process (path_search (args[0]), args.data (),
+ in, out, err,
+ cwd,
+ envvars)
+ {
+ }
+
+ inline process::
+ process (const char** args,
+ pipe in, pipe out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (path_search (args[0]), args,
+ std::move (in), std::move (out), std::move (err),
+ cwd, envvars)
+ {
+ }
+
+ inline process::
+ process (const char** args,
+ int in, int out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (path_search (args[0]), args,
+ pipe (in, -1), pipe (-1, out), std::move (err),
+ cwd, envvars)
+ {
+ }
+
+ inline process::
+ process (const process_path& pp, const char* const* args,
+ int in, int out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (pp, args,
+ pipe (in, -1), pipe (-1, out), std::move (err),
+ cwd,
+ envvars)
+ {
+ }
+
+ inline process::
+ process (std::vector<const char*>& args,
+ pipe in, pipe out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (path_search (args[0]), args.data (),
+ std::move (in), std::move (out), std::move (err),
+ cwd,
+ envvars)
+ {
+ }
+
+ inline process::
+ process (std::vector<const char*>& args,
+ int in, int out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (path_search (args[0]), args.data (),
+ pipe (in, -1), pipe (-1, out), std::move (err),
+ cwd,
+ envvars)
+ {
+ }
+
+ inline process::
+ process (const process_path& pp, const std::vector<const char*>& args,
+ pipe in, pipe out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (pp, args.data (),
+ std::move (in), std::move (out), std::move (err),
+ cwd,
+ envvars)
+ {
+ }
+
+ inline process::
+ process (const process_path& pp, const std::vector<const char*>& args,
+ int in, int out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (pp, args.data (),
+ pipe (in, -1), pipe (-1, out), std::move (err),
+ cwd,
+ envvars)
+ {
+ }
+
+ inline process::
+ process (const process_path& pp, const char* const* args,
+ process& in, pipe out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (pp, args,
+ [&in] ()
+ {
+ assert (in.in_ofd != nullfd); // Should be a pipe.
+ return process::pipe (std::move (in.in_ofd), -1);
+ } (),
+ std::move (out), std::move (err),
+ cwd, envvars)
+ {
+ }
inline process::
- process (const process_path& pp, const char* args[],
+ process (const process_path& pp, const char* const* args,
process& in, int out, int err,
const char* cwd,
const char* const* envvars)
- : process (pp, args, in.in_ofd.get (), out, err, cwd, envvars)
+ : process (pp, args, in, pipe (-1, out), pipe (-1, err), cwd, envvars)
{
- assert (in.in_ofd.get () != -1); // Should be a pipe.
- in.in_ofd.reset (); // Close it on our side.
}
inline process::
- process (const char* args[],
+ process (const char** args,
process& in, int out, int err,
const char* cwd,
const char* const* envvars)
- : process (path_search (args[0]), args, in, out, err, cwd, envvars) {}
+ : process (path_search (args[0]), args, in, out, err, cwd, envvars)
+ {
+ }
+
+ inline process::
+ process (const char** args,
+ process& in, pipe out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (path_search (args[0]), args,
+ in, std::move (out), std::move (err),
+ cwd, envvars)
+ {
+ }
+
+ inline process::
+ process (const char** args,
+ process& in, int out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (path_search (args[0]), args,
+ in, pipe (-1, out), std::move (err),
+ cwd, envvars)
+ {
+ }
+
+ inline process::
+ process (const process_path& pp, const char* const* args,
+ process& in, int out, pipe err,
+ const char* cwd,
+ const char* const* envvars)
+ : process (pp, args, in, pipe (-1, out), std::move (err), cwd, envvars)
+ {
+ }
inline process::
- process (process&& p)
+ process (process&& p) noexcept
: handle (p.handle),
exit (std::move (p.exit)),
out_fd (std::move (p.out_fd)),
@@ -233,7 +422,7 @@ namespace butl
}
inline process& process::
- operator= (process&& p)
+ operator= (process&& p) noexcept (false)
{
if (this != &p)
{
@@ -270,13 +459,13 @@ namespace butl
// process_env
//
inline process_env::
- process_env (process_env&& e)
+ process_env (process_env&& e) noexcept
{
*this = std::move (e);
}
inline process_env& process_env::
- operator= (process_env&& e)
+ operator= (process_env&& e) noexcept
{
if (this != &e)
{
diff --git a/libbutl/project-name.cxx b/libbutl/project-name.cxx
index 7a14b49..a7ed8a8 100644
--- a/libbutl/project-name.cxx
+++ b/libbutl/project-name.cxx
@@ -1,38 +1,16 @@
// file : libbutl/project-name.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/project-name.mxx>
-#endif
+#include <libbutl/project-name.hxx>
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <utility> // move()
#include <algorithm> // find()
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.project_name;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.utility;
-#endif
-
-import butl.path; // path::traits
-import butl.utility; // alpha(), alnum()
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/utility.mxx>
-#endif
+
+#include <libbutl/path.hxx> // path::traits
+#include <libbutl/utility.hxx> // alpha(), alnum()
using namespace std;
diff --git a/libbutl/project-name.mxx b/libbutl/project-name.hxx
index 1117e28..6e1f925 100644
--- a/libbutl/project-name.mxx
+++ b/libbutl/project-name.hxx
@@ -1,34 +1,17 @@
-// file : libbutl/project-name.mxx -*- C++ -*-
+// file : libbutl/project-name.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <utility> // move()
#include <ostream>
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-export module butl.project_name;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.utility; // icasecmp(), sanitize_identifier()
-#else
-#include <libbutl/utility.mxx>
-#endif
+#include <libbutl/utility.hxx> // icasecmp(), sanitize_identifier()
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Build system project name.
//
diff --git a/libbutl/prompt.cxx b/libbutl/prompt.cxx
index 1c0820a..154522c 100644
--- a/libbutl/prompt.cxx
+++ b/libbutl/prompt.cxx
@@ -1,33 +1,11 @@
// file : libbutl/prompt.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/prompt.mxx>
-#endif
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
+#include <libbutl/prompt.hxx>
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.prompt;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
-import butl.diagnostics;
-#else
-#include <libbutl/diagnostics.mxx> // diag_stream
-#endif
+#include <libbutl/diagnostics.hxx> // diag_stream
using namespace std;
@@ -66,8 +44,8 @@ namespace butl
if (!e)
a = def;
}
- } while (a != "y" && a != "n");
+ } while (a != "y" && a != "Y" && a != "n" && a != "N");
- return a == "y";
+ return a == "y" || a == "Y";
}
}
diff --git a/libbutl/prompt.mxx b/libbutl/prompt.hxx
index 2489b2f..2a07708 100644
--- a/libbutl/prompt.mxx
+++ b/libbutl/prompt.hxx
@@ -1,28 +1,13 @@
-// file : libbutl/prompt.mxx -*- C++ -*-
+// file : libbutl/prompt.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.prompt;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// The Y/N prompt. The def argument, if specified, should be either 'y' or
// 'n'. It is used as the default answer, in case the user just hits enter.
@@ -30,6 +15,10 @@ LIBBUTL_MODEXPORT namespace butl
// Write the prompt to diag_stream. Throw ios_base::failure if no answer
// could be extracted from stdin (for example, because it was closed).
//
+ // Note that the implementation accepts both lower and upper case y/n as
+ // valid answers (apparently the capitalized default answer confuses some
+ // users into answering with capital letters).
+ //
LIBBUTL_SYMEXPORT bool
yn_prompt (const std::string&, char def = '\0');
}
diff --git a/libbutl/regex.cxx b/libbutl/regex.cxx
index 83e296c..34536f2 100644
--- a/libbutl/regex.cxx
+++ b/libbutl/regex.cxx
@@ -1,42 +1,17 @@
// file : libbutl/regex.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/regex.mxx>
-#endif
-
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
-#include <regex>
-#include <string>
+#include <libbutl/regex.hxx>
#include <ostream>
#include <sstream>
#include <stdexcept> // runtime_error
+
#if defined(_MSC_VER) && _MSC_VER < 2000
# include <cstring> // strstr()
#endif
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-module butl.regex;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-import std.regex;
-#endif
-#endif
-
-import butl.utility; // operator<<(ostream, exception)
-#else
-#include <libbutl/utility.mxx>
-#endif
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
namespace std
{
diff --git a/libbutl/regex.mxx b/libbutl/regex.hxx
index b1ba1b9..9b31075 100644
--- a/libbutl/regex.mxx
+++ b/libbutl/regex.hxx
@@ -1,23 +1,13 @@
-// file : libbutl/regex.mxx -*- C++ -*-
+// file : libbutl/regex.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-
-// C includes.
-#ifndef __cpp_lib_modules_ts
#include <regex>
#include <iosfwd>
#include <string>
#include <utility> // pair
-
-#include <locale>
-#include <cstddef> // size_t
-#include <utility> // move(), make_pair()
-#include <stdexcept> // invalid_argument
-#endif
+#include <cstddef> // size_t
#if defined(__clang__)
# if __has_include(<__config>)
@@ -25,20 +15,9 @@
# endif
#endif
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.regex;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-import std.regex; // @@ MOD TODO should probably be re-exported.
-#endif
-#endif
-
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// The regex semantics for the following functions is like that of
// std::regex_replace() extended the standard ECMA-262 substitution escape
@@ -141,7 +120,7 @@ LIBBUTL_MODEXPORT namespace butl
regex_replace_parse (const C*, size_t, size_t& end);
}
-LIBBUTL_MODEXPORT namespace std
+namespace std
{
// Print regex error description but only if it is meaningful (this is also
// why we have to print leading colon).
diff --git a/libbutl/regex.ixx b/libbutl/regex.ixx
index f55cb07..08962cf 100644
--- a/libbutl/regex.ixx
+++ b/libbutl/regex.ixx
@@ -1,7 +1,9 @@
// file : libbutl/regex.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+#include <utility> // move(), make_pair()
+
+namespace butl
{
template <typename C>
inline std::pair<std::basic_string<C>, bool>
diff --git a/libbutl/regex.txx b/libbutl/regex.txx
index 8fe5308..214d949 100644
--- a/libbutl/regex.txx
+++ b/libbutl/regex.txx
@@ -1,7 +1,10 @@
// file : libbutl/regex.txx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+#include <locale>
+#include <stdexcept> // invalid_argument
+
+namespace butl
{
template <typename C>
std::basic_string<C>
diff --git a/libbutl/semantic-version.cxx b/libbutl/semantic-version.cxx
index 445890d..9e0a1ef 100644
--- a/libbutl/semantic-version.cxx
+++ b/libbutl/semantic-version.cxx
@@ -1,38 +1,12 @@
// file : libbutl/semantic-version.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/semantic-version.mxx>
-#endif
+#include <libbutl/semantic-version.hxx>
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <cstddef>
-#include <cstdint>
-#include <ostream>
-
#include <cstring> // strchr()
#include <utility> // move()
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.semantic_version;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.optional;
-#endif
-#else
-#endif
using namespace std;
@@ -79,9 +53,9 @@ namespace butl
}
semantic_version::
- semantic_version (const std::string& s, size_t p, const char* bs)
+ semantic_version (const std::string& s, size_t p, flags fs, const char* bs)
{
- semantic_version_result r (parse_semantic_version_impl (s, p, bs));
+ semantic_version_result r (parse_semantic_version_impl (s, p, fs, bs));
if (r.version)
*this = move (*r.version);
@@ -97,8 +71,27 @@ namespace butl
uint64_t min = 0, uint64_t max = uint64_t (~0));
semantic_version_result
- parse_semantic_version_impl (const string& s, size_t p, const char* bs)
+ parse_semantic_version_impl (const string& s, size_t p,
+ semantic_version::flags fs,
+ const char* bs)
{
+ bool allow_build ((fs & semantic_version::allow_build) != 0);
+
+ // If build separators are specified, then the allow_build flag must be
+ // specified explicitly.
+ //
+ assert (bs == nullptr || allow_build);
+
+ if (allow_build && bs == nullptr)
+ bs = "-+";
+
+ bool require_minor ((fs & semantic_version::allow_omit_minor) == 0);
+
+ if (!require_minor)
+ fs |= semantic_version::allow_omit_patch;
+
+ bool require_patch ((fs & semantic_version::allow_omit_patch) == 0);
+
auto bail = [] (string m)
{
return semantic_version_result {nullopt, move (m)};
@@ -109,31 +102,47 @@ namespace butl
if (!parse_uint64 (s, p, r.major))
return bail ("invalid major version");
- if (s[p] != '.')
- return bail ("'.' expected after major version");
-
- if (!parse_uint64 (s, ++p, r.minor))
- return bail ("invalid minor version");
-
- if (s[p] == '.')
+ if (s[p] == '.') // Is there a minor version?
{
- // Treat it as build if failed to parse as patch (e.g., 1.2.alpha).
+ // Try to parse the minor version and treat it as build on failure
+ // (e.g., 1.alpha).
//
- if (!parse_uint64 (s, ++p, r.patch))
+ if (parse_uint64 (s, ++p, r.minor))
+ {
+ if (s[p] == '.') // Is there a patch version?
+ {
+ // Try to parse the patch version and treat it as build on failure
+ // (e.g., 1.2.alpha).
+ //
+ if (parse_uint64 (s, ++p, r.patch))
+ ;
+ else
+ {
+ if (require_patch)
+ return bail ("invalid patch version");
+
+ --p;
+ // Fall through.
+ }
+ }
+ else if (require_patch)
+ return bail ("'.' expected after minor version");
+ }
+ else
{
- //if (require_patch)
- // return bail ("invalid patch version");
+ if (require_minor)
+ return bail ("invalid minor version");
--p;
// Fall through.
}
}
- //else if (require_patch)
- // return bail ("'.' expected after minor version");
+ else if (require_minor)
+ return bail ("'.' expected after major version");
if (char c = s[p])
{
- if (bs == nullptr || (*bs != '\0' && strchr (bs, c) == nullptr))
+ if (!allow_build || (*bs != '\0' && strchr (bs, c) == nullptr))
return bail ("junk after version");
r.build.assign (s, p, string::npos);
diff --git a/libbutl/semantic-version.mxx b/libbutl/semantic-version.hxx
index 566d192..4eba38a 100644
--- a/libbutl/semantic-version.mxx
+++ b/libbutl/semantic-version.hxx
@@ -1,32 +1,15 @@
-// file : libbutl/semantic-version.mxx -*- C++ -*-
+// file : libbutl/semantic-version.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-
-// C includes.
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <cstddef> // size_t
#include <cstdint> // uint*_t
#include <utility> // move()
#include <ostream>
-#endif
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.semantic_version;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.optional;
-#else
-#include <libbutl/optional.mxx>
-#endif
+#include <libbutl/optional.hxx>
#include <libbutl/export.hxx>
@@ -40,19 +23,13 @@ import butl.optional;
# undef minor
#endif
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Semantic or semantic-like version.
//
- // <major>.<minor>[.<patch>][<build>]
+ // <major>[.<minor>[.<patch>]][<build>]
//
- // If the patch component is absent, then it defaults to 0.
- //
- // @@ Currently there is no way to enforce the three-component version.
- // Supporting this will require changing allow_build to a bit-wise
- // flag. See parse_semantic_version_impl() for some sketched code.
- // We may also want to pass these flags to string() to not print
- // 0 patch.
+ // If the minor and patch components are absent, then they default to 0.
//
// By default, a version containing the <build> component is considered
// valid only if separated from <patch> with '-' (semver pre-release) or '+'
@@ -80,23 +57,36 @@ LIBBUTL_MODEXPORT namespace butl
std::uint64_t patch,
std::string build = "");
- // The build_separators argument can be NULL (no build component allowed),
- // empty (any build component allowed), or a string of characters to allow
- // as separators. When allow_build is true build_separators defaults to
- // "-+".
+ // If the allow_build flag is specified, then build_separators argument
+ // can be a string of characters to allow as separators, empty (any build
+ // component allowed), or NULL (defaults to "-+").
//
- explicit
- semantic_version (const std::string&, bool allow_build = true);
+ // Note: allow_omit_minor implies allow_omit_patch.
+ //
+ enum flags
+ {
+ none = 0, // Exact <major>.<minor>.<patch> form.
+ allow_omit_minor = 0x01, // Allow <major> form.
+ allow_omit_patch = 0x02, // Allow <major>.<minor> form.
+ allow_build = 0x04, // Allow <major>.<minor>.<patch>-<build> form.
+ };
- semantic_version (const std::string&, const char* build_separators);
+ explicit
+ semantic_version (const std::string&,
+ flags = none,
+ const char* build_separators = nullptr);
// As above but parse from the specified position until the end of the
// string.
//
- semantic_version (const std::string&, std::size_t pos, bool = true);
-
- semantic_version (const std::string&, std::size_t pos, const char*);
+ semantic_version (const std::string&,
+ std::size_t pos,
+ flags = none,
+ const char* = nullptr);
+ // @@ We may also want to pass allow_* flags not to print 0 minor/patch or
+ // maybe invent ignore_* flags.
+ //
std::string
string (bool ignore_build = false) const;
@@ -133,16 +123,15 @@ LIBBUTL_MODEXPORT namespace butl
// Try to parse a string as a semantic version returning nullopt if invalid.
//
optional<semantic_version>
- parse_semantic_version (const std::string&, bool allow_build = true);
-
- optional<semantic_version>
- parse_semantic_version (const std::string&, const char* build_separators);
-
- optional<semantic_version>
- parse_semantic_version (const std::string&, std::size_t pos, bool = true);
+ parse_semantic_version (const std::string&,
+ semantic_version::flags = semantic_version::none,
+ const char* build_separators = nullptr);
optional<semantic_version>
- parse_semantic_version (const std::string&, std::size_t pos, const char*);
+ parse_semantic_version (const std::string&,
+ std::size_t pos,
+ semantic_version::flags = semantic_version::none,
+ const char* = nullptr);
// NOTE: comparison operators take the build component into account.
//
@@ -187,6 +176,18 @@ LIBBUTL_MODEXPORT namespace butl
{
return o << x.string ();
}
+
+ semantic_version::flags
+ operator& (semantic_version::flags, semantic_version::flags);
+
+ semantic_version::flags
+ operator| (semantic_version::flags, semantic_version::flags);
+
+ semantic_version::flags
+ operator&= (semantic_version::flags&, semantic_version::flags);
+
+ semantic_version::flags
+ operator|= (semantic_version::flags&, semantic_version::flags);
}
#include <libbutl/semantic-version.ixx>
diff --git a/libbutl/semantic-version.ixx b/libbutl/semantic-version.ixx
index 6bf7584..8de1554 100644
--- a/libbutl/semantic-version.ixx
+++ b/libbutl/semantic-version.ixx
@@ -15,23 +15,9 @@ namespace butl
{
}
- // Note: the order is important to MinGW GCC (DLL linkage).
- //
inline semantic_version::
- semantic_version (const std::string& s, std::size_t p, bool ab)
- : semantic_version (s, p, ab ? "-+" : nullptr)
- {
- }
-
- inline semantic_version::
- semantic_version (const std::string& s, const char* bs)
- : semantic_version (s, 0, bs)
- {
- }
-
- inline semantic_version::
- semantic_version (const std::string& s, bool ab)
- : semantic_version (s, ab ? "-+" : nullptr)
+ semantic_version (const std::string& s, flags fs, const char* bs)
+ : semantic_version (s, 0, fs, bs)
{
}
@@ -42,29 +28,53 @@ namespace butl
};
LIBBUTL_SYMEXPORT semantic_version_result
- parse_semantic_version_impl (const std::string&, std::size_t, const char*);
+ parse_semantic_version_impl (const std::string&,
+ std::size_t,
+ semantic_version::flags,
+ const char*);
inline optional<semantic_version>
- parse_semantic_version (const std::string& s, bool ab)
+ parse_semantic_version (const std::string& s,
+ semantic_version::flags fs,
+ const char* bs)
{
- return parse_semantic_version (s, ab ? "-+" : nullptr);
+ return parse_semantic_version_impl (s, 0, fs, bs).version;
}
inline optional<semantic_version>
- parse_semantic_version (const std::string& s, const char* bs)
+ parse_semantic_version (const std::string& s,
+ std::size_t p,
+ semantic_version::flags fs,
+ const char* bs)
{
- return parse_semantic_version_impl (s, 0, bs).version;
+ return parse_semantic_version_impl (s, p, fs, bs).version;
}
- inline optional<semantic_version>
- parse_semantic_version (const std::string& s, std::size_t p, bool ab)
+ inline semantic_version::flags
+ operator&= (semantic_version::flags& x, semantic_version::flags y)
{
- return parse_semantic_version (s, p, ab ? "-+" : nullptr);
+ return x = static_cast<semantic_version::flags> (
+ static_cast<std::uint16_t> (x) &
+ static_cast<std::uint16_t> (y));
}
- inline optional<semantic_version>
- parse_semantic_version (const std::string& s, std::size_t p, const char* bs)
+ inline semantic_version::flags
+ operator|= (semantic_version::flags& x, semantic_version::flags y)
+ {
+ return x = static_cast<semantic_version::flags> (
+ static_cast<std::uint16_t> (x) |
+ static_cast<std::uint16_t> (y));
+ }
+
+ inline semantic_version::flags
+ operator& (semantic_version::flags x, semantic_version::flags y)
+ {
+ return x &= y;
+ }
+
+ inline semantic_version::flags
+ operator| (semantic_version::flags x, semantic_version::flags y)
{
- return parse_semantic_version_impl (s, p, bs).version;
+ return x |= y;
}
}
diff --git a/libbutl/sendmail.cxx b/libbutl/sendmail.cxx
index 1038cf4..5fec1a6 100644
--- a/libbutl/sendmail.cxx
+++ b/libbutl/sendmail.cxx
@@ -1,32 +1,7 @@
// file : libbutl/sendmail.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/sendmail.mxx>
-#endif
-
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.sendmail;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.process;
-import butl.fdstream;
-import butl.small_vector;
-#endif
-
-#endif
+#include <libbutl/sendmail.hxx>
using namespace std;
diff --git a/libbutl/sendmail.mxx b/libbutl/sendmail.hxx
index 0d5b239..97a4d82 100644
--- a/libbutl/sendmail.mxx
+++ b/libbutl/sendmail.hxx
@@ -1,38 +1,17 @@
-// file : libbutl/sendmail.mxx -*- C++ -*-
+// file : libbutl/sendmail.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
-#include <cstddef> // size_t
-#include <utility> // move(), forward()
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.sendmail;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.process;
-import butl.fdstream;
-import butl.small_vector;
-#else
-#include <libbutl/process.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/small-vector.mxx>
-#endif
+#include <libbutl/process.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/small-vector.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Send email using the sendmail(1) program.
//
diff --git a/libbutl/sendmail.ixx b/libbutl/sendmail.ixx
index 105c1af..35b5c47 100644
--- a/libbutl/sendmail.ixx
+++ b/libbutl/sendmail.ixx
@@ -1,7 +1,10 @@
// file : libbutl/sendmail.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+#include <cstddef> // size_t
+#include <utility> // move(), forward()
+
+namespace butl
{
template <typename E, typename... O>
inline sendmail::
diff --git a/libbutl/sha1.c b/libbutl/sha1.c
index 37e862e..98fce5e 100644
--- a/libbutl/sha1.c
+++ b/libbutl/sha1.c
@@ -121,11 +121,17 @@ main ()
#include <string.h>
+/* Assume if bzero/bcopy are defined as macros, then they do what we need. */
+
/* void bzero(void *s, size_t n); */
-#define bzero(s, n) memset((s), 0, (n))
+#ifndef bzero
+# define bzero(s, n) memset((s), 0, (n))
+#endif
/* void bcopy(const void *s1, void *s2, size_t n); */
-#define bcopy(s1, s2, n) memmove((s2), (s1), (n))
+#ifndef bcopy
+# define bcopy(s1, s2, n) memmove((s2), (s1), (n))
+#endif
/* The rest is the unmodified (except for adjusting function declarations and
adding a few explicit casts to make compilable in C++ without warnings)
diff --git a/libbutl/sha1.cxx b/libbutl/sha1.cxx
index f4a6bad..e546922 100644
--- a/libbutl/sha1.cxx
+++ b/libbutl/sha1.cxx
@@ -1,9 +1,7 @@
// file : libbutl/sha1.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/sha1.mxx>
-#endif
+#include <libbutl/sha1.hxx>
// C interface for sha1c.
//
@@ -42,29 +40,9 @@ extern "C"
#define SHA1_Final(x, y) sha1_result((y), (char(&)[20])(x))
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <cstddef>
-#include <cstdint>
-
#include <istream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.sha1;
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
-#else
#include <libbutl/bufstreambuf.hxx>
-#endif
using namespace std;
diff --git a/libbutl/sha1.mxx b/libbutl/sha1.hxx
index f6fafc0..62710f4 100644
--- a/libbutl/sha1.mxx
+++ b/libbutl/sha1.hxx
@@ -1,32 +1,17 @@
-// file : libbutl/sha1.mxx -*- C++ -*-
+// file : libbutl/sha1.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <iosfwd> // istream
#include <string>
#include <cstddef> // size_t
#include <cstdint>
#include <cstring> // strlen()
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.sha1;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// SHA1 checksum calculator.
//
diff --git a/libbutl/sha256.cxx b/libbutl/sha256.cxx
index 8a34402..95987ec 100644
--- a/libbutl/sha256.cxx
+++ b/libbutl/sha256.cxx
@@ -1,9 +1,7 @@
// file : libbutl/sha256.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/sha256.mxx>
-#endif
+#include <libbutl/sha256.hxx>
// C interface for sha256c.
//
@@ -26,39 +24,13 @@ extern "C"
#include "sha256c.c"
}
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <cstddef>
-#include <cstdint>
-
#include <cctype> // isxdigit()
+#include <cassert>
#include <istream>
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.sha256;
-
-// Only imports additional to interface.
-#ifdef __cpp_lib_modules_ts
-import std.io;
-#endif
-
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
-import butl.utility; // *case()
-#else
-#include <libbutl/utility.mxx>
+#include <libbutl/utility.hxx> // *case()
#include <libbutl/bufstreambuf.hxx>
-#endif
using namespace std;
diff --git a/libbutl/sha256.mxx b/libbutl/sha256.hxx
index d5128b1..566068f 100644
--- a/libbutl/sha256.mxx
+++ b/libbutl/sha256.hxx
@@ -1,33 +1,18 @@
-// file : libbutl/sha256.mxx -*- C++ -*-
+// file : libbutl/sha256.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iosfwd> // istream
#include <cstddef> // size_t
#include <cstdint>
#include <cstring> // strlen(), memcpy()
#include <type_traits> // enable_if, is_integral
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.sha256;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// SHA256 checksum calculator.
//
diff --git a/libbutl/small-allocator.mxx b/libbutl/small-allocator.hxx
index 5ef74be..429ba41 100644
--- a/libbutl/small-allocator.mxx
+++ b/libbutl/small-allocator.hxx
@@ -1,30 +1,16 @@
-// file : libbutl/small-allocator.mxx -*- C++ -*-
+// file : libbutl/small-allocator.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <cstddef> // size_t
#include <utility> // move()
#include <type_traits> // true_type, is_same
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.small_allocator;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Implementation of the allocator (and its buffer) for small containers.
//
diff --git a/libbutl/small-forward-list.mxx b/libbutl/small-forward-list.hxx
index 6aa4986..8d1cf68 100644
--- a/libbutl/small-forward-list.mxx
+++ b/libbutl/small-forward-list.hxx
@@ -1,31 +1,18 @@
-// file : libbutl/small-forward-list.mxx -*- C++ -*-
+// file : libbutl/small-forward-list.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-#ifndef __cpp_lib_modules_ts
#include <cstddef> // size_t
#include <utility> // move()
+#include <type_traits> // is_nothrow_move_constructible
#include <forward_list>
-#endif
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.small_forward_list;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.small_allocator;
-#else
-#include <libbutl/small-allocator.mxx>
-#endif
+#include <libbutl/small-allocator.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Issues and limitations.
//
@@ -115,14 +102,20 @@ LIBBUTL_MODEXPORT namespace butl
return *this;
}
+ // See small_vector for the move-constructor/assignment noexept
+ // expressions reasoning.
+ //
small_forward_list (small_forward_list&& v)
+#if !defined(_MSC_VER) || _MSC_VER > 1900
+ noexcept (std::is_nothrow_move_constructible<T>::value)
+#endif
: base_type (allocator_type (this))
{
*this = std::move (v); // Delegate to operator=(&&).
}
small_forward_list&
- operator= (small_forward_list&& v)
+ operator= (small_forward_list&& v) noexcept (false)
{
// VC14's implementation of operator=(&&) swaps pointers without regard
// for allocator (fixed in 15).
diff --git a/libbutl/small-list.mxx b/libbutl/small-list.hxx
index ff62192..7cb51fd 100644
--- a/libbutl/small-list.mxx
+++ b/libbutl/small-list.hxx
@@ -1,31 +1,18 @@
-// file : libbutl/small-list.mxx -*- C++ -*-
+// file : libbutl/small-list.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-#ifndef __cpp_lib_modules_ts
#include <list>
#include <cstddef> // size_t
#include <utility> // move()
-#endif
+#include <type_traits> // is_nothrow_move_constructible
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.small_list;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.small_allocator;
-#else
-#include <libbutl/small-allocator.mxx>
-#endif
+#include <libbutl/small-allocator.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Issues and limitations.
//
@@ -117,14 +104,20 @@ LIBBUTL_MODEXPORT namespace butl
return *this;
}
+ // See small_vector for the move-constructor/assignment noexept
+ // expressions reasoning.
+ //
small_list (small_list&& v)
+#if !defined(__GLIBCXX__) && (!defined(_MSC_VER) || _MSC_VER > 1900)
+ noexcept (std::is_nothrow_move_constructible<T>::value)
+#endif
: base_type (allocator_type (this))
{
*this = std::move (v); // Delegate to operator=(&&).
}
small_list&
- operator= (small_list&& v)
+ operator= (small_list&& v) noexcept (false)
{
// libstdc++'s implementation prior to GCC 6 is broken (calls swap()).
// Since there is no easy way to determine this library's version, for
@@ -136,7 +129,7 @@ LIBBUTL_MODEXPORT namespace butl
#if defined(__GLIBCXX__) || (defined(_MSC_VER) && _MSC_VER <= 1900)
this->clear ();
for (T& x: v)
- this->push_back (std::move (x));
+ this->push_back (std::move (x)); // Note: can throw bad_alloc.
v.clear ();
#else
// Note: propagate_on_container_move_assignment = false
diff --git a/libbutl/small-vector-odb.hxx b/libbutl/small-vector-odb.hxx
index af9d96c..289ca38 100644
--- a/libbutl/small-vector-odb.hxx
+++ b/libbutl/small-vector-odb.hxx
@@ -5,7 +5,7 @@
#include <odb/pre.hxx>
-#include <libbutl/small-vector.mxx>
+#include <libbutl/small-vector.hxx>
#include <odb/container-traits.hxx>
diff --git a/libbutl/small-vector.mxx b/libbutl/small-vector.hxx
index 7f9bb1e..44a3ef5 100644
--- a/libbutl/small-vector.mxx
+++ b/libbutl/small-vector.hxx
@@ -1,31 +1,18 @@
-// file : libbutl/small-vector.mxx -*- C++ -*-
+// file : libbutl/small-vector.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-#ifndef __cpp_lib_modules_ts
#include <vector>
#include <cstddef> // size_t
#include <utility> // move()
-#endif
-
-// Other includes.
+#include <type_traits> // is_nothrow_move_constructible
-#ifdef __cpp_modules_ts
-export module butl.small_vector;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.small_allocator;
-#else
-#include <libbutl/small-allocator.mxx>
-#endif
+#include <libbutl/small-allocator.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Issues and limitations.
//
@@ -38,6 +25,9 @@ LIBBUTL_MODEXPORT namespace butl
//
// - swap() is deleted (see notes below).
//
+ // - In contrast to std::vector, the references, pointers, and iterators
+ // referring to elements are invalidated after moving from it.
+ //
template <typename T, std::size_t N>
class small_vector: private small_allocator_buffer<T, N>,
public std::vector<T, small_allocator<T, N>>
@@ -118,7 +108,25 @@ LIBBUTL_MODEXPORT namespace butl
return *this;
}
+ // Note that while the move constructor is implemented via the move
+ // assignment it may not throw if the value type is no-throw move
+ // constructible.
+ //
+ // Specifically, if v.size() > N then allocators evaluate as equal and the
+ // buffer ownership is transferred. Otherwise, the allocators do not
+ // evaluate as equal and the individual elements are move-constructed in
+ // the preallocated buffer.
+ //
+ // Also note that this constructor ends up calling
+ // base_type::operator=(base_type&&) whose noexcept expression evaluates
+ // to false (propagate_on_container_move_assignment and is_always_equal
+ // are false for small_allocator; see std::vector documentation for
+ // details). We, however, assume that the noexcept expression we use here
+ // is strict enough for all "sane" std::vector implementations since
+ // small_allocator never throws directly.
+ //
small_vector (small_vector&& v)
+ noexcept (std::is_nothrow_move_constructible<T>::value)
: base_type (allocator_type (this))
{
if (v.size () <= N)
@@ -132,8 +140,14 @@ LIBBUTL_MODEXPORT namespace butl
v.clear ();
}
+ // Note that when size() <= N and v.size() > N, then allocators of this
+ // and other containers do not evaluate as equal. Thus, the memory for the
+ // new elements is allocated on the heap and so std::bad_alloc can be
+ // thrown. @@ TODO: maybe we could re-implement this case in terms of
+ // swap()?
+ //
small_vector&
- operator= (small_vector&& v)
+ operator= (small_vector&& v) noexcept (false)
{
// VC's implementation of operator=(&&) (both 14 and 15) frees the
// memory and then reallocated with capacity equal to v.size(). This is
diff --git a/libbutl/standard-version.cxx b/libbutl/standard-version.cxx
index 863cb29..36f4830 100644
--- a/libbutl/standard-version.cxx
+++ b/libbutl/standard-version.cxx
@@ -1,41 +1,14 @@
// file : libbutl/standard-version.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/standard-version.mxx>
-#endif
+#include <libbutl/standard-version.hxx>
#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <cstdint>
-#include <cstddef>
-#include <ostream>
-
#include <cstdlib> // strtoull()
#include <utility> // move()
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.standard_version;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.optional;
-#endif
-
-import butl.utility;
-#else
-#include <libbutl/utility.mxx> // alnum()
-#endif
+
+#include <libbutl/utility.hxx> // alnum()
using namespace std;
diff --git a/libbutl/standard-version.mxx b/libbutl/standard-version.hxx
index b86e3a9..e973352 100644
--- a/libbutl/standard-version.mxx
+++ b/libbutl/standard-version.hxx
@@ -1,31 +1,14 @@
-// file : libbutl/standard-version.mxx -*- C++ -*-
+// file : libbutl/standard-version.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-
-// C includes.
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <cstdint> // uint*_t
#include <cstddef> // size_t
#include <ostream>
-#endif
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.standard_version;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.optional;
-#else
-#include <libbutl/optional.mxx>
-#endif
+#include <libbutl/optional.hxx>
#include <libbutl/export.hxx>
@@ -39,7 +22,7 @@ import butl.optional;
# undef minor
#endif
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// The build2 "standard version" (normal, earliest, and stub):
//
@@ -221,7 +204,7 @@ LIBBUTL_MODEXPORT namespace butl
// Create empty version.
//
- standard_version () {} // = default; @@ MOD VC
+ standard_version () = default;
};
// Try to parse a string as a standard version returning nullopt if invalid.
diff --git a/libbutl/string-parser.cxx b/libbutl/string-parser.cxx
index 5d5ec47..af5c1b3 100644
--- a/libbutl/string-parser.cxx
+++ b/libbutl/string-parser.cxx
@@ -1,33 +1,7 @@
// file : libbutl/string-parser.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/string-parser.mxx>
-#endif
-
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <vector>
-#include <cstddef>
-#include <utility> // move()
-#include <stdexcept>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.string_parser;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
-
-#endif
+#include <libbutl/string-parser.hxx>
using namespace std;
@@ -40,7 +14,7 @@ namespace butl
inline static bool
space (char c) noexcept
{
- return c == ' ' || c == '\t';
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
vector<pair<string, size_t>>
diff --git a/libbutl/string-parser.mxx b/libbutl/string-parser.hxx
index 4ff1590..9fc20c0 100644
--- a/libbutl/string-parser.mxx
+++ b/libbutl/string-parser.hxx
@@ -1,32 +1,17 @@
-// file : libbutl/string-parser.mxx -*- C++ -*-
+// file : libbutl/string-parser.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <cstddef> // size_t
#include <utility> // pair
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.string_parser;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
namespace string_parser
{
diff --git a/libbutl/string-table.mxx b/libbutl/string-table.hxx
index 78c6cd6..010fb01 100644
--- a/libbutl/string-table.mxx
+++ b/libbutl/string-table.hxx
@@ -1,36 +1,18 @@
-// file : libbutl/string-table.mxx -*- C++ -*-
+// file : libbutl/string-table.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
+#include <cassert>
#include <unordered_map>
-#include <limits> // numeric_limits
-#include <cstddef> // size_t
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.string_table;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.multi_index;
-#else
-#include <libbutl/multi-index.mxx>
-#endif
+#include <libbutl/multi-index.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// A pool of strings and, optionally, other accompanying data in which each
// entry is assigned an individual index (or id) of type I (e.g., uint8_t,
diff --git a/libbutl/string-table.txx b/libbutl/string-table.txx
index 4db0a6b..8416b48 100644
--- a/libbutl/string-table.txx
+++ b/libbutl/string-table.txx
@@ -1,6 +1,9 @@
// file : libbutl/string-table.txx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
+#include <limits> // numeric_limits
+#include <cstddef> // size_t
+
namespace butl
{
template <typename I, typename D>
diff --git a/libbutl/tab-parser.cxx b/libbutl/tab-parser.cxx
index cca2792..d7e5a14 100644
--- a/libbutl/tab-parser.cxx
+++ b/libbutl/tab-parser.cxx
@@ -1,39 +1,12 @@
// file : libbutl/tab-parser.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/tab-parser.mxx>
-#endif
-
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <vector>
-#include <cstdint>
-#include <stdexcept>
+#include <libbutl/tab-parser.hxx>
#include <istream>
#include <sstream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.tab_parser;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
-import butl.string_parser;
-#else
-#include <libbutl/string-parser.mxx>
-#endif
+#include <libbutl/string-parser.hxx>
using namespace std;
diff --git a/libbutl/tab-parser.mxx b/libbutl/tab-parser.hxx
index a7f7e01..2dc612b 100644
--- a/libbutl/tab-parser.mxx
+++ b/libbutl/tab-parser.hxx
@@ -1,33 +1,17 @@
-// file : libbutl/tab-parser.mxx -*- C++ -*-
+// file : libbutl/tab-parser.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <iosfwd>
#include <string>
#include <vector>
#include <cstdint> // uint64_t
#include <stdexcept> // runtime_error
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.tab_parser;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
class LIBBUTL_SYMEXPORT tab_parsing: public std::runtime_error
{
diff --git a/libbutl/target-triplet.cxx b/libbutl/target-triplet.cxx
index 611b758..e28f119 100644
--- a/libbutl/target-triplet.cxx
+++ b/libbutl/target-triplet.cxx
@@ -1,33 +1,9 @@
// file : libbutl/target-triplet.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/target-triplet.mxx>
-#endif
-
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <ostream>
+#include <libbutl/target-triplet.hxx>
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.target_triplet;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
-
-#endif
using namespace std;
@@ -112,6 +88,13 @@ namespace butl
if (system.front () == '-' || system.back () == '-')
bad ("invalid os/kernel/abi");
+ // Canonicalize SYSTEM.
+ //
+ if (system == "linux")
+ system = "linux-gnu"; // Per config.sub.
+ else if (system == "windows-gnu" && vendor == "w64") // Clang's innovation.
+ system = "mingw32";
+
// Extract VERSION for some recognized systems.
//
string::size_type v (0);
diff --git a/libbutl/target-triplet.mxx b/libbutl/target-triplet.hxx
index 3861809..bfb2c00 100644
--- a/libbutl/target-triplet.mxx
+++ b/libbutl/target-triplet.hxx
@@ -1,30 +1,14 @@
-// file : libbutl/target-triplet.mxx -*- C++ -*-
+// file : libbutl/target-triplet.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <ostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.target_triplet;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// This is the ubiquitous 'target triplet' that loosely has the CPU-VENDOR-OS
// form which, these days, quite often takes the CPU-VENDOR-OS-ABI form. Plus
@@ -91,8 +75,10 @@ LIBBUTL_MODEXPORT namespace butl
// arm-softfloat-linux-gnu arm softfloat linux-gnu
// i686-pc-mingw32 i686 mingw32
// i686-w64-mingw32 i686 w64 mingw32
+ // i686-w64-windows-gnu i686 w64 mingw32
// i686-lfs-linux-gnu i686 lfs linux-gnu
// x86_64-unknown-linux-gnu x86_64 linux-gnu
+ // x86_64-redhat-linux x86_64 redhat linux-gnu
// x86_64-linux-gnux32 x86_64 linux-gnux32
// x86_64-microsoft-win32-msvc14.0 x86_64 microsoft win32-msvc 14.0
// x86_64-pc-windows-msvc x86_64 windows-msvc
@@ -115,6 +101,8 @@ LIBBUTL_MODEXPORT namespace butl
// windows *-*-win32-* | *-*-windows-* | *-*-mingw32
// ios *-apple-ios*
//
+ // NOTE: see also os_release if adding anything new here.
+ //
// References:
//
// 1. The libtool repository contains the PLATFORM file that lists many known
@@ -164,7 +152,7 @@ LIBBUTL_MODEXPORT namespace butl
explicit
target_triplet (const std::string&);
- target_triplet () {} // = default; @@ MOD VC
+ target_triplet () = default;
};
inline bool
diff --git a/libbutl/timestamp.cxx b/libbutl/timestamp.cxx
index 589c29c..260fbef 100644
--- a/libbutl/timestamp.cxx
+++ b/libbutl/timestamp.cxx
@@ -1,9 +1,7 @@
// file : libbutl/timestamp.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/timestamp.mxx>
-#endif
+#include <libbutl/timestamp.hxx>
#include <time.h> // localtime_{r,s}(), gmtime_{r,s}(), strptime(), timegm()
#include <errno.h> // EINVAL
@@ -25,17 +23,13 @@
#ifdef __GLIBCXX__
extern "C"
{
-#include "strptime.c"
+# include "strptime.c"
}
#else
-#include <locale.h> // LC_ALL
+# include <locale.h> // LC_ALL
#endif
#endif
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <chrono>
-
#include <ctime> // tm, time_t, mktime(), strftime()[libstdc++]
#include <cstdlib> // strtoull()
#include <sstream> // ostringstream, stringstream[VC]
@@ -49,31 +43,14 @@ extern "C"
//
#ifdef _WIN32
#ifndef __GLIBCXX__
-#include <ios>
-#include <locale>
-#include <clocale>
-#include <iomanip>
-#endif
-#endif
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.timestamp;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
+# include <ios>
+# include <locale>
+# include <clocale>
+# include <iomanip>
#endif
#endif
-import butl.utility;
-#else
-#include <libbutl/utility.mxx> // throw_generic_error()
-#endif
+#include <libbutl/utility.hxx> // throw_generic_error()
using namespace std;
diff --git a/libbutl/timestamp.mxx b/libbutl/timestamp.hxx
index 141e13d..2714a0d 100644
--- a/libbutl/timestamp.mxx
+++ b/libbutl/timestamp.hxx
@@ -1,34 +1,15 @@
-// file : libbutl/timestamp.mxx -*- C++ -*-
+// file : libbutl/timestamp.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <iosfwd>
#include <string>
#include <chrono>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.timestamp;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
-
-//@@ MOD TODO: should't we re-export chrono (for somparison operator, etc)?
-// or ADL should kick in?
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// On all three main platforms that we target (GNU/Linux, Windows (both
// VC++ and GCC/MinGW64), and MacOS X) with recent C++ runtimes,
@@ -61,21 +42,12 @@ LIBBUTL_MODEXPORT namespace butl
// unreal and all of them are less than any non-special value (strictly
// speaking unreal is no greater (older) than any real value).
//
-#if defined(__cpp_modules_ts) && defined(__clang__) //@@ MOD Clang duplicate sym.
- inline const timestamp::rep timestamp_unknown_rep = -1;
- inline const timestamp timestamp_unknown = timestamp (duration (-1));
- inline const timestamp::rep timestamp_nonexistent_rep = 0;
- inline const timestamp timestamp_nonexistent = timestamp (duration (0));
- inline const timestamp::rep timestamp_unreal_rep = 1;
- inline const timestamp timestamp_unreal = timestamp (duration (1));
-#else
const timestamp::rep timestamp_unknown_rep = -1;
const timestamp timestamp_unknown = timestamp (duration (-1));
const timestamp::rep timestamp_nonexistent_rep = 0;
const timestamp timestamp_nonexistent = timestamp (duration (0));
const timestamp::rep timestamp_unreal_rep = 1;
const timestamp timestamp_unreal = timestamp (duration (1));
-#endif
// Print human-readable representation of the timestamp.
//
diff --git a/libbutl/unicode.cxx b/libbutl/unicode.cxx
index 4219846..294bb3f 100644
--- a/libbutl/unicode.cxx
+++ b/libbutl/unicode.cxx
@@ -1,32 +1,11 @@
// file : libbutl/unicode.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/unicode.mxx>
-#endif
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <ostream>
-#include <cstdint>
+#include <libbutl/unicode.hxx>
#include <cstddef> // size_t
#include <utility> // pair
#include <algorithm> // lower_bound()
-#endif
-
-#ifdef __cpp_modules_ts
-module butl.unicode;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
-
-#endif
using namespace std;
diff --git a/libbutl/unicode.mxx b/libbutl/unicode.hxx
index b846476..8d99d0e 100644
--- a/libbutl/unicode.mxx
+++ b/libbutl/unicode.hxx
@@ -1,31 +1,15 @@
-// file : libbutl/unicode.mxx -*- C++ -*-
+// file : libbutl/unicode.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <ostream>
#include <cstdint> // uint16_t
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.unicode;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Note that the Unicode Standard requires the surrogates ([D800 DFFF]) to
// only be used in the context of the UTF-16 character encoding form. Thus,
diff --git a/libbutl/url.mxx b/libbutl/url.hxx
index 713bc3e..5721cfd 100644
--- a/libbutl/url.mxx
+++ b/libbutl/url.hxx
@@ -1,50 +1,23 @@
-// file : libbutl/url.mxx -*- C++ -*-
+// file : libbutl/url.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
+#include <cassert>
+#include <cstddef> // size_t
#include <cstdint> // uint*_t
#include <utility> // move()
#include <ostream>
#include <iterator> // back_inserter
-#include <cstddef> // size_t
-#include <stdexcept> // invalid_argument
-#include <algorithm> // find(), find_if()
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.url;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.utility;
-import butl.optional;
-
-import butl.small_vector;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/optional.mxx>
-
-#include <libbutl/small-vector.mxx>
-#endif
+#include <libbutl/path.hxx>
+#include <libbutl/utility.hxx>
+#include <libbutl/optional.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// RFC3986 Uniform Resource Locator (URL).
//
diff --git a/libbutl/url.ixx b/libbutl/url.ixx
index b823ee7..19d54c7 100644
--- a/libbutl/url.ixx
+++ b/libbutl/url.ixx
@@ -1,7 +1,7 @@
// file : libbutl/url.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+namespace butl
{
// url_traits
//
diff --git a/libbutl/url.txx b/libbutl/url.txx
index 0951e80..b2caa37 100644
--- a/libbutl/url.txx
+++ b/libbutl/url.txx
@@ -1,7 +1,12 @@
// file : libbutl/url.txx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason.
+#include <stdexcept> // invalid_argument
+#include <algorithm> // find(), find_if()
+
+#include <libbutl/small-vector.hxx>
+
+namespace butl
{
// Convenience functions.
//
diff --git a/libbutl/utf8.mxx b/libbutl/utf8.hxx
index 15e8ded..697f77a 100644
--- a/libbutl/utf8.mxx
+++ b/libbutl/utf8.hxx
@@ -1,33 +1,17 @@
-// file : libbutl/utf8.mxx -*- C++ -*-
+// file : libbutl/utf8.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <cstdint> // uint8_t
#include <utility> // pair
-#endif
-
-// Other includes.
-#ifdef __cpp_modules_ts
-export module butl.utf8;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.unicode;
-#else
-#include <libbutl/unicode.mxx>
-#endif
+#include <libbutl/unicode.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Here and below we will refer to bytes that encode a singe Unicode
// codepoint as "UTF-8 byte sequence" ("UTF-8 sequence" or "byte sequence"
diff --git a/libbutl/utf8.ixx b/libbutl/utf8.ixx
index 3d2e092..10624f8 100644
--- a/libbutl/utf8.ixx
+++ b/libbutl/utf8.ixx
@@ -116,7 +116,7 @@ namespace butl
{
if (b < 0xFE)
{
- *what = b < 0xFC ? "5" : "6";
+ *what = b < 0xFC ? '5' : '6';
*what += "-byte length UTF-8 sequence";
}
else
diff --git a/libbutl/utility.cxx b/libbutl/utility.cxx
index a891fc2..b03a8f8 100644
--- a/libbutl/utility.cxx
+++ b/libbutl/utility.cxx
@@ -1,9 +1,7 @@
// file : libbutl/utility.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/utility.mxx>
-#endif
+#include <libbutl/utility.hxx>
#ifdef _WIN32
#include <libbutl/win32-utility.hxx>
@@ -11,35 +9,15 @@
#include <stdlib.h> // getenv(), setenv(), unsetenv(), _putenv()
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <cstddef>
-#include <utility>
-
#include <cstring> // strncmp(), strlen()
#include <ostream>
#include <type_traits> // enable_if, is_base_of
#include <system_error>
-#endif
#include <libbutl/ft/lang.hxx>
#include <libbutl/ft/exception.hxx>
-#ifdef __cpp_modules_ts
-module butl.utility;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
-
-import butl.utf8;
-#else
-#include <libbutl/utf8.mxx>
-#endif
+#include <libbutl/utf8.hxx>
namespace butl
{
@@ -193,13 +171,42 @@ namespace butl
for (; i != n && ws (l[i]); ++i) ;
for (; n != i && ws (l[n - 1]); --n) ;
- if (i != 0)
+ if (n != l.size ()) l.resize (n);
+ if (i != 0) l.erase (0, i);
+
+ return l;
+ }
+
+ string&
+ trim_left (string& l)
+ {
+ auto ws = [] (char c )
{
- string s (l, i, n - i);
- l.swap (s);
- }
- else if (n != l.size ())
- l.resize (n);
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+ };
+
+ size_t i (0), n (l.size ());
+
+ for (; i != n && ws (l[i]); ++i) ;
+
+ if (i != 0) l.erase (0, i);
+
+ return l;
+ }
+
+ string&
+ trim_right (string& l)
+ {
+ auto ws = [] (char c )
+ {
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+ };
+
+ size_t i (0), n (l.size ());
+
+ for (; n != i && ws (l[n - 1]); --n) ;
+
+ if (n != l.size ()) l.resize (n);
return l;
}
diff --git a/libbutl/utility.mxx b/libbutl/utility.hxx
index bd24ffd..779a0aa 100644
--- a/libbutl/utility.mxx
+++ b/libbutl/utility.hxx
@@ -1,9 +1,7 @@
-// file : libbutl/utility.mxx -*- C++ -*-
+// file : libbutl/utility.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
#ifndef _WIN32
# include <strings.h> // strcasecmp(), strncasecmp()
@@ -11,7 +9,6 @@
# include <string.h> // _stricmp(), _strnicmp()
#endif
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iosfwd> // ostream
#include <istream>
@@ -20,29 +17,17 @@
#include <cstring> // strcmp(), strlen()
#include <exception> // exception, uncaught_exception[s]()
//#include <functional> // hash
-#endif
#include <libbutl/ft/lang.hxx> // thread_local
#include <libbutl/ft/exception.hxx> // uncaught_exceptions
-#ifdef __cpp_modules_ts
-export module butl.utility;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.utf8;
-import butl.unicode;
-import butl.optional;
-#else
-#include <libbutl/utf8.mxx>
-#include <libbutl/unicode.mxx>
-#include <libbutl/optional.mxx>
-#endif
+#include <libbutl/utf8.hxx>
+#include <libbutl/unicode.hxx>
+#include <libbutl/optional.hxx>
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// Throw std::system_error with generic_category or system_category,
// respectively.
@@ -147,11 +132,13 @@ LIBBUTL_MODEXPORT namespace butl
bool digit (char);
bool alnum (char);
bool xdigit (char);
+ bool wspace (char);
bool alpha (wchar_t);
bool digit (wchar_t);
bool alnum (wchar_t);
bool xdigit (wchar_t);
+ bool wspace (wchar_t);
// Basic string utilities.
//
@@ -161,13 +148,31 @@ LIBBUTL_MODEXPORT namespace butl
LIBBUTL_SYMEXPORT std::string&
trim (std::string&);
+ LIBBUTL_SYMEXPORT std::string&
+ trim_left (std::string&);
+
+ LIBBUTL_SYMEXPORT std::string&
+ trim_right (std::string&);
+
inline std::string
trim (std::string&& s)
{
return move (trim (s));
}
- // Find the beginning and end poistions of the next word. Return the size
+ inline std::string
+ trim_left (std::string&& s)
+ {
+ return move (trim_left (s));
+ }
+
+ inline std::string
+ trim_right (std::string&& s)
+ {
+ return move (trim_right (s));
+ }
+
+ // Find the beginning and end positions of the next word. Return the size
// of the word or 0 and set b = e = n if there are no more words. For
// example:
//
@@ -185,6 +190,24 @@ LIBBUTL_MODEXPORT namespace butl
//
// The second version examines up to the n'th character in the string.
//
+ // The third version, instead of skipping consecutive delimiters, treats
+ // them as separating empty words. The additional m variable contains an
+ // unspecified internal state and should be initialized to 0. Note that in
+ // this case you should use the (b == n) condition to detect the end. Note
+ // also that a leading delimiter is considered as separating an empty word
+ // from the rest and the trailing delimiter is considered as separating the
+ // rest from an empty word. For example, this is how to parse lines while
+ // observing blanks:
+ //
+ // for (size_t b (0), e (0), m (0), n (s.size ());
+ // next_word (s, n, b, e, m, '\n', '\r'), b != n; )
+ // {
+ // string l (s, b, e - b);
+ // }
+ //
+ // For string "\na\n" this code will observe the {"", "a", ""} words. And
+ // for just "\n" it will observe the {"", ""} words.
+ //
std::size_t
next_word (const std::string&, std::size_t& b, std::size_t& e,
char d1 = ' ', char d2 = '\0');
@@ -193,6 +216,11 @@ LIBBUTL_MODEXPORT namespace butl
next_word (const std::string&, std::size_t n, std::size_t& b, std::size_t& e,
char d1 = ' ', char d2 = '\0');
+ std::size_t
+ next_word (const std::string&, std::size_t n,
+ std::size_t& b, std::size_t& e, std::size_t& m,
+ char d1 = ' ', char d2 = '\0');
+
// Sanitize a string to only contain characters valid in an identifier
// (ASCII alphanumeric plus `_`) replacing all others with `_`.
//
@@ -309,8 +337,8 @@ LIBBUTL_MODEXPORT namespace butl
// Move-to-empty-only type.
//
- auto_thread_env (auto_thread_env&&);
- auto_thread_env& operator= (auto_thread_env&&);
+ auto_thread_env (auto_thread_env&&) noexcept;
+ auto_thread_env& operator= (auto_thread_env&&) noexcept;
auto_thread_env (const auto_thread_env&) = delete;
auto_thread_env& operator= (const auto_thread_env&) = delete;
@@ -540,7 +568,7 @@ LIBBUTL_MODEXPORT namespace butl
#endif
}
-LIBBUTL_MODEXPORT namespace std
+namespace std
{
// Sanitize the exception description before printing. This includes:
//
diff --git a/libbutl/utility.ixx b/libbutl/utility.ixx
index 6501bf7..fda1ce5 100644
--- a/libbutl/utility.ixx
+++ b/libbutl/utility.ixx
@@ -1,12 +1,10 @@
// file : libbutl/utility.ixx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_lib_modules_ts
#include <cctype> // toupper(), tolower(), is*()
#include <cwctype> // isw*()
#include <algorithm> // for_each()
#include <stdexcept> // invalid_argument
-#endif
namespace butl
{
@@ -145,6 +143,12 @@ namespace butl
}
inline bool
+ wspace (char c)
+ {
+ return std::isspace (c);
+ }
+
+ inline bool
alpha (wchar_t c)
{
return std::iswalpha (c);
@@ -168,6 +172,12 @@ namespace butl
return std::iswxdigit (c);
}
+ inline bool
+ wspace (wchar_t c)
+ {
+ return std::iswspace (c);
+ }
+
inline std::size_t
next_word (const std::string& s, std::size_t& b, std::size_t& e,
char d1, char d2)
@@ -200,6 +210,66 @@ namespace butl
return e - b;
}
+ inline std::size_t
+ next_word (const std::string& s,
+ std::size_t n, std::size_t& b, std::size_t& e, std::size_t& m,
+ char d1, char d2)
+ {
+ // An empty word will necessarily be represented as b and e being the
+ // position of a delimiter. Consider these corner cases (in all three we
+ // should produce two words):
+ //
+ // \n
+ // a\n
+ // \na
+ //
+ // It feels sensible to represent an empty word as the position of the
+ // trailing delimiter except if it is the last character (the first two
+ // cases). Thus the additional m state, which, if 0 or 1 indicates the
+ // number of delimiters to skip before parsing the next word and 2 if
+ // this is a trailing delimiter for which we need to fake an empty word
+ // with the leading delimiter.
+
+ if (b != e)
+ b = e;
+
+ if (m > 1)
+ {
+ --m;
+ return 0;
+ }
+
+ // Skip the leading delimiter, if any.
+ //
+ b += m;
+
+ if (b == n)
+ {
+ e = n;
+ return 0;
+ }
+
+ // Find first trailing delimiter.
+ //
+ m = 0;
+ for (e = b; e != n; ++e)
+ {
+ if (s[e] == d1 || s[e] == d2)
+ {
+ m = 1;
+
+ // Handle the special delimiter as the last character case.
+ //
+ if (e + 1 == n)
+ ++m;
+
+ break;
+ }
+ }
+
+ return e - b;
+ }
+
inline std::string&
sanitize_identifier (std::string& s)
{
@@ -361,14 +431,14 @@ namespace butl
}
inline auto_thread_env::
- auto_thread_env (auto_thread_env&& x)
+ auto_thread_env (auto_thread_env&& x) noexcept
: prev_env (std::move (x.prev_env))
{
x.prev_env = nullopt;
}
inline auto_thread_env& auto_thread_env::
- operator= (auto_thread_env&& x)
+ operator= (auto_thread_env&& x) noexcept
{
if (this != &x)
{
diff --git a/libbutl/uuid-linux.cxx b/libbutl/uuid-linux.cxx
index 7689088..82af2e9 100644
--- a/libbutl/uuid-linux.cxx
+++ b/libbutl/uuid-linux.cxx
@@ -13,7 +13,7 @@
#include <utility> // move()
#include <system_error>
-#include <libbutl/utility.mxx> // function_cast()
+#include <libbutl/utility.hxx> // function_cast()
using namespace std;
diff --git a/libbutl/uuid-openbsd.cxx b/libbutl/uuid-openbsd.cxx
new file mode 100644
index 0000000..b64436b
--- /dev/null
+++ b/libbutl/uuid-openbsd.cxx
@@ -0,0 +1,80 @@
+// file : libbutl/uuid-openbsd.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_BOOTSTRAP
+
+#include <libbutl/uuid.hxx>
+
+#include <uuid.h>
+
+#include <errno.h>
+
+#include <cassert>
+#include <cstring> // memcpy()
+#include <system_error>
+
+using namespace std;
+
+namespace butl
+{
+ void
+ uuid_throw_weak (); // uuid.cxx
+
+ uuid uuid_system_generator::
+ generate (bool strong)
+ {
+ // The OpenBSD uuid_*() (<uuid.h>, uuid_compare(3)) API generates version
+ // 4 UUIDs (i.e. randomly generated) at least from version 6.4. For now we
+ // will assume that only random ones are strong.
+ //
+ // Here we assume uuid_t has the same definition as in FreeBSD/NetBSD (it
+ // is defined in <sys/uuid.h>).
+ //
+ uuid_t d;
+ uint32_t s;
+ uuid_create (&d, &s);
+
+ // None of the uuid_s_* errors seem plausible for this function so let's
+ // return the generic "not supported" error code.
+ //
+ if (s != uuid_s_ok)
+ throw system_error (ENOSYS, system_category ());
+
+ uuid r;
+
+ // This is effectively just memcpy() but let's reference the member names
+ // in case anything changes on either side.
+ //
+ r.time_low = d.time_low;
+ r.time_mid = d.time_mid;
+ r.time_hiv = d.time_hi_and_version;
+ r.clock_seq_hir = d.clock_seq_hi_and_reserved;
+ r.clock_seq_low = d.clock_seq_low;
+ memcpy (r.node, d.node, 6);
+
+ assert (r.variant () == uuid_variant::dce); // Sanity check.
+
+ if (strong)
+ {
+ switch (r.version ())
+ {
+ case uuid_version::random: break;
+ default: uuid_throw_weak ();
+ }
+ }
+
+ return r;
+ }
+
+ void uuid_system_generator::
+ initialize ()
+ {
+ }
+
+ void uuid_system_generator::
+ terminate ()
+ {
+ }
+}
+
+#endif // BUILD2_BOOTSTRAP
diff --git a/libbutl/uuid.cxx b/libbutl/uuid.cxx
index 377afb7..2132808 100644
--- a/libbutl/uuid.cxx
+++ b/libbutl/uuid.cxx
@@ -5,7 +5,7 @@
#include <errno.h> // ENOTSUP
-#include <cstdio> // sprintf() scanf()
+#include <cstdio> // snprintf() sscanf()
#include <cstring> // strlen()
#include <stdexcept>
#include <system_error>
@@ -19,16 +19,17 @@ namespace butl
{
array<char, 37> r;
- sprintf (r.data (),
- (upper
- ? "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X"
- : "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x"),
- time_low,
- time_mid,
- time_hiv,
- clock_seq_hir,
- clock_seq_low,
- node[0], node[1], node[2], node[3], node[4], node[5]);
+ snprintf (r.data (),
+ 37,
+ (upper
+ ? "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X"
+ : "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x"),
+ time_low,
+ time_mid,
+ time_hiv,
+ clock_seq_hir,
+ clock_seq_low,
+ node[0], node[1], node[2], node[3], node[4], node[5]);
return r;
}
diff --git a/libbutl/uuid.hxx b/libbutl/uuid.hxx
index 2361640..862f02d 100644
--- a/libbutl/uuid.hxx
+++ b/libbutl/uuid.hxx
@@ -48,12 +48,12 @@ namespace butl
{
// Normally not accessed directly (see RFC4122 Section 4.1.2).
//
- std::uint32_t time_low = 0;
- std::uint16_t time_mid = 0;
- std::uint16_t time_hiv = 0; // hi_and_version
- std::uint8_t clock_seq_hir = 0; // hi_and_reserved
- std::uint8_t clock_seq_low = 0;
- std::uint8_t node[6] = {0, 0, 0, 0, 0, 0};
+ std::uint32_t time_low = 0;
+ std::uint16_t time_mid = 0;
+ std::uint16_t time_hiv = 0; // hi_and_version
+ std::uint8_t clock_seq_hir = 0; // hi_and_reserved
+ std::uint8_t clock_seq_low = 0;
+ std::uint8_t node[6] = {0, 0, 0, 0, 0, 0};
// System UUID generator. See the uuid_generator interface for details.
//
@@ -158,10 +158,10 @@ namespace butl
void
swap (uuid&);
- uuid (uuid&&);
+ uuid (uuid&&) noexcept;
uuid (const uuid&) = default;
- uuid& operator= (uuid&&);
+ uuid& operator= (uuid&&) noexcept;
uuid& operator= (const uuid&) = default;
};
@@ -183,7 +183,7 @@ namespace butl
~uuid_generator () = default;
// Generate a UUID. If strong is true (default), generate a strongly-
- // unique UUID. Throw std::runtime_error to report errors, including if
+ // unique UUID. Throw std::system_error to report errors, including if
// strong uniqueness cannot be guaranteed.
//
// A weak UUID is not guaranteed to be unique, neither universialy nor
@@ -207,7 +207,7 @@ namespace butl
// Optional explicit initialization and termination. Note that it is not
// thread-safe and must only be performed once (normally from main())
// before/after any calls to generate(), respectively. Both functions may
- // throw std::runtime_error to report errors.
+ // throw std::system_error to report errors.
//
static void
initialize ();
diff --git a/libbutl/uuid.ixx b/libbutl/uuid.ixx
index 6744af7..6115be1 100644
--- a/libbutl/uuid.ixx
+++ b/libbutl/uuid.ixx
@@ -39,14 +39,14 @@ namespace butl
}
inline uuid::
- uuid (uuid&& u)
+ uuid (uuid&& u) noexcept
: uuid () // nil
{
swap (u);
}
inline uuid& uuid::
- operator= (uuid&& u)
+ operator= (uuid&& u) noexcept
{
if (this != &u)
{
diff --git a/libbutl/vector-view.mxx b/libbutl/vector-view.hxx
index 7924371..16ab08e 100644
--- a/libbutl/vector-view.mxx
+++ b/libbutl/vector-view.hxx
@@ -1,32 +1,17 @@
-// file : libbutl/vector-view.mxx -*- C++ -*-
+// file : libbutl/vector-view.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
#pragma once
-#endif
-// C includes.
-
-#ifndef __cpp_lib_modules_ts
#include <vector>
#include <cstddef> // size_t, ptrdiff_t
#include <utility> // swap()
#include <iterator> // reverse_iterator
#include <stdexcept> // out_of_range
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-export module butl.vector_view;
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-#endif
#include <libbutl/export.hxx>
-LIBBUTL_MODEXPORT namespace butl
+namespace butl
{
// In our version a const view allows the modification of the elements
// unless T is made const (the same semantics as in smart pointers).
diff --git a/libbutl/win32-utility.cxx b/libbutl/win32-utility.cxx
index 3b44d60..c69842b 100644
--- a/libbutl/win32-utility.cxx
+++ b/libbutl/win32-utility.cxx
@@ -8,16 +8,9 @@
//
#ifdef _WIN32
-#ifndef __cpp_lib_modules_ts
-#include <string>
#include <memory> // unique_ptr
-#include <libbutl/utility.mxx> // throw_system_error()
-#else
-import std.core;
-
-import butl.utility;
-#endif
+#include <libbutl/utility.hxx> // throw_system_error()
using namespace std;
diff --git a/libbutl/win32-utility.hxx b/libbutl/win32-utility.hxx
index b71eb1a..9bed647 100644
--- a/libbutl/win32-utility.hxx
+++ b/libbutl/win32-utility.hxx
@@ -31,11 +31,7 @@
# endif
#endif
-#ifndef __cpp_lib_modules_ts
#include <string>
-#else
-import std.core;
-#endif
#include <libbutl/export.hxx>
diff --git a/manifest b/manifest
index ae15b8c..7ac415a 100644
--- a/manifest
+++ b/manifest
@@ -1,9 +1,9 @@
: 1
name: libbutl
-version: 0.14.0-a.0.z
+version: 0.17.0-a.0.z
project: build2
summary: build2 utility library
-license: MIT AND BSD-3-Clause AND BSD-2-Clause ; MIT except for files from the FreeBSD and LZ4 projects.
+license: MIT AND BSD-3-Clause AND BSD-2-Clause ; MIT except for files from the FreeBSD, LZ4, and mingw-std-threads projects.
topics: build toolchain
description-file: README
changes-file: NEWS
@@ -12,7 +12,7 @@ doc-url: https://build2.org/doc.xhtml
src-url: https://git.build2.org/cgit/libbutl/tree/
email: users@build2.org
build-warning-email: builds@build2.org
-builds: host
+builds: all : &host
requires: c++14
-depends: * build2 >= 0.13.0
-depends: * bpkg >= 0.13.0
+depends: * build2 >= 0.16.0-
+depends: * bpkg >= 0.16.0-
diff --git a/tests/b-info/driver.cxx b/tests/b-info/driver.cxx
index c5a2013..5691221 100644
--- a/tests/b-info/driver.cxx
+++ b/tests/b-info/driver.cxx
@@ -1,28 +1,15 @@
// file : tests/b-info/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.b;
-import butl.path;
-import butl.utility; // operator<<(ostream,exception)
-#else
-#include <libbutl/b.mxx>
-#include <libbutl/path.mxx>
-#include <libbutl/utility.mxx>
-#endif
+
+#include <libbutl/b.hxx>
+#include <libbutl/path.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream,exception)
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -62,13 +49,14 @@ try
cout.exceptions (ios::failbit | ios::badbit);
- b_project_info pi (b_info (project,
- true /* ext_mods */,
- 1 /* verb */,
- {} /* cmd_callback */,
- b,
- {} /* search_fallback */,
- {"--no-default-options"}));
+ b_project_info pi (
+ b_info (project,
+ b_info_flags::ext_mods | b_info_flags::subprojects,
+ 1 /* verb */,
+ {} /* cmd_callback */,
+ b,
+ {} /* search_fallback */,
+ {"--no-default-options"}));
cout << "project: " << pi.project << endl
<< "version: " << pi.version << endl
diff --git a/tests/backtrace/driver.cxx b/tests/backtrace/driver.cxx
index d998942..ecfa58e 100644
--- a/tests/backtrace/driver.cxx
+++ b/tests/backtrace/driver.cxx
@@ -5,31 +5,17 @@
# include <sys/resource.h> // setrlimit()
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
#include <exception> // set_terminate(), terminate_handler
#include <system_error>
-#else
-import std.io;
-#endif
-// Other includes.
+#include <libbutl/process.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/backtrace.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.process;
-import butl.fdstream;
-import butl.backtrace;
-#else
-#include <libbutl/process.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/backtrace.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/base64/driver.cxx b/tests/base64/driver.cxx
index c7906f5..32d5236 100644
--- a/tests/base64/driver.cxx
+++ b/tests/base64/driver.cxx
@@ -1,29 +1,20 @@
// file : tests/base64/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <sstream>
-#endif
-// Other includes.
+#include <libbutl/base64.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.base64;
-#else
-#include <libbutl/base64.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
+// Test base64 encoding and decoding.
+//
static bool
encode (const string& i, const string& o)
{
@@ -79,9 +70,44 @@ encode (const string& i, const string& o)
return r;
}
+// Test base64url encoding only (decoding not yet implemented).
+//
+static bool
+encode_url (const string& i, const string& o)
+{
+ istringstream is (i);
+ string s (base64url_encode (is));
+ bool r (s == o && is.eof ());
+
+ if (r)
+ {
+ is.seekg (0);
+
+ // VC15 seekg() doesn't clear eofbit.
+ //
+#if defined(_MSC_VER) && _MSC_VER < 1920
+ is.clear ();
+#endif
+
+ assert (!is.eof ());
+
+ ostringstream os;
+ base64url_encode (os, is);
+ r = os.str () == o && is.eof ();
+ }
+
+ if (r)
+ r = base64url_encode (vector<char> (i.begin (), i.end ())) == o;
+
+ return r;
+}
+
+
int
main ()
{
+ // base64
+ //
assert (encode ("", ""));
assert (encode ("B", "Qg=="));
assert (encode ("BX", "Qlg="));
@@ -91,6 +117,19 @@ main ()
assert (encode ("BXzS@#", "Qlh6U0Aj"));
assert (encode ("BXzS@#/", "Qlh6U0AjLw=="));
+ // base64url: no padding in output.
+ //
+ assert (encode_url ("", ""));
+ assert (encode_url ("B", "Qg"));
+ assert (encode_url ("BX", "Qlg"));
+ assert (encode_url ("BXz", "Qlh6"));
+ assert (encode_url ("BXzS", "Qlh6Uw"));
+ assert (encode_url ("BXzS@", "Qlh6U0A"));
+ assert (encode_url ("BXzS@#", "Qlh6U0Aj"));
+ assert (encode_url ("BXzS@#/", "Qlh6U0AjLw"));
+
+ // Multi-line input.
+ //
const char* s (
"class fdstream_base\n"
"{\n"
@@ -102,10 +141,29 @@ main ()
" fdbuf buf_;\n"
"};\n");
+ // base64
+ //
const char* r (
"Y2xhc3MgZmRzdHJlYW1fYmFzZQp7CnByb3RlY3RlZDoKICBmZHN0cmVhbV9iYXNlICgpID0gZGVm\n"
"YXVsdDsKICBmZHN0cmVhbV9iYXNlIChpbnQgZmQpOiBidWZfIChmZCkge30KCnByb3RlY3RlZDoK\n"
"ICBmZGJ1ZiBidWZfOwp9Owo=");
assert (encode (s, r));
+
+ // base64url: no newlines or padding in output.
+ //
+ r =
+"Y2xhc3MgZmRzdHJlYW1fYmFzZQp7CnByb3RlY3RlZDoKICBmZHN0cmVhbV9iYXNlICgpID0gZGVm"
+"YXVsdDsKICBmZHN0cmVhbV9iYXNlIChpbnQgZmQpOiBidWZfIChmZCkge30KCnByb3RlY3RlZDoK"
+"ICBmZGJ1ZiBidWZfOwp9Owo";
+
+ assert (encode_url (s, r));
+
+ // Test 63rd and 64th characters: `>` maps to `+` or `-`; `?` maps to `/` or
+ // `_`.
+ //
+ assert (encode (">>>>>>", "Pj4+Pj4+"));
+ assert (encode_url (">>>>>>", "Pj4-Pj4-"));
+ assert (encode ("??????", "Pz8/Pz8/"));
+ assert (encode_url ("??????", "Pz8_Pz8_"));
}
diff --git a/tests/build/root.build b/tests/build/root.build
index a1e935c..515e1c9 100644
--- a/tests/build/root.build
+++ b/tests/build/root.build
@@ -14,9 +14,16 @@ if ($cxx.target.system == 'win32-msvc')
if ($cxx.class == 'msvc')
cxx.coptions += /wd4251 /wd4275 /wd4800
elif ($cxx.id == 'gcc')
+{
cxx.coptions += -Wno-maybe-uninitialized -Wno-free-nonheap-object \
-Wno-stringop-overread
+ if ($cxx.version.major >= 13)
+ cxx.coptions += -Wno-dangling-reference
+}
+elif ($cxx.id.type == 'clang' && $cxx.version.major >= 15)
+ cxx.coptions += -Wno-unqualified-std-cast-call
+
# Every exe{} in this subproject is by default a test.
#
exe{*}: test = true
diff --git a/tests/builtin/driver.cxx b/tests/builtin/driver.cxx
index bab74aa..bdf3fa9 100644
--- a/tests/builtin/driver.cxx
+++ b/tests/builtin/driver.cxx
@@ -5,9 +5,6 @@
# include <libbutl/win32-utility.hxx>
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <chrono>
@@ -18,31 +15,26 @@
#ifndef _WIN32
# include <thread> // this_thread::sleep_for()
#endif
-#endif
-// Other includes.
+#include <libbutl/path.hxx>
+#include <libbutl/utility.hxx> // eof()
+#include <libbutl/builtin.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/timestamp.hxx> // to_stream(duration)
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.utility; // eof()
-import butl.builtin;
-import butl.optional;
-import butl.timestamp; // to_stream(duration)
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/builtin.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/timestamp.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
+// Disable arguments globbing that may be enabled by default for MinGW runtime
+// (see tests/wildcard/driver.cxx for details).
+//
+#ifdef __MINGW32__
+int _CRT_glob = 0;
+#endif
+
inline ostream&
operator<< (ostream& os, const path& p)
{
diff --git a/tests/builtin/find.testscript b/tests/builtin/find.testscript
new file mode 100644
index 0000000..b09822c
--- /dev/null
+++ b/tests/builtin/find.testscript
@@ -0,0 +1,276 @@
+# file : tests/builtin/find.testscript
+# license : MIT; see accompanying LICENSE file
+
+posix = ($cxx.target.class != 'windows')
+
+test.arguments = "find"
+
+: no-paths
+:
+$* 2>"find: missing start path" == 1
+
+: no-paths-primary
+:
+$* -name foo 2>"find: unknown option '-name'" == 1
+
+: unknown-primary
+:
+$* . -foo 2>"find: unknown primary '-foo'" == 1
+
+
+: no-primary-value
+:
+$* . -name 2>"find: missing value for primary '-name'" == 1
+
+: empty-primary-value
+:
+$* . -type '' 2>"find: empty value for primary '-type'" == 1
+
+: invalid-type-primary
+:
+$* . -type foo 2>"find: invalid value 'foo' for primary '-type'" == 1
+
+: invalid-mindepth-primary
+:
+$* . -mindepth 12a 2>"find: invalid value '12a' for primary '-mindepth'" == 1
+
+: path-not-exists
+:
+{
+ mkdir d;
+ $* x d >'d' 2>"find: 'x' doesn't exists" != 0
+}
+
+: path
+:
+{
+ : relative
+ :
+ {
+ : no-cwd
+ :
+ {
+ mkdir a;
+ touch a/b;
+
+ $* a >>/EOO
+ a
+ a/b
+ EOO
+ }
+
+ : absolute-cwd
+ :
+ : When cross-testing we cannot guarantee that host absolute paths are
+ : recognized by the target process.
+ :
+ if ($test.target == $build.host)
+ {
+ test.options += -d $~/a;
+ mkdir a;
+ touch a/b;
+
+ $* b >'b'
+ }
+
+ : relative-cwd
+ :
+ if ($test.target == $build.host)
+ {
+ test.options += -d a;
+ mkdir a;
+ touch a/b;
+
+ $* b >'b'
+ }
+ }
+
+ : non-normalized
+ :
+ {
+ mkdir a;
+ touch a/b;
+
+ # Note that the path specified on the command line is used unaltered.
+ #
+ s = ($posix ? '/' : '\');
+
+ $* ./a >>"EOO"
+ ./a
+ ./a$(s)b
+ EOO
+ }
+
+ : absolute
+ :
+ {
+ mkdir a;
+ touch a/b;
+
+ $* $~/a >>/"EOO"
+ $~/a
+ $~/a/b
+ EOO
+ }
+
+ : non-existent
+ :
+ {
+ touch a b;
+
+ $* a x b >>EOO 2>"find: 'x' doesn't exists" != 0
+ a
+ b
+ EOO
+ }
+
+ : non-directory
+ :
+ {
+ touch a b c;
+
+ $* a b/ c >>EOO 2>"find: 'b' is not a directory" != 0
+ a
+ c
+ EOO
+ }
+
+ : trailing-slash
+ :
+ {
+ mkdir -p a/b;
+
+ $* a >>/"EOO";
+ a
+ a/b
+ EOO
+
+ $* a/ >>"EOO"
+ a/
+ a/b
+ EOO
+ }
+}
+
+: name-primary
+:
+{
+ : basic
+ :
+ {
+ mkdir a;
+ touch a/ab a/ba;
+
+ $* . -name 'a*' >>/EOO;
+ ./a
+ ./a/ab
+ EOO
+
+ $* . -name 'b*' >>/EOO;
+ ./a/ba
+ EOO
+
+ $* a -name 'a*' >>/EOO
+ a
+ a/ab
+ EOO
+ }
+
+ : empty
+ :
+ {
+ touch a;
+
+ $* . -name ''
+ }
+}
+
+: type-primary
+:
+{
+ : regular
+ :
+ {
+ mkdir -p a/b;
+ touch a/b/c;
+
+ $* a -type f >>/EOO
+ a/b/c
+ EOO
+ }
+
+ : directory
+ :
+ {
+ mkdir -p a/b;
+ touch a/b/c;
+
+ $* a -type d >>/EOO
+ a
+ a/b
+ EOO
+ }
+
+ : symlink
+ :
+ if $posix
+ {
+ mkdir -p a/b;
+ touch a/b/c;
+ ln -s c a/b/d;
+
+ $* a -type l >>/EOO
+ a/b/d
+ EOO
+ }
+}
+
+: mindepth-primary
+:
+{
+ mkdir -p a/b/c;
+
+ $* a -mindepth 0 >>/EOO;
+ a
+ a/b
+ a/b/c
+ EOO
+
+ $* a -mindepth 1 >>/EOO;
+ a/b
+ a/b/c
+ EOO
+
+ $* a -mindepth 2 >>/EOO;
+ a/b/c
+ EOO
+
+ $* a -mindepth 3
+}
+
+: maxdepth-primary
+:
+{
+ mkdir -p a/b/c;
+
+ $* a -maxdepth 0 >>/EOO;
+ a
+ EOO
+
+ $* a -maxdepth 1 >>/EOO;
+ a
+ a/b
+ EOO
+
+ $* a -maxdepth 2 >>/EOO;
+ a
+ a/b
+ a/b/c
+ EOO
+
+ $* a -maxdepth 3 >>/EOO
+ a
+ a/b
+ a/b/c
+ EOO
+}
diff --git a/tests/command/driver.cxx b/tests/command/driver.cxx
index 0c070ab..9194c13 100644
--- a/tests/command/driver.cxx
+++ b/tests/command/driver.cxx
@@ -1,38 +1,22 @@
// file : tests/command/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <ios>
#include <string>
#include <vector>
#include <iostream>
#include <stdexcept> // invalid_argument
#include <system_error>
-#endif
-// Other includes.
+#include <libbutl/path.hxx>
+#include <libbutl/path-io.hxx>
+#include <libbutl/process.hxx>
+#include <libbutl/command.hxx>
+#include <libbutl/utility.hxx>
+#include <libbutl/optional.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.path_io;
-import butl.process; // process::print()
-import butl.command;
-import butl.utility;
-import butl.optional;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/path-io.mxx>
-#include <libbutl/process.mxx>
-#include <libbutl/command.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/optional.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/cpfile/driver.cxx b/tests/cpfile/driver.cxx
index c613b49..fe01bdd 100644
--- a/tests/cpfile/driver.cxx
+++ b/tests/cpfile/driver.cxx
@@ -1,29 +1,16 @@
// file : tests/cpfile/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <ios>
#include <string>
#include <system_error>
-#endif
-// Other includes.
+#include <libbutl/path.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/filesystem.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.fdstream;
-import butl.filesystem;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/filesystem.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/curl/driver.cxx b/tests/curl/driver.cxx
index 18ed1e6..856fde3 100644
--- a/tests/curl/driver.cxx
+++ b/tests/curl/driver.cxx
@@ -1,35 +1,17 @@
// file : tests/curl/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <iostream>
#include <system_error>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.curl;
-import butl.path;
-import butl.process;
-import butl.utility; // operator<<(ostream, exception)
-import butl.fdstream;
-
-import butl.optional; // @@ MOD Clang should not be necessary.
-import butl.small_vector; // @@ MOD Clang should not be necessary.
-#else
-#include <libbutl/curl.mxx>
-#include <libbutl/path.mxx>
-#include <libbutl/process.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/fdstream.mxx>
-#endif
+
+#include <libbutl/curl.hxx>
+#include <libbutl/path.hxx>
+#include <libbutl/process.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
+#include <libbutl/fdstream.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -122,6 +104,26 @@ http ()
c.out.close ();
assert (!c.wait ());
}
+
+ // POST from stream without --fail.
+ //
+ {
+ curl c (p, path ("-"), nullfd, 2,
+ curl::post,
+ curl::flags::no_fail,
+ u + "/bogus");
+
+ c.out << "bogus" << endl;
+ c.out.close ();
+ assert (c.wait ());
+ }
+
+ // POST empty data.
+ //
+ {
+ curl c (p, nullfd, 1, 2, curl::post, u + "/bogus", "--verbose");
+ assert (!c.wait ());
+ }
}
int
diff --git a/tests/curl/testscript b/tests/curl/testscript
index 3da2306..d2056cd 100644
--- a/tests/curl/testscript
+++ b/tests/curl/testscript
@@ -43,14 +43,22 @@ sudo /usr/sbin/in.tftpd \
: http
:
{
- $* 'http' 2>>EOE
+ $* 'http' 2>>~%EOE%
- curl -s -S --fail --location https://build2.org/bogus
- curl: (22) The requested URL returned error: 404 Not Found
+ curl -sS --fail --location https://build2.org/bogus
+ %curl: \(22\) The requested URL returned error: 404( Not Found)?%
- curl -s -S --fail --location https://build2.org
+ curl -sS --fail --location https://build2.org
- curl -s -S --fail --location --data-binary @- https://build2.org/bogus
- curl: (22) The requested URL returned error: 404 Not Found
+ curl -sS --fail --location --data-binary @- https://build2.org/bogus
+ %curl: \(22\) The requested URL returned error: 404( Not Found)?%
+
+ curl -sS --location --data-binary @- https://build2.org/bogus
+
+ curl -sS --fail --location --data-raw "" --verbose https://build2.org/bogus
+ %.*
+ %> POST /bogus HTTP.+%
+ %.*
+ %curl: \(22\) The requested URL returned error: 404( Not Found)?%
EOE
}
diff --git a/tests/default-options/driver.cxx b/tests/default-options/driver.cxx
index 17da19c..766dca8 100644
--- a/tests/default-options/driver.cxx
+++ b/tests/default-options/driver.cxx
@@ -1,37 +1,22 @@
// file : tests/default-options/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <limits>
#include <string>
#include <vector>
#include <iostream>
#include <exception>
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.path_io;
-import butl.optional;
-import butl.fdstream;
-import butl.default_options;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/path-io.mxx>
-#include <libbutl/utility.mxx> // eof()
-#include <libbutl/optional.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/default-options.mxx>
-#endif
+
+#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;
diff --git a/tests/dir-iterator/driver.cxx b/tests/dir-iterator/driver.cxx
index b73e2e2..c9f7218 100644
--- a/tests/dir-iterator/driver.cxx
+++ b/tests/dir-iterator/driver.cxx
@@ -1,30 +1,17 @@
// file : tests/dir-iterator/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <cstddef> // size_t
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.path_io;
-import butl.utility;
-import butl.filesystem;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/path-io.mxx>
-#include <libbutl/utility.mxx> // operator<<(ostream, exception)
-#include <libbutl/filesystem.mxx>
-#endif
+
+#include <libbutl/path.hxx>
+#include <libbutl/path-io.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
+#include <libbutl/timestamp.hxx>
+#include <libbutl/filesystem.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -40,7 +27,7 @@ operator<< (ostream& os, entry_type e)
return os << entry_type_string[static_cast<size_t> (e)];
}
-// Usage: argv[0] [-v] [-i] <dir>
+// Usage: argv[0] [-v] [-i|-d] <dir>
//
// Iterates over a directory filesystem sub-entries, obtains their types and
// target types for symlinks.
@@ -52,6 +39,10 @@ operator<< (ostream& os, entry_type e)
// Ignore dangling symlinks, rather than fail trying to obtain the target
// type.
//
+// -d
+// Detect dangling symlinks, rather than fail trying to obtain the target
+// type.
+//
int
main (int argc, const char* argv[])
{
@@ -59,6 +50,7 @@ main (int argc, const char* argv[])
bool verbose (false);
bool ignore_dangling (false);
+ bool detect_dangling (false);
int i (1);
for (; i != argc; ++i)
@@ -69,6 +61,8 @@ main (int argc, const char* argv[])
verbose = true;
else if (v == "-i")
ignore_dangling = true;
+ else if (v == "-d")
+ detect_dangling = true;
else
break;
}
@@ -79,15 +73,42 @@ main (int argc, const char* argv[])
return 1;
}
+ assert (!ignore_dangling || !detect_dangling);
+
const char* d (argv[i]);
try
{
- for (const dir_entry& de: dir_iterator (dir_path (d), ignore_dangling))
+ for (const dir_entry& de:
+ dir_iterator (dir_path (d),
+ (ignore_dangling ? dir_iterator::ignore_dangling :
+ detect_dangling ? dir_iterator::detect_dangling :
+ dir_iterator::no_follow)))
{
+ timestamp mt (de.mtime ());
+ timestamp at (de.atime ());
+
entry_type lt (de.ltype ());
entry_type t (lt == entry_type::symlink ? de.type () : lt);
+
const path& p (de.path ());
+ path fp (de.base () / p);
+
+ entry_time et (t == entry_type::directory
+ ? dir_time (path_cast<dir_path> (fp))
+ : file_time (fp));
+
+ if (mt != timestamp_unknown)
+ assert (mt == et.modification);
+
+ if (at != timestamp_unknown)
+ assert (mt == et.access);
+
+ if (de.mtime () != timestamp_unknown)
+ assert (de.mtime () == et.modification);
+
+ if (de.atime () != timestamp_unknown)
+ assert (de.atime () == et.access);
if (verbose)
{
diff --git a/tests/dir-iterator/testscript b/tests/dir-iterator/testscript
index 03ed164..9bc5513 100644
--- a/tests/dir-iterator/testscript
+++ b/tests/dir-iterator/testscript
@@ -7,6 +7,8 @@ test.options = -v
:
mkdir a;
touch a/b;
+sleep 1;
+echo "a" >=a/b; # Change modification time.
$* a >"reg b"
: dir
@@ -24,16 +26,16 @@ $* a >"dir b"
if ($test.target == $build.host)
{
+if ($cxx.target.class != 'windows')
- lnf = ^ln -s t wd/l &wd/l
- lnd = $lnf
+ lnf = [cmdline] ^ln -s t wd/l &wd/l
+ lnd = [cmdline] $lnf
else
echo 'yes' >=t
if cmd /C 'mklink l t' >- 2>- &?l && cat l >'yes'
- lnf = cmd /C 'mklink wd\l t' &wd/l >-
- lnd = cmd /C 'mklink /D wd\l t' &wd/l >-
+ lnf = [cmdline] cmd /C 'mklink wd\l t' &wd/l >-
+ lnd = [cmdline] cmd /C 'mklink /D wd\l t' &wd/l >-
end
- jnc = cmd /C 'mklink /J wd\l wd\t' &wd/l >-
+ jnc = [cmdline] cmd /C 'mklink /J wd\l wd\t' &wd/l >-
end
: symlink
@@ -54,6 +56,12 @@ if ($test.target == $build.host)
$* ../wd >- 2>! != 0 : keep
$* -i ../wd >'reg f': skip
+
+ : detect
+ :
+ $* -d ../wd >>~%EOO%
+ %(reg f|sym unk l)%{2}
+ EOO
}
: dir
@@ -71,6 +79,12 @@ if ($test.target == $build.host)
$* ../wd >- 2>! != 0 : keep
$* -i ../wd >'dir d': skip
+
+ : detect
+ :
+ $* -d ../wd >>~%EOO%
+ %(dir d|sym unk l)%{2}
+ EOO
}
}
@@ -89,5 +103,11 @@ if ($test.target == $build.host)
$* ../wd >- 2>! != 0 : keep
$* -i ../wd >'dir d': skip
+
+ : detect
+ :
+ $* -d ../wd >>~%EOO%
+ %(dir d|sym unk l)%{2}
+ EOO
}
}
diff --git a/tests/entry-time/driver.cxx b/tests/entry-time/driver.cxx
index 1e64b0d..c29837d 100644
--- a/tests/entry-time/driver.cxx
+++ b/tests/entry-time/driver.cxx
@@ -1,31 +1,17 @@
// file : tests/entry-time/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <chrono>
#include <iostream>
-#endif
-// Other includes.
+#include <libbutl/path.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/timestamp.hxx>
+#include <libbutl/filesystem.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.filesystem;
-
-import butl.optional; // @@ MOD Clang should not be necessary.
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/timestamp.mxx>
-#include <libbutl/filesystem.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/fdstream/driver.cxx b/tests/fdstream/driver.cxx
index 2309a7a..ec0c54e 100644
--- a/tests/fdstream/driver.cxx
+++ b/tests/fdstream/driver.cxx
@@ -5,9 +5,6 @@
# include <libbutl/win32-utility.hxx>
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#ifndef _WIN32
# include <chrono>
#endif
@@ -15,38 +12,28 @@
#include <ios>
#include <string>
#include <vector>
-#include <thread>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <utility> // move()
#include <iostream>
#include <exception>
-#endif
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#ifndef _WIN32
-import std.threading;
-#endif
-#endif
-import butl.path;
-import butl.process;
-import butl.fdstream;
-import butl.timestamp;
-import butl.filesystem;
+#ifndef LIBBUTL_MINGW_STDTHREAD
+# include <thread>
#else
-#include <libbutl/path.mxx>
-#include <libbutl/process.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/timestamp.mxx>
-#include <libbutl/filesystem.mxx>
+# include <libbutl/mingw-thread.hxx>
#endif
+#include <libbutl/path.hxx>
+#include <libbutl/process.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/timestamp.hxx>
+#include <libbutl/filesystem.hxx>
+
+#undef NDEBUG
+#include <cassert>
+
using namespace std;
using namespace butl;
@@ -55,7 +42,9 @@ static const string text2 ("12"); // Keep shorter than text1.
// Windows text mode write-translated form of text1.
//
+#ifdef _WIN32
static const string text3 ("ABCDEF\r\nXYZ");
+#endif
static string
from_stream (ifdstream& is)
@@ -133,6 +122,12 @@ read_time (const path& p, const T& s, size_t n)
int
main (int argc, const char* argv[])
{
+#ifndef LIBBUTL_MINGW_STDTHREAD
+ using std::thread;
+#else
+ using mingw_stdthread::thread;
+#endif
+
bool v (false);
bool child (false);
@@ -575,6 +570,83 @@ main (int argc, const char* argv[])
t.join ();
}
+ // Test (non-blocking) reading with getline_non_blocking().
+ //
+ {
+ const string ln (
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
+
+ string s;
+ for (size_t i (0); i < 300; ++i)
+ {
+ s += ln;
+ s += '\n';
+ }
+
+ const char* args[] = {argv[0], "-c", nullptr};
+
+ auto test_read = [&args, &s, &ln] ()
+ {
+ try
+ {
+ process pr (args, -1, -1);
+ ofdstream os (move (pr.out_fd));
+
+ ifdstream is (move (pr.in_ofd),
+ fdstream_mode::non_blocking,
+ ios_base::badbit);
+
+ os << s;
+ os.close ();
+
+ fdselect_set fds {is.fd ()};
+ fdselect_state& ist (fds[0]);
+
+ string r;
+ for (string l; ist.fd != nullfd; )
+ {
+ if (ist.fd != nullfd && getline_non_blocking (is, l))
+ {
+ if (eof (is))
+ ist.fd = nullfd;
+ else
+ {
+ assert (l == ln);
+
+ r += l;
+ r += '\n';
+
+ l.clear ();
+ }
+
+ continue;
+ }
+
+ ifdselect (fds);
+ }
+
+ is.close ();
+
+ assert (r == s);
+ }
+ catch (const ios::failure&)
+ {
+ assert (false);
+ }
+ catch (const process_error&)
+ {
+ assert (false);
+ }
+ };
+
+ vector<thread> threads;
+ for (size_t i (0); i < 20; ++i)
+ threads.emplace_back (test_read);
+
+ for (thread& t: threads)
+ t.join ();
+ }
+
// Test setting and getting position via the non-standard fdstreambuf
// interface.
//
diff --git a/tests/host-os-release/buildfile b/tests/host-os-release/buildfile
new file mode 100644
index 0000000..cd277ff
--- /dev/null
+++ b/tests/host-os-release/buildfile
@@ -0,0 +1,6 @@
+# file : tests/host-os-release/buildfile
+# license : MIT; see accompanying LICENSE file
+
+import libs = libbutl%lib{butl}
+
+exe{driver}: {hxx cxx}{*} $libs testscript
diff --git a/tests/host-os-release/driver.cxx b/tests/host-os-release/driver.cxx
new file mode 100644
index 0000000..249cbff
--- /dev/null
+++ b/tests/host-os-release/driver.cxx
@@ -0,0 +1,58 @@
+// file : tests/host-os-release/driver.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <libbutl/host-os-release.hxx>
+
+#include <libbutl/path.hxx>
+
+namespace butl
+{
+ LIBBUTL_SYMEXPORT os_release
+ host_os_release_linux (path f = {});
+}
+
+#include <iostream>
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace butl;
+
+int
+main (int argc, char* argv[])
+{
+ assert (argc >= 2); // <host-target-triplet>
+
+ target_triplet host (argv[1]);
+
+ os_release r;
+ if (host.class_ == "linux")
+ {
+ assert (argc == 3); // <host-target-triplet> <file-path>
+ r = host_os_release_linux (path (argv[2]));
+ }
+ else
+ {
+ assert (argc == 2);
+ if (optional<os_release> o = host_os_release (host))
+ r = move (*o);
+ else
+ {
+ cerr << "unrecognized host os " << host.string () << endl;
+ return 1;
+ }
+ }
+
+ cout << r.name_id << '\n';
+ for (auto b (r.like_ids.begin ()), i (b); i != r.like_ids.end (); ++i)
+ cout << (i != b ? "|" : "") << *i;
+ cout << '\n'
+ << r.version_id << '\n'
+ << r.variant_id << '\n'
+ << r.name << '\n'
+ << r.version_codename << '\n'
+ << r.variant << '\n';
+
+ return 0;
+}
diff --git a/tests/host-os-release/testscript b/tests/host-os-release/testscript
new file mode 100644
index 0000000..a18aa74
--- /dev/null
+++ b/tests/host-os-release/testscript
@@ -0,0 +1,223 @@
+# file : tests/host-os-release/testscript
+# license : MIT; see accompanying LICENSE file
+
+: linux
+:
+$* x86_64-linux-gnu os-release >>EOO
+ linux
+
+
+
+ Linux
+
+
+ EOO
+
+: debian-10
+:
+cat <<EOI >=os-release;
+ PRETTY_NAME="Debian GNU/Linux 10 (buster)"
+ NAME="Debian GNU/Linux"
+ VERSION_ID="10"
+ VERSION="10 (buster)"
+ VERSION_CODENAME=buster
+ ID=debian
+ HOME_URL="https://www.debian.org/"
+ SUPPORT_URL="https://www.debian.org/support"
+ BUG_REPORT_URL="https://bugs.debian.org/"
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ debian
+
+ 10
+
+ Debian GNU/Linux
+ buster
+
+ EOO
+
+: debian-testing
+:
+cat <<EOI >=os-release;
+ PRETTY_NAME="Debian GNU/Linux bookworm/sid"
+ NAME="Debian GNU/Linux"
+ ID=debian
+ HOME_URL="https://www.debian.org/"
+ SUPPORT_URL="https://www.debian.org/support"
+ BUG_REPORT_URL="https://bugs.debian.org/"
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ debian
+
+
+
+ Debian GNU/Linux
+
+
+ EOO
+
+: ubuntu-20.04
+:
+cat <<EOI >=os-release;
+ NAME="Ubuntu"
+ VERSION="20.04.1 LTS (Focal Fossa)"
+ ID=ubuntu
+ ID_LIKE=debian
+ PRETTY_NAME="Ubuntu 20.04.1 LTS"
+ VERSION_ID="20.04"
+ HOME_URL="https://www.ubuntu.com/"
+ SUPPORT_URL="https://help.ubuntu.com/"
+ BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
+ PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
+ VERSION_CODENAME=focal
+ UBUNTU_CODENAME=focal
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ ubuntu
+ debian
+ 20.04
+
+ Ubuntu
+ focal
+
+ EOO
+
+: fedora-35
+:
+cat <<EOI >=os-release;
+ NAME="Fedora Linux"
+ VERSION="35 (Workstation Edition)"
+ ID=fedora
+ VERSION_ID=35
+ VERSION_CODENAME=""
+ PLATFORM_ID="platform:f35"
+ PRETTY_NAME="Fedora Linux 35 (Workstation Edition)"
+ ANSI_COLOR="0;38;2;60;110;180"
+ LOGO=fedora-logo-icon
+ CPE_NAME="cpe:/o:fedoraproject:fedora:35"
+ HOME_URL="https://fedoraproject.org/"
+ DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f35/system-administrators-guide/"
+ SUPPORT_URL="https://ask.fedoraproject.org/"
+ BUG_REPORT_URL="https://bugzilla.redhat.com/"
+ REDHAT_BUGZILLA_PRODUCT="Fedora"
+ REDHAT_BUGZILLA_PRODUCT_VERSION=35
+ REDHAT_SUPPORT_PRODUCT="Fedora"
+ REDHAT_SUPPORT_PRODUCT_VERSION=35
+ PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"
+ VARIANT="Workstation Edition"
+ VARIANT_ID=workstation
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ fedora
+
+ 35
+ workstation
+ Fedora Linux
+
+ Workstation Edition
+ EOO
+
+: rhel-8.2
+:
+cat <<EOI >=os-release;
+ NAME="Red Hat Enterprise Linux"
+ VERSION="8.2 (Ootpa)"
+ ID="rhel"
+ ID_LIKE="fedora"
+ VERSION_ID="8.2"
+ PLATFORM_ID="platform:el8"
+ PRETTY_NAME="Red Hat Enterprise Linux 8.2 (Ootpa)"
+ ANSI_COLOR="0;31"
+ CPE_NAME="cpe:/o:redhat:enterprise_linux:8.2:GA"
+ HOME_URL="https://www.redhat.com/"
+ BUG_REPORT_URL="https://bugzilla.redhat.com/"
+
+ REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8"
+ REDHAT_BUGZILLA_PRODUCT_VERSION=8.2
+ REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
+ REDHAT_SUPPORT_PRODUCT_VERSION="8.2"
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ rhel
+ fedora
+ 8.2
+
+ Red Hat Enterprise Linux
+
+
+ EOO
+
+: centos-8
+:
+cat <<EOI >=os-release;
+ NAME="CentOS Linux"
+ VERSION="8 (Core)"
+ ID="centos"
+ ID_LIKE="rhel fedora"
+ VERSION_ID="8"
+ PLATFORM_ID="platform:el8"
+ PRETTY_NAME="CentOS Linux 8 (Core)"
+ ANSI_COLOR="0;31"
+ CPE_NAME="cpe:/o:centos:centos:8"
+ HOME_URL="https://www.centos.org/"
+ BUG_REPORT_URL="https://bugs.centos.org/"
+
+ CENTOS_MANTISBT_PROJECT="CentOS-8"
+ CENTOS_MANTISBT_PROJECT_VERSION="8"
+ REDHAT_SUPPORT_PRODUCT="centos"
+ REDHAT_SUPPORT_PRODUCT_VERSION="8"
+ EOI
+$* x86_64-linux-gnu os-release >>EOO
+ centos
+ rhel|fedora
+ 8
+
+ CentOS Linux
+
+
+ EOO
+
+: macos
+:
+if ($build.host.class == 'macos')
+{
+ $* $build.host >>~/EOO/
+ macos
+
+ /[0-9]+(\.[0-9]+(\.[0-9]+)?)?/
+
+ Mac OS
+
+
+ EOO
+}
+
+: freebsd
+:
+if ($build.host.system == 'freebsd')
+{
+ $* $build.host >>~/EOO/
+ freebsd
+
+ /[0-9]+\.[0-9]+/
+
+ FreeBSD
+
+
+ EOO
+}
+
+: windows
+:
+if ($build.host.system == 'windows')
+{
+ $* $build.host >>~/EOO/
+ windows
+
+ /[0-9]+(\.[0-9]+)?/
+
+ Windows
+
+
+ EOO
+}
diff --git a/tests/link/driver.cxx b/tests/link/driver.cxx
index 231da4b..b659838 100644
--- a/tests/link/driver.cxx
+++ b/tests/link/driver.cxx
@@ -1,34 +1,19 @@
// file : tests/link/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <set>
#include <utility> // pair
#include <iostream> // cerr
#include <system_error>
-#endif
-// Other includes.
+#include <libbutl/path.hxx>
+#include <libbutl/path-io.hxx>
+#include <libbutl/utility.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/filesystem.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.path_io;
-import butl.utility;
-import butl.fdstream;
-import butl.filesystem;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/path-io.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/filesystem.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -122,11 +107,11 @@ link_dir (const dir_path& target,
dir_path tp (target.absolute () ? target : link.directory () / target);
set<pair<entry_type, path>> te;
- for (const dir_entry& de: dir_iterator (tp, false /* ignore_dangling */))
+ for (const dir_entry& de: dir_iterator (tp, dir_iterator::no_follow))
te.emplace (de.ltype (), de.path ());
set<pair<entry_type, path>> le;
- for (const dir_entry& de: dir_iterator (link, false /* ignore_dangling */))
+ for (const dir_entry& de: dir_iterator (link, dir_iterator::no_follow))
le.emplace (de.ltype (), de.path ());
return te == le;
@@ -321,7 +306,7 @@ main (int argc, const char* argv[])
assert (pe.first && pe.second.type == entry_type::directory);
}
- for (const dir_entry& de: dir_iterator (td, false /* ignore_dangling */))
+ for (const dir_entry& de: dir_iterator (td, dir_iterator::no_follow))
{
assert (de.path () != path ("dslink") ||
(de.type () == entry_type::directory &&
@@ -383,7 +368,9 @@ main (int argc, const char* argv[])
{
mksymlink (dp / "non-existing", dp / "lnk");
assert (!dir_empty (dp));
- assert (dir_iterator (dp, true /* ignore_dangling */) == dir_iterator ());
+
+ assert (dir_iterator (dp, dir_iterator::ignore_dangling) ==
+ dir_iterator ());
}
catch (const system_error& e)
{
@@ -408,10 +395,10 @@ main (int argc, const char* argv[])
mksymlink (dp / "non-existing", dp / "lnk1", true /* dir */);
assert (!dir_empty (dp));
- assert (dir_iterator (dp, true /* ignore_dangling */) == dir_iterator ());
+ assert (dir_iterator (dp, dir_iterator::ignore_dangling) == dir_iterator ());
mksymlink (tgd, dp / "lnk2", true /* dir */);
- assert (dir_iterator (dp, true /* ignore_dangling */) != dir_iterator ());
+ assert (dir_iterator (dp, dir_iterator::ignore_dangling) != dir_iterator ());
rmdir_r (dp);
assert (dir_exists (tgd));
diff --git a/tests/lz4/driver.cxx b/tests/lz4/driver.cxx
index e2fd537..8139c34 100644
--- a/tests/lz4/driver.cxx
+++ b/tests/lz4/driver.cxx
@@ -5,8 +5,11 @@
#include <exception>
#include <libbutl/lz4.hxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/filesystem.mxx> // entry_stat, path_entry()
+#include <libbutl/fdstream.hxx>
+#include <libbutl/filesystem.hxx> // entry_stat, path_entry()
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/manifest-parser/driver.cxx b/tests/manifest-parser/driver.cxx
index a34f2b7..56c614a 100644
--- a/tests/manifest-parser/driver.cxx
+++ b/tests/manifest-parser/driver.cxx
@@ -1,29 +1,17 @@
// file : tests/manifest-parser/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <vector>
#include <string>
#include <utility> // pair, move()
#include <sstream>
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.optional;
-import butl.manifest_parser;
-#else
-#include <libbutl/optional.mxx>
-#include <libbutl/manifest-parser.mxx>
-#endif
+
+#include <libbutl/optional.hxx>
+#include <libbutl/manifest-parser.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
@@ -176,14 +164,18 @@ namespace butl
// Manifest value splitting (into the value/comment pair).
//
+ // Single-line.
+ //
{
- auto p (manifest_parser::split_comment ("value\\; text ; comment text"));
- assert (p.first == "value; text" && p.second == "comment text");
+ auto p (manifest_parser::split_comment (
+ "\\value\\\\\\; text ; comment text"));
+
+ assert (p.first == "\\value\\; text" && p.second == "comment text");
}
{
- auto p (manifest_parser::split_comment ("value"));
- assert (p.first == "value" && p.second == "");
+ auto p (manifest_parser::split_comment ("value\\"));
+ assert (p.first == "value\\" && p.second == "");
}
{
@@ -191,6 +183,59 @@ namespace butl
assert (p.first == "" && p.second == "comment");
}
+ // Multi-line.
+ //
+ {
+ auto p (manifest_parser::split_comment ("value\n;"));
+ assert (p.first == "value" && p.second == "");
+ }
+
+ {
+ auto p (manifest_parser::split_comment ("value\ntext\n"));
+ assert (p.first == "value\ntext\n" && p.second == "");
+ }
+
+ {
+ auto p (manifest_parser::split_comment ("value\ntext\n;"));
+ assert (p.first == "value\ntext" && p.second == "");
+ }
+
+ {
+ auto p (manifest_parser::split_comment ("value\ntext\n;\n"));
+ assert (p.first == "value\ntext" && p.second == "");
+ }
+
+ {
+ auto p (manifest_parser::split_comment ("\n\\\nvalue\ntext\n"
+ ";\n"
+ "\n\n comment\ntext"));
+
+ assert (p.first == "\n\\\nvalue\ntext" && p.second ==
+ "\n\n comment\ntext");
+ }
+
+ {
+ auto p (manifest_parser::split_comment ("\n;\ncomment"));
+ assert (p.first == "" && p.second == "comment");
+ }
+
+ {
+ auto p (manifest_parser::split_comment (";\ncomment"));
+ assert (p.first == "" && p.second == "comment");
+ }
+
+ {
+ auto p (manifest_parser::split_comment (";\n"));
+ assert (p.first == "" && p.second == "");
+ }
+
+ {
+ auto p (manifest_parser::split_comment (
+ "\\;\n\\\\;\n\\\\\\;\n\\\\\\\\;\n\\\\\\\\\\;"));
+
+ assert (p.first == ";\n\\;\n\\;\n\\\\;\n\\\\;" && p.second == "");
+ }
+
// UTF-8.
//
assert (test (":1\n#\xD0\xB0\n\xD0\xB0y\xD0\xB0:\xD0\xB0z\xD0\xB0",
diff --git a/tests/manifest-rewriter/driver.cxx b/tests/manifest-rewriter/driver.cxx
index ec73d81..3b1dfe9 100644
--- a/tests/manifest-rewriter/driver.cxx
+++ b/tests/manifest-rewriter/driver.cxx
@@ -1,36 +1,21 @@
// file : tests/manifest-rewriter/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <vector>
#include <string>
#include <cstdint> // uint64_t
#include <utility> // move()
#include <iostream>
#include <exception>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.optional;
-import butl.fdstream;
-import butl.manifest_parser;
-import butl.manifest_rewriter;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/manifest-parser.mxx>
-#include <libbutl/manifest-rewriter.mxx>
-#endif
+
+#include <libbutl/path.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/manifest-parser.hxx>
+#include <libbutl/manifest-rewriter.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
@@ -82,17 +67,26 @@ namespace butl
{{"a", "xyz"}, edit_cmd {"x", "y", "c"}, {"e", "123"}}) ==
":1\na: xyz\nc:d\nx: y\ne: 123");
- assert (edit (":1\na: b", {{"a", "xy\nz"}}) == ":1\na: \\\nxy\nz\n\\");
+ assert (edit (":1\na: b", {{"a", "xy\nz"}}) == ":1\na:\\\nxy\nz\n\\");
+
+ assert (edit (":1\na:\\\nxy\nz\n\\\nb: c", {{"a", "ab\ncd\ne"}}) ==
+ ":1\na:\\\nab\ncd\ne\n\\\nb: c");
+
+ assert (edit (":1\na: \\\nxy\nz\n\\\nb: c", {{"a", "ab\ncd\ne"}}) ==
+ ":1\na:\\\nab\ncd\ne\n\\\nb: c");
+
+ assert (edit (":1\na:\n\\\nxy\nz\n\\\nb: c", {{"a", "ab\ncd\ne"}}) ==
+ ":1\na:\\\nab\ncd\ne\n\\\nb: c");
assert (edit (":1\n", {{"a", "b", ""}}) == ":1\na: b\n");
assert (edit (":1\n abc: b",
{{"abc", "xyz"}}) ==
- ":1\n abc: \\\nxyz\n\\");
+ ":1\n abc:\\\nxyz\n\\");
assert (edit (":1\n a\xD0\xB0g : b",
{{"a\xD0\xB0g", "xyz"}}) ==
- ":1\n a\xD0\xB0g : \\\nxyz\n\\");
+ ":1\n a\xD0\xB0g :\\\nxyz\n\\");
// Test editing of manifests that contains CR characters.
//
diff --git a/tests/manifest-roundtrip/buildfile b/tests/manifest-roundtrip/buildfile
index 8056f64..7ddcc1f 100644
--- a/tests/manifest-roundtrip/buildfile
+++ b/tests/manifest-roundtrip/buildfile
@@ -3,5 +3,4 @@
import libs = libbutl%lib{butl}
-exe{driver}: {hxx cxx}{*} $libs
-exe{driver}: manifest: test.roundtrip = true
+exe{driver}: {hxx cxx}{*} $libs testscript
diff --git a/tests/manifest-roundtrip/driver.cxx b/tests/manifest-roundtrip/driver.cxx
index 53b688e..c63a729 100644
--- a/tests/manifest-roundtrip/driver.cxx
+++ b/tests/manifest-roundtrip/driver.cxx
@@ -1,45 +1,60 @@
// file : tests/manifest-roundtrip/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.utility; // operator<<(ostream, exception)
-import butl.fdstream;
-import butl.manifest_parser;
-import butl.manifest_serializer;
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/manifest-parser.mxx>
-#include <libbutl/manifest-serializer.mxx>
-#endif
+
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
+#include <libbutl/fdstream.hxx>
+#include <libbutl/manifest-parser.hxx>
+#include <libbutl/manifest-serializer.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
+// Usage: argv[0] [-m]
+//
+// Round-trip a manifest reading it from stdin and printing to stdout.
+//
+// -m
+// Serialize multi-line manifest values using the v2 form.
+//
+// -s
+// Split values into the value/comment pairs and merge them back before
+// printing.
+//
int
-main ()
+main (int argc, const char* argv[])
try
{
+ bool multiline_v2 (false);
+ bool split (false);
+
+ for (int i (1); i != argc; ++i)
+ {
+ string v (argv[i]);
+
+ if (v == "-m")
+ multiline_v2 = true;
+ else if (v == "-s")
+ split = true;
+ }
+
// Read/write in binary mode.
//
stdin_fdmode (fdstream_mode::binary);
stdout_fdmode (fdstream_mode::binary);
manifest_parser p (cin, "stdin");
- manifest_serializer s (cout, "stdout");
+
+ manifest_serializer s (cout,
+ "stdout",
+ false /* long_lines */,
+ {} /* filter */,
+ multiline_v2);
for (bool eom (true), eos (false); !eos; )
{
@@ -53,6 +68,12 @@ try
else
eom = false;
+ if (split)
+ {
+ const auto& vc (manifest_parser::split_comment (nv.value));
+ nv.value = manifest_serializer::merge_comment (vc.first, vc.second);
+ }
+
s.next (nv.name, nv.value);
}
}
diff --git a/tests/manifest-roundtrip/manifest b/tests/manifest-roundtrip/manifest
deleted file mode 100644
index 23c2730..0000000
--- a/tests/manifest-roundtrip/manifest
+++ /dev/null
@@ -1,32 +0,0 @@
-: 1
-name: libbpkg
-version: 1.0.1
-summary: build2 package manager library
-license: MIT
-tags: c++, package, manager, bpkg
-description: A very very very very very very very very very very very very\
- very very very very very very very very very very very very very very very\
- very very long description.
-changes: \
-1.0.1
- - Fixed a very very very very very very very very very very very very very\
- very annoying bug.
-1.0.0
- - Firts public release
- - Lots of really cool features
-\
-url: http://www.codesynthesis.com/projects/libstudxml/
-email: build-users@codesynthesis.com; Public mailing list, posts by\
- non-members are allowed but moderated.
-package-email: boris@codesynthesis.com; Direct email to the author.
-depends: libbutl
-depends: * build2
-depends: ?* bpkg
-requires: ?* linux | windows
-requires: c++11
-:
-path: c:\windows\\
-path: \
-
-c:\windows\\
-\
diff --git a/tests/manifest-roundtrip/testscript b/tests/manifest-roundtrip/testscript
new file mode 100644
index 0000000..a228b0f
--- /dev/null
+++ b/tests/manifest-roundtrip/testscript
@@ -0,0 +1,118 @@
+# file : tests/manifest-roundtrip/testscript
+# license : MIT; see accompanying LICENSE file
+
+: basics
+:
+$* <<EOF >>EOF
+ : 1
+ name: libbpkg
+ version: 1.0.1
+ summary: build2 package manager library
+ license: MIT
+ tags: c++, package, manager, bpkg
+ description: A very very very very very very very very very very very very\
+ very very very very very very very very very very very very very very very\
+ very very long description.
+ changes:\
+ 1.0.1
+ - Fixed a very very very very very very very very very very very very very\
+ very annoying bug.
+ 1.0.0
+ - Firts public release
+ - Lots of really cool features
+ \
+ url: http://www.codesynthesis.com/projects/libstudxml/
+ email: build-users@codesynthesis.com; Public mailing list, posts by\
+ non-members are allowed but moderated.
+ package-email: boris@codesynthesis.com; Direct email to the author.
+ depends: libbutl
+ depends: * build2
+ depends: * bpkg
+ requires: * linux ? ($linux) | windows ? ($windows)
+ requires: c++11
+ :
+ path: c:\windows\\
+ path:\
+
+ c:\windows\\
+ \
+ EOF
+
+: multiline-v2
+:
+$* -m <<EOF >>EOF
+ : 1
+ name: libbpkg
+ version: 1.0.1
+ summary: build2 package manager library
+ license: MIT
+ tags: c++, package, manager, bpkg
+ description: A very very very very very very very very very very very very\
+ very very very very very very very very very very very very very very very\
+ very very long description.
+ changes:
+ \
+ 1.0.1
+ - Fixed a very very very very very very very very very very very very very\
+ very annoying bug.
+ 1.0.0
+ - Firts public release
+ - Lots of really cool features
+ \
+ url: http://www.codesynthesis.com/projects/libstudxml/
+ email: build-users@codesynthesis.com; Public mailing list, posts by\
+ non-members are allowed but moderated.
+ package-email: boris@codesynthesis.com; Direct email to the author.
+ depends: libbutl
+ depends: * build2
+ depends: * bpkg
+ requires: * linux ? ($linux) | windows ? ($windows)
+ requires: c++11
+ :
+ path: c:\windows\\
+ path:
+ \
+
+ c:\windows\\
+ \
+ EOF
+
+: split-merge-comment
+:
+$* -s <<EOF >>EOF
+ : 1
+ info:\
+ value
+ text
+ \
+ info:\
+ value
+ text
+ ;
+ comment
+ \
+ info:\
+ ;
+ comment
+ text
+ \
+ info:\
+ value
+ \;
+ \\
+ ;
+ comment
+ \
+ info:\
+ value
+ \\;
+ ;
+ comment
+ \
+ info:\
+ value
+ \\\\;
+ ;
+ comment
+ \
+ EOF
diff --git a/tests/manifest-serializer/driver.cxx b/tests/manifest-serializer/driver.cxx
index c818b4a..a003fa4 100644
--- a/tests/manifest-serializer/driver.cxx
+++ b/tests/manifest-serializer/driver.cxx
@@ -1,27 +1,16 @@
// file : tests/manifest-serializer/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <vector>
#include <string>
#include <utility> // pair
#include <sstream>
#include <iostream>
-#endif
-// Other includes.
+#include <libbutl/manifest-serializer.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.manifest_serializer;
-#else
-#include <libbutl/manifest-serializer.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -200,21 +189,21 @@ main ()
//
string n ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
assert (test ({{"","1"},{n,"x"},{"",""},{"",""}},
- ": 1\n" + n + ": \\\nx\n\\\n"));
+ ": 1\n" + n + ":\\\nx\n\\\n"));
assert (test ({{"","1"},{"a","\n"},{"",""},{"",""}},
- ": 1\na: \\\n\n\n\\\n"));
+ ": 1\na:\\\n\n\n\\\n"));
assert (test ({{"","1"},{"a","\n\n"},{"",""},{"",""}},
- ": 1\na: \\\n\n\n\n\\\n"));
+ ": 1\na:\\\n\n\n\n\\\n"));
assert (test ({{"","1"},{"a","\nx\n"},{"",""},{"",""}},
- ": 1\na: \\\n\nx\n\n\\\n"));
+ ": 1\na:\\\n\nx\n\n\\\n"));
assert (test ({{"","1"},{"a","x\ny\nz"},{"",""},{"",""}},
- ": 1\na: \\\nx\ny\nz\n\\\n"));
+ ": 1\na:\\\nx\ny\nz\n\\\n"));
assert (test ({{"","1"},{"a"," x"},{"",""},{"",""}},
- ": 1\na: \\\n x\n\\\n"));
+ ": 1\na:\\\n x\n\\\n"));
assert (test ({{"","1"},{"a","x "},{"",""},{"",""}},
- ": 1\na: \\\nx \n\\\n"));
+ ": 1\na:\\\nx \n\\\n"));
assert (test ({{"","1"},{"a"," x "},{"",""},{"",""}},
- ": 1\na: \\\n x \n\\\n"));
+ ": 1\na:\\\n x \n\\\n"));
// The long lines mode.
//
@@ -223,51 +212,76 @@ main ()
true /* long_lines */));
assert (test ({{"","1"},{"a", " abc\n" + l1 + "\ndef"},{"",""},{"",""}},
- ": 1\na: \\\n abc\n" + l1 + "\ndef\n\\\n",
+ ": 1\na:\\\n abc\n" + l1 + "\ndef\n\\\n",
true /* long_lines */));
assert (test ({{"","1"},{n,l1},{"",""},{"",""}},
- ": 1\n" + n + ": \\\n" + l1 + "\n\\\n",
+ ": 1\n" + n + ":\\\n" + l1 + "\n\\\n",
true /* long_lines */));
// Carriage return character.
//
assert (test ({{"","1"},{"a","x\ry"},{"",""},{"",""}},
- ": 1\na: \\\nx\ny\n\\\n"));
+ ": 1\na:\\\nx\ny\n\\\n"));
assert (test ({{"","1"},{"a","x\r"},{"",""},{"",""}},
- ": 1\na: \\\nx\n\n\\\n"));
+ ": 1\na:\\\nx\n\n\\\n"));
assert (test ({{"","1"},{"a","x\r\ny"},{"",""},{"",""}},
- ": 1\na: \\\nx\ny\n\\\n"));
+ ": 1\na:\\\nx\ny\n\\\n"));
assert (test ({{"","1"},{"a","x\r\n"},{"",""},{"",""}},
- ": 1\na: \\\nx\n\n\\\n"));
+ ": 1\na:\\\nx\n\n\\\n"));
// Extra three x's are for the leading name part ("a: ") that we
// don't have.
//
assert (test ({{"","1"},{"a","\nxxx" + l1},{"",""},{"",""}},
- ": 1\na: \\\n\nxxx" + e1 + "\n\\\n"));
+ ": 1\na:\\\n\nxxx" + e1 + "\n\\\n"));
assert (test ({{"","1"},{"a","\nxxx" + l2},{"",""},{"",""}},
- ": 1\na: \\\n\nxxx" + e2 + "\n\\\n"));
+ ": 1\na:\\\n\nxxx" + e2 + "\n\\\n"));
assert (test ({{"","1"},{"a","\nxxx" + l3},{"",""},{"",""}},
- ": 1\na: \\\n\nxxx" + e3 + "\n\\\n"));
+ ": 1\na:\\\n\nxxx" + e3 + "\n\\\n"));
assert (test ({{"","1"},{"a","\nxxx" + l4},{"",""},{"",""}},
- ": 1\na: \\\n\nxxx" + e4 + "\n\\\n"));
+ ": 1\na:\\\n\nxxx" + e4 + "\n\\\n"));
// Backslash escaping (simple and multi-line).
//
assert (test ({{"","1"},{"a","c:\\"},{"",""},{"",""}},
": 1\na: c:\\\\\n"));
assert (test ({{"","1"},{"a","c:\\\nd:\\"},{"",""},{"",""}},
- ": 1\na: \\\nc:\\\\\nd:\\\\\n\\\n"));
+ ": 1\na:\\\nc:\\\\\nd:\\\\\n\\\n"));
// Manifest value/comment merging.
//
- assert (manifest_serializer::merge_comment ("value; text", "comment") ==
- "value\\; text; comment");
+ // Single-line.
+ //
+ assert (manifest_serializer::merge_comment ("value\\; text", "comment") ==
+ "value\\\\\\; text; comment");
assert (manifest_serializer::merge_comment ("value text", "") ==
"value text");
+ // Multi-line.
+ //
+ assert (manifest_serializer::merge_comment ("value\n;\ntext", "comment") ==
+ "value\n\\;\ntext\n;\ncomment");
+
+ assert (manifest_serializer::merge_comment ("value\n\\;\ntext\n",
+ "comment") ==
+ "value\n\\\\;\ntext\n\n;\ncomment");
+
+ assert (manifest_serializer::merge_comment ("value\n\\\\;\ntext\n",
+ "comment") ==
+ "value\n\\\\\\\\;\ntext\n\n;\ncomment");
+
+
+ assert (manifest_serializer::merge_comment ("value\n\\\ntext", "comment") ==
+ "value\n\\\ntext\n;\ncomment");
+
+ assert (manifest_serializer::merge_comment ("\\", "comment\n") ==
+ "\\\n;\ncomment\n");
+
+ assert (manifest_serializer::merge_comment ("", "comment\ntext") ==
+ ";\ncomment\ntext");
+
// Filtering.
//
assert (test ({{"","1"},{"a","abc"},{"b","bca"},{"c","cab"},{"",""},{"",""}},
diff --git a/tests/move-only-function/buildfile b/tests/move-only-function/buildfile
new file mode 100644
index 0000000..9012fd6
--- /dev/null
+++ b/tests/move-only-function/buildfile
@@ -0,0 +1,6 @@
+# file : tests/move-only-function/buildfile
+# license : MIT; see accompanying LICENSE file
+
+import libs = libbutl%lib{butl}
+
+exe{driver}: {hxx cxx}{*} $libs
diff --git a/tests/move-only-function/driver.cxx b/tests/move-only-function/driver.cxx
new file mode 100644
index 0000000..b94d674
--- /dev/null
+++ b/tests/move-only-function/driver.cxx
@@ -0,0 +1,149 @@
+// file : tests/move-only-function/driver.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <memory> // unique_ptr
+#include <utility> // move()
+
+#include <libbutl/move-only-function.hxx>
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+
+static int
+func (int v)
+{
+ return v + 1;
+}
+
+struct functor
+{
+ int i;
+
+ int
+ operator() (int v)
+ {
+ return v + i;
+ }
+};
+
+int
+main ()
+{
+ using butl::move_only_function_ex;
+
+ // Attempt to copy-construct or copy-assign should not compile.
+ // Also check non-collable.
+ //
+#if 0
+ {
+ using ft = move_only_function_ex<int (int)>;
+ ft f;
+ ft f2 (f);
+ ft f3; f3 = f;
+ ft f4 (123);
+ }
+#endif
+
+ // NULL.
+ //
+ {
+ using ft = move_only_function_ex<int (int)>;
+
+ ft f1;
+ assert (!f1);
+
+ ft f2 (nullptr);
+ assert (f2 == nullptr);
+
+ f1 = func;
+ assert (f1 != nullptr);
+ f1 = nullptr;
+ assert (!f1);
+
+ int (*f) (int) = nullptr;
+ f2 = f;
+ assert (!f2);
+ }
+
+ // Function.
+ //
+ {
+ using ft = move_only_function_ex<int (int)>;
+
+ ft f (func);
+
+ assert (f (1) == 2);
+
+ ft f1 (move (f));
+ assert (!f);
+ assert (f1 (1) == 2);
+
+ f = &func;
+
+ assert (f (1) == 2);
+
+ assert (f.target<int (*) (int)> () != nullptr);
+ assert (f1.target<int (*) (int)> () != nullptr);
+ }
+
+ // Functor.
+ //
+ {
+ using ft = move_only_function_ex<int (int)>;
+
+ ft f (functor {1});
+
+ assert (f (1) == 2);
+
+ ft f1 (move (f));
+ assert (!f);
+ assert (f1 (1) == 2);
+
+ f = functor {2};
+
+ assert (f (1) == 3);
+
+ assert (ft (functor {1}).target<functor> () != nullptr);
+ }
+
+ // Lambda.
+ //
+ {
+ using ft = move_only_function_ex<int (int)>;
+
+ ft f ([p = unique_ptr<int> (new int (1))] (int v)
+ {
+ return *p + v;
+ });
+
+ assert (f (1) == 2);
+
+ ft f1 (move (f));
+ assert (!f);
+ assert (f1 (1) == 2);
+
+ f = ([p = unique_ptr<int> (new int (2))] (int v)
+ {
+ return *p + v;
+ });
+
+ assert (f (1) == 3);
+ }
+
+ // Void result.
+ //
+ {
+ using ft = move_only_function_ex<void (int)>;
+
+ ft f ([] (int v)
+ {
+ assert (v == 1);
+ });
+
+ f (1);
+ ft f1 (move (f));
+ f1 (1);
+ }
+}
diff --git a/tests/mventry/driver.cxx b/tests/mventry/driver.cxx
index cb1c348..e895ad6 100644
--- a/tests/mventry/driver.cxx
+++ b/tests/mventry/driver.cxx
@@ -1,28 +1,15 @@
// file : tests/mventry/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <iostream>
#include <system_error>
-#endif
-// Other includes.
+#include <libbutl/path.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
+#include <libbutl/filesystem.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.utility; // operator<<(ostream, exception)
-import butl.filesystem;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/filesystem.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/mventry/testscript b/tests/mventry/testscript
index 61ef871..f52be79 100644
--- a/tests/mventry/testscript
+++ b/tests/mventry/testscript
@@ -98,16 +98,16 @@
if ($test.target == $build.host)
{
+if ($cxx.target.class != 'windows')
- lnf = ^ln -s t l &l
- lnd = $lnf
+ lnf = [cmdline] ^ln -s t l &l
+ lnd = [cmdline] $lnf
else
echo 'yes' >=t
if cmd /C 'mklink l t' >- 2>- &?l && cat l >'yes'
- lnf = cmd /C 'mklink l t' &l >-
- lnd = cmd /C 'mklink /D l t' &l >-
+ lnf = [cmdline] cmd /C 'mklink l t' &l >-
+ lnd = [cmdline] cmd /C 'mklink /D l t' &l >-
end
- jnc = cmd /C 'mklink /J l t' &l >-
+ jnc = [cmdline] cmd /C 'mklink /J l t' &l >-
end
: symlink
diff --git a/tests/next-word/buildfile b/tests/next-word/buildfile
new file mode 100644
index 0000000..e06cd88
--- /dev/null
+++ b/tests/next-word/buildfile
@@ -0,0 +1,6 @@
+# file : tests/next-word/buildfile
+# license : MIT; see accompanying LICENSE file
+
+import libs = libbutl%lib{butl}
+
+exe{driver}: {hxx cxx}{*} $libs
diff --git a/tests/next-word/driver.cxx b/tests/next-word/driver.cxx
new file mode 100644
index 0000000..4ebe1a5
--- /dev/null
+++ b/tests/next-word/driver.cxx
@@ -0,0 +1,46 @@
+// file : tests/next-word/driver.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <vector>
+#include <string>
+//#include <iostream>
+
+#include <libbutl/utility.hxx>
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace butl;
+
+using strings = vector<string>;
+
+static strings
+parse_lines (const string& s)
+{
+ strings r;
+ for (size_t b (0), e (0), m (0), n (s.size ());
+ next_word (s, n, b, e, m, '\n', '\r'), b != n; )
+ {
+ //cerr << "'" << string (s, b, e - b) << "'" << endl;
+ r.push_back (string (s, b, e - b));
+ }
+ return r;
+}
+
+int
+main ()
+{
+ assert ((parse_lines("") == strings {}));
+ assert ((parse_lines("a") == strings {"a"}));
+ assert ((parse_lines("\n") == strings {"", ""}));
+ assert ((parse_lines("\n\n") == strings {"", "", ""}));
+ assert ((parse_lines("\n\n\n") == strings {"", "", "", ""}));
+ assert ((parse_lines("\na") == strings {"", "a"}));
+ assert ((parse_lines("\n\na") == strings {"", "", "a"}));
+ assert ((parse_lines("a\n") == strings {"a", ""}));
+ assert ((parse_lines("a\n\n") == strings {"a", "", ""}));
+ assert ((parse_lines("a\nb") == strings {"a", "b"}));
+ assert ((parse_lines("a\n\nb") == strings {"a", "", "b"}));
+ assert ((parse_lines("\na\nb\n") == strings {"", "a", "b", ""}));
+}
diff --git a/tests/openssl/driver.cxx b/tests/openssl/driver.cxx
index d245a3a..55f91dd 100644
--- a/tests/openssl/driver.cxx
+++ b/tests/openssl/driver.cxx
@@ -1,36 +1,18 @@
// file : tests/openssl/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <vector>
#include <iostream>
#include <iterator>
#include <system_error>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.utility; // operator<<(ostream, exception)
-import butl.openssl;
-import butl.process;
-import butl.fdstream; // nullfd
-
-import butl.optional; // @@ MOD Clang should not be necessary.
-import butl.small_vector; // @@ MOD Clang should not be necessary.
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/openssl.mxx>
-#include <libbutl/fdstream.mxx>
-#endif
+
+#include <libbutl/path.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
+#include <libbutl/openssl.hxx>
+#include <libbutl/fdstream.hxx> // nullfd
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -41,12 +23,28 @@ int
main (int, const char* argv[])
try
{
- openssl os (nullfd, path ("-"), 2, path ("openssl"), "rand", 128);
+ using butl::optional;
+
+ // Test openssl rand command.
+ //
+ {
+ openssl os (nullfd, path ("-"), 2, path ("openssl"), "rand", 128);
+
+ vector<char> r (os.in.read_binary ());
+ os.in.close ();
+
+ assert (os.wait () && r.size () == 128);
+ }
+
+ // Test openssl info retrieval.
+ //
+ {
+ optional<openssl_info> v (openssl::info (2, path ("openssl")));
- vector<char> r (os.in.read_binary ());
- os.in.close ();
+ assert (v);
+ }
- return os.wait () && r.size () == 128 ? 0 : 1;
+ return 0;
}
catch (const system_error& e)
{
diff --git a/tests/optional/driver.cxx b/tests/optional/driver.cxx
index 5d72f08..da09cf5 100644
--- a/tests/optional/driver.cxx
+++ b/tests/optional/driver.cxx
@@ -1,23 +1,13 @@
// file : tests/optional/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <vector>
#include <utility> // move()
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.optional;
-#else
-#include <libbutl/optional.mxx>
-#endif
+
+#include <libbutl/optional.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
diff --git a/tests/pager/driver.cxx b/tests/pager/driver.cxx
index ca3c3b9..c807ed0 100644
--- a/tests/pager/driver.cxx
+++ b/tests/pager/driver.cxx
@@ -1,28 +1,17 @@
// file : tests/pager/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <ios> // ios_base::failure
#include <vector>
#include <string>
#include <utility> // move()
#include <sstream>
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.pager;
-#else
-#include <libbutl/pager.mxx>
-#endif
+
+#include <libbutl/pager.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/path-entry/driver.cxx b/tests/path-entry/driver.cxx
index 30aae92..d9ea2be 100644
--- a/tests/path-entry/driver.cxx
+++ b/tests/path-entry/driver.cxx
@@ -1,36 +1,20 @@
// file : tests/path-entry/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
#include <stdexcept> // invalid_argument
#include <system_error>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.path-io;
-import butl.utility; // operator<<(ostream, exception)
-import butl.optional;
-import butl.timestamp;
-import butl.filesystem;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/path-io.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/timestamp.mxx>
-#include <libbutl/filesystem.mxx>
-#endif
+
+#include <libbutl/path.hxx>
+#include <libbutl/path-io.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
+#include <libbutl/optional.hxx>
+#include <libbutl/timestamp.hxx>
+#include <libbutl/filesystem.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/path-entry/testscript b/tests/path-entry/testscript
index 16039fa..3ac363b 100644
--- a/tests/path-entry/testscript
+++ b/tests/path-entry/testscript
@@ -57,16 +57,16 @@
if ($test.target == $build.host)
{
+if ($cxx.target.class != 'windows')
- lnf = ^ln -s t l &l
- lnd = $lnf
+ lnf = [cmdline] ^ln -s t l &l
+ lnd = [cmdline] $lnf
else
echo 'yes' >=t
if cmd /C 'mklink l t' >- 2>- &?l && cat l >'yes'
- lnf = cmd /C 'mklink l t' &l >-
- lnd = cmd /C 'mklink /D l t' &l >-
+ lnf = [cmdline] cmd /C 'mklink l t' &l >-
+ lnd = [cmdline] cmd /C 'mklink /D l t' &l >-
end
- jnc = cmd /C 'mklink /J l t' &l >-
+ jnc = [cmdline] cmd /C 'mklink /J l t' &l >-
end
: symlink
diff --git a/tests/path/driver.cxx b/tests/path/driver.cxx
index b855e34..3124c13 100644
--- a/tests/path/driver.cxx
+++ b/tests/path/driver.cxx
@@ -1,27 +1,15 @@
// file : tests/path/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <sstream>
#include <iostream>
#include <type_traits>
-#endif
-// Other includes.
+#include <libbutl/path.hxx>
+//#include <libbutl/path-io.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-//import butl.path_io;
-#else
-#include <libbutl/path.mxx>
-//#include <libbutl/path-io.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/prefix-map/driver.cxx b/tests/prefix-map/driver.cxx
index e0da9ea..8ed35ea 100644
--- a/tests/prefix-map/driver.cxx
+++ b/tests/prefix-map/driver.cxx
@@ -1,24 +1,13 @@
// file : tests/prefix-map/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.prefix_map;
-#else
-#include <libbutl/prefix-map.mxx>
-#endif
+
+#include <libbutl/prefix-map.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/process-run/driver.cxx b/tests/process-run/driver.cxx
index 94b6e00..032f890 100644
--- a/tests/process-run/driver.cxx
+++ b/tests/process-run/driver.cxx
@@ -1,31 +1,16 @@
// file : tests/process-run/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.process;
-import butl.optional; // @@ MOD Clang shouldn't be needed.
-import butl.fdstream;
-import butl.small_vector;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/process.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/small-vector.mxx>
-#endif
+
+#include <libbutl/path.hxx>
+#include <libbutl/process.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/small-vector.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/process-term/driver.cxx b/tests/process-term/driver.cxx
index 835272f..799757c 100644
--- a/tests/process-term/driver.cxx
+++ b/tests/process-term/driver.cxx
@@ -10,9 +10,6 @@
# include <libbutl/win32-utility.hxx>
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <cerrno> // ERANGE
#include <utility> // move()
@@ -23,23 +20,13 @@
#ifndef _WIN32
# include <chrono>
#endif
-#endif
-// Other includes.
+#include <libbutl/process.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/fdstream.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.process;
-import butl.optional;
-import butl.fdstream;
-#else
-#include <libbutl/process.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/fdstream.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx
index b018ac1..1ee5710 100644
--- a/tests/process/driver.cxx
+++ b/tests/process/driver.cxx
@@ -1,9 +1,6 @@
// file : tests/process/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <ios>
#include <string>
#include <vector>
@@ -12,30 +9,17 @@
#include <iterator> // istreambuf_iterator, ostream_iterator
#include <algorithm> // copy()
#include <iostream>
-#endif
-// Other includes.
+#include <libbutl/path.hxx>
+#include <libbutl/utility.hxx> // setenv(), getenv()
+#include <libbutl/process.hxx>
+#include <libbutl/process-io.hxx>
+#include <libbutl/optional.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/timestamp.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.utility; // setenv(), getenv()
-import butl.process;
-import butl.optional;
-import butl.fdstream;
-import butl.timestamp;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/process.mxx>
-#include <libbutl/process-io.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/timestamp.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/progress/driver.cxx b/tests/progress/driver.cxx
index 2a0b647..f1a257c 100644
--- a/tests/progress/driver.cxx
+++ b/tests/progress/driver.cxx
@@ -8,38 +8,19 @@
# include <io.h> //_write()
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <cstddef> // size_t
#include <iostream>
#ifndef _WIN32
# include <thread> // this_thread::sleep_for()
#endif
-#endif
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#ifndef _WIN32
-import std.threading;
-#endif
-#endif
-import butl.process;
-import butl.fdstream;
-import butl.diagnostics;
+#include <libbutl/process.hxx>
+#include <libbutl/fdstream.hxx> // fdopen_null(), stderr_fd()
+#include <libbutl/diagnostics.hxx>
-import butl.optional; // @@ MOD Clang should not be necessary.
-import butl.small_vector; // @@ MOD Clang should not be necessary.
-#else
-#include <libbutl/process.mxx>
-#include <libbutl/fdstream.mxx> // fdopen_null(), stderr_fd()
-#include <libbutl/diagnostics.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/project-name/driver.cxx b/tests/project-name/driver.cxx
index 02b3ae3..ac1c898 100644
--- a/tests/project-name/driver.cxx
+++ b/tests/project-name/driver.cxx
@@ -1,28 +1,17 @@
// file : tests/project-name/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <ios> // ios::*bit
#include <string>
#include <iostream>
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.utility; // operator<<(ostream,exception), eof(), *case()
-import butl.project_name;
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/project-name.mxx>
-#endif
+
+#include <libbutl/utility.hxx> // operator<<(ostream,exception), eof(),
+ // *case()
+#include <libbutl/project-name.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/regex/driver.cxx b/tests/regex/driver.cxx
index cb59cd8..f8363e1 100644
--- a/tests/regex/driver.cxx
+++ b/tests/regex/driver.cxx
@@ -1,31 +1,18 @@
// file : tests/regex/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <regex>
#include <string>
#include <utility> // pair
#include <iostream>
#include <stdexcept> // invalid_argument
#include <exception>
-#endif
-// Other includes.
+#include <libbutl/regex.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-import std.regex; // @@ MOD TODO: shouldn't be necessary (re-export).
-#endif
-import butl.regex;
-import butl.utility; // operator<<(ostream, exception)
-#else
-#include <libbutl/regex.mxx>
-#include <libbutl/utility.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/semantic-version/driver.cxx b/tests/semantic-version/driver.cxx
index 032cb14..3c20a6c 100644
--- a/tests/semantic-version/driver.cxx
+++ b/tests/semantic-version/driver.cxx
@@ -1,23 +1,12 @@
// file : tests/semantic-version/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <iostream>
-#endif
-// Other includes.
+#include <libbutl/semantic-version.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.semantic_version;
-#else
-#include <libbutl/semantic-version.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -34,7 +23,6 @@ main ()
semver v;
assert (v.major == 0 && v.minor == 0 && v.patch == 0 && v.build.empty ());
}
-
{
semver v (1, 2, 3);
assert (v.major == 1 && v.minor == 2 && v.patch == 3 && v.build.empty ());
@@ -57,17 +45,27 @@ main ()
// String representation.
//
- assert (semver ("1.2") == semver (1, 2, 0));
- assert (semver ("1.2-3") == semver (1, 2, 0, "-3"));
- assert (semver ("1.2.a1", "+-.") == semver (1, 2, 0, ".a1"));
- assert (semver ("1.2.3") == semver (1, 2, 3));
- assert (semver ("1.2.3-4") == semver (1, 2, 3, "-4"));
- assert (semver ("1.2.3+4") == semver (1, 2, 3, "+4"));
- assert (semver ("1.2.3.4", "+-.") == semver (1, 2, 3, ".4"));
- assert (semver ("1.2.3a", "") == semver (1, 2, 3, "a"));
- try {semver v ("1.2.3-4", false); assert (false);} catch (failed) {}
- try {semver v ("1.2.3.4"); assert (false);} catch (failed) {}
- try {semver v ("1.2.3a"); assert (false);} catch (failed) {}
+ assert (semver ("1", semver::allow_omit_minor) == semver (1, 0, 0));
+ assert (semver ("1-2", semver::allow_omit_minor | semver::allow_build) == semver (1, 0, 0, "-2"));
+ assert (semver ("1.2", semver::allow_omit_minor) == semver (1, 2, 0));
+ assert (semver ("1.2+a", semver::allow_omit_minor | semver::allow_build) == semver (1, 2, 0, "+a"));
+ assert (semver ("1.2", semver::allow_omit_patch) == semver (1, 2, 0));
+ assert (semver ("1.2-3", semver::allow_omit_patch | semver::allow_build) == semver (1, 2, 0, "-3"));
+ assert (semver ("1.2.a1", semver::allow_omit_patch | semver::allow_build, ".+-") == semver (1, 2, 0, ".a1"));
+ assert (semver ("1.2.3") == semver (1, 2, 3));
+ assert (semver ("1.2.3-4", semver::allow_build) == semver (1, 2, 3, "-4"));
+ assert (semver ("1.2.3+4", semver::allow_build) == semver (1, 2, 3, "+4"));
+ assert (semver ("1.2.3.4", semver::allow_build, "+-.") == semver (1, 2, 3, ".4"));
+ assert (semver ("1.2.3a", semver::allow_build, "") == semver (1, 2, 3, "a"));
+
+ try {semver v ("1"); assert (false);} catch (failed) {}
+ try {semver v ("1.x.2"); assert (false);} catch (failed) {}
+ try {semver v ("1.2"); assert (false);} catch (failed) {}
+ try {semver v ("1.2.x"); assert (false);} catch (failed) {}
+ try {semver v ("1.2.3-4"); assert (false);} catch (failed) {}
+ try {semver v ("1.2.3.4"); assert (false);} catch (failed) {}
+ try {semver v ("1.2.3a"); assert (false);} catch (failed) {}
+
assert (!parse_semantic_version ("1.2.3.4"));
// Numeric representation.
diff --git a/tests/sendmail/driver.cxx b/tests/sendmail/driver.cxx
index e73940b..3b97202 100644
--- a/tests/sendmail/driver.cxx
+++ b/tests/sendmail/driver.cxx
@@ -1,34 +1,16 @@
// file : tests/sendmail/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <iostream>
#include <system_error>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.process;
-import butl.utility; // operator<<(ostream, exception)
-import butl.sendmail;
-import butl.fdstream;
-
-import butl.optional; // @@ MOD Clang should not be necessary.
-import butl.small_vector; // @@ MOD Clang should not be necessary.
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/process.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/sendmail.mxx>
-#endif
+
+#include <libbutl/path.hxx>
+#include <libbutl/process.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
+#include <libbutl/sendmail.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/sha1/driver.cxx b/tests/sha1/driver.cxx
index 2b58113..1e8e254 100644
--- a/tests/sha1/driver.cxx
+++ b/tests/sha1/driver.cxx
@@ -1,29 +1,16 @@
// file : tests/sha1/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <cstddef> // size_t
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.sha1;
-import butl.path;
-import butl.fdstream;
-import butl.filesystem;
-#else
-#include <libbutl/sha1.mxx>
-#include <libbutl/path.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/filesystem.mxx> // auto_rmfile
-#endif
+
+#include <libbutl/sha1.hxx>
+#include <libbutl/path.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/filesystem.hxx> // auto_rmfile
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/sha256/driver.cxx b/tests/sha256/driver.cxx
index 2946755..30dfa49 100644
--- a/tests/sha256/driver.cxx
+++ b/tests/sha256/driver.cxx
@@ -1,29 +1,16 @@
// file : tests/sha256/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <cstddef> // size_t
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.path;
-import butl.sha256;
-import butl.fdstream;
-import butl.filesystem;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/sha256.mxx>
-#include <libbutl/fdstream.mxx>
-#include <libbutl/filesystem.mxx> // auto_rmfile
-#endif
+
+#include <libbutl/path.hxx>
+#include <libbutl/sha256.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/filesystem.hxx> // auto_rmfile
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/small-forward-list/driver.cxx b/tests/small-forward-list/driver.cxx
index 670fff1..1cfea77 100644
--- a/tests/small-forward-list/driver.cxx
+++ b/tests/small-forward-list/driver.cxx
@@ -1,24 +1,13 @@
// file : tests/small-forward-list/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.small_forward_list;
-#else
-#include <libbutl/small-forward-list.mxx>
-#endif
+
+#include <libbutl/small-forward-list.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/small-list/driver.cxx b/tests/small-list/driver.cxx
index 9674402..8e2fb6e 100644
--- a/tests/small-list/driver.cxx
+++ b/tests/small-list/driver.cxx
@@ -1,24 +1,13 @@
// file : tests/small-list/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
-#endif
-// Other includes.
+#include <libbutl/small-list.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.small_list;
-#else
-#include <libbutl/small-list.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/small-vector/driver.cxx b/tests/small-vector/driver.cxx
index a8326be..cc012fc 100644
--- a/tests/small-vector/driver.cxx
+++ b/tests/small-vector/driver.cxx
@@ -1,24 +1,13 @@
// file : tests/small-vector/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.small_vector;
-#else
-#include <libbutl/small-vector.mxx>
-#endif
+
+#include <libbutl/small-vector.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/standard-version/driver.cxx b/tests/standard-version/driver.cxx
index 4b985e1..4bddf08 100644
--- a/tests/standard-version/driver.cxx
+++ b/tests/standard-version/driver.cxx
@@ -1,31 +1,18 @@
// file : tests/standard-version/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <ios> // ios::failbit, ios::badbit
#include <string>
#include <cstdint> // uint*_t
#include <iostream>
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.utility; // operator<<(ostream,exception), eof()
-import butl.optional;
-import butl.standard_version;
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/standard-version.mxx>
-#endif
+
+#include <libbutl/utility.hxx> // operator<<(ostream,exception), eof()
+#include <libbutl/optional.hxx>
+#include <libbutl/standard-version.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -96,7 +83,7 @@ version (const string& s,
if (v.minor () != 99999)
{
- standard_version_constraint c1 ("~" + s);
+ standard_version_constraint c1 ('~' + s);
standard_version_constraint c2 ('[' + s + ' ' + max_ver ('~') + ')');
assert (c1 == c2);
}
@@ -104,7 +91,7 @@ version (const string& s,
if ((v.major () == 0 && v.minor () != 99999) ||
(v.major () != 0 && v.major () != 99999))
{
- standard_version_constraint c1 ("^" + s);
+ standard_version_constraint c1 ('^' + s);
standard_version_constraint c2 ('[' + s + ' ' + max_ver ('^') + ')');
assert (c1 == c2);
}
diff --git a/tests/strcase/driver.cxx b/tests/strcase/driver.cxx
index f9ea3b6..8e964a6 100644
--- a/tests/strcase/driver.cxx
+++ b/tests/strcase/driver.cxx
@@ -1,22 +1,12 @@
// file : tests/strcase/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
-#endif
-// Other includes.
+#include <libbutl/utility.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.utility;
-#else
-#include <libbutl/utility.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/string-parser/driver.cxx b/tests/string-parser/driver.cxx
index 4e4984e..8cba912 100644
--- a/tests/string-parser/driver.cxx
+++ b/tests/string-parser/driver.cxx
@@ -1,27 +1,15 @@
// file : tests/string-parser/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <iostream>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.utility; // operator<<(ostream,exception)
-import butl.string_parser;
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/string-parser.mxx>
-#endif
+
+#include <libbutl/utility.hxx> // operator<<(ostream,exception)
+#include <libbutl/string-parser.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl::string_parser;
diff --git a/tests/tab-parser/driver.cxx b/tests/tab-parser/driver.cxx
index 5a527cf..99c19d9 100644
--- a/tests/tab-parser/driver.cxx
+++ b/tests/tab-parser/driver.cxx
@@ -1,26 +1,14 @@
// file : tests/tab-parser/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
-#endif
-// Other includes.
+#include <libbutl/utility.hxx> // operator<<(ostream,exception)
+#include <libbutl/tab-parser.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.utility; // operator<<(ostream,exception)
-import butl.tab_parser;
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/tab-parser.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/target-triplet/driver.cxx b/tests/target-triplet/driver.cxx
index bfa01fa..8c08a90 100644
--- a/tests/target-triplet/driver.cxx
+++ b/tests/target-triplet/driver.cxx
@@ -1,25 +1,14 @@
// file : tests/target-triplet/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <iostream>
#include <stdexcept> // invalid_argument
-#endif
-// Other includes.
+#include <libbutl/target-triplet.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.target_triplet;
-#else
-#include <libbutl/target-triplet.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -85,6 +74,10 @@ main ()
"i686-w64-mingw32",
"i686", "w64", "mingw32", "", "windows"));
+ assert (test ("x86_64-w64-windows-gnu",
+ "x86_64-w64-mingw32",
+ "x86_64", "w64", "mingw32", "", "windows"));
+
assert (test ("i686-lfs-linux-gnu",
"i686-lfs-linux-gnu",
"i686", "lfs", "linux-gnu", "", "linux"));
@@ -93,6 +86,10 @@ main ()
"x86_64-linux-gnu",
"x86_64", "", "linux-gnu", "", "linux"));
+ assert (test ("x86_64-redhat-linux",
+ "x86_64-redhat-linux-gnu",
+ "x86_64", "redhat", "linux-gnu", "", "linux"));
+
assert (test ("x86_64-linux-gnux32",
"x86_64-linux-gnux32",
"x86_64", "", "linux-gnux32", "", "linux"));
diff --git a/tests/timestamp/driver.cxx b/tests/timestamp/driver.cxx
index 6283798..956b295 100644
--- a/tests/timestamp/driver.cxx
+++ b/tests/timestamp/driver.cxx
@@ -3,28 +3,17 @@
#include <time.h> // tzset() (POSIX), _tzset() (Windows)
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <chrono>
#include <locale>
#include <clocale>
#include <sstream>
#include <iomanip>
#include <system_error>
-#endif
-// Other includes.
+#include <libbutl/timestamp.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.timestamp;
-#else
-#include <libbutl/timestamp.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -189,15 +178,15 @@ main ()
assert (parse (".384902285 Feb 21 19:31:10 2016",
"%[.N] %b %d %H:%M:%S %Y",
- "." + ns (384902285) + " Feb 21 19:31:10 2016"));
+ '.' + ns (384902285) + " Feb 21 19:31:10 2016"));
assert (parse (".384902285 2016-02-21 19:31:10",
"%[.N] %Y-%m-%d %H:%M:%S",
- "." + ns (384902285) + " 2016-02-21 19:31:10"));
+ '.' + ns (384902285) + " 2016-02-21 19:31:10"));
assert (parse (".3849022852016-02-21 19:31:10",
"%[.N]%Y-%m-%d %H:%M:%S",
- "." + ns (384902285) + "2016-02-21 19:31:10"));
+ '.' + ns (384902285) + "2016-02-21 19:31:10"));
assert (parse ("Feb 1 2016", "%b %e %Y", "Feb 1 2016"));
assert (parse ("Feb 11 2016", "%b %e %Y", "Feb 11 2016"));
diff --git a/tests/url/driver.cxx b/tests/url/driver.cxx
index 95be244..869eed5 100644
--- a/tests/url/driver.cxx
+++ b/tests/url/driver.cxx
@@ -1,29 +1,17 @@
// file : tests/url/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
#include <utility> // move()
#include <iostream>
#include <iterator> // back_inserter
#include <stdexcept> // invalid_argument
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.url;
-import butl.utility; // operator<<(ostream, exception)
-#else
-#include <libbutl/url.mxx>
-#include <libbutl/utility.mxx>
-#endif
+
+#include <libbutl/url.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/utf8/driver.cxx b/tests/utf8/driver.cxx
index f35e65e..ccc2870 100644
--- a/tests/utf8/driver.cxx
+++ b/tests/utf8/driver.cxx
@@ -1,24 +1,13 @@
// file : tests/utf8/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <string>
-#endif
-
-// Other includes.
-
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-#endif
-import butl.utf8;
-import butl.utility;
-#else
-#include <libbutl/utf8.mxx>
-#include <libbutl/utility.mxx>
-#endif
+
+#include <libbutl/utf8.hxx>
+#include <libbutl/utility.hxx>
+
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
diff --git a/tests/uuid/driver.cxx b/tests/uuid/driver.cxx
index d8dae23..63e5bc7 100644
--- a/tests/uuid/driver.cxx
+++ b/tests/uuid/driver.cxx
@@ -5,13 +5,15 @@
# include <rpc.h> // GUID
#endif
-#include <cassert>
#include <sstream>
#include <iostream>
#include <libbutl/uuid.hxx>
#include <libbutl/uuid-io.hxx>
+#undef NDEBUG
+#include <cassert>
+
using namespace std;
using namespace butl;
diff --git a/tests/wildcard/driver.cxx b/tests/wildcard/driver.cxx
index 00a317a..fee2748 100644
--- a/tests/wildcard/driver.cxx
+++ b/tests/wildcard/driver.cxx
@@ -1,36 +1,24 @@
// file : tests/wildcard/driver.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
#include <map>
#include <string>
#include <vector>
-#include <algorithm> // sort()
-#include <exception>
#include <iostream>
-#endif
+#include <algorithm> // sort()
+#include <exception>
+#include <functional>
+#include <system_error>
-// Other includes.
+#include <libbutl/path.hxx>
+#include <libbutl/path-io.hxx>
+#include <libbutl/utility.hxx> // operator<<(ostream, exception)
+#include <libbutl/optional.hxx>
+#include <libbutl/filesystem.hxx>
+#include <libbutl/path-pattern.hxx>
-#ifdef __cpp_modules_ts
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-import butl.path;
-import butl.utility; // operator<<(ostream, exception)
-import butl.optional;
-import butl.filesystem;
-import butl.path_pattern;
-#else
-#include <libbutl/path.mxx>
-#include <libbutl/utility.mxx>
-#include <libbutl/optional.mxx>
-#include <libbutl/filesystem.mxx>
-#include <libbutl/path-pattern.mxx>
-#endif
+#undef NDEBUG
+#include <cassert>
using namespace std;
using namespace butl;
@@ -74,8 +62,13 @@ int _CRT_glob = 0;
// through contains only the specified entry. The start directory is used if
// the first pattern component is a self-matching wildcard.
//
+// -d (print|stop)
+// If a inaccessible/dangling link is encountered, then print its path to
+// stderr and, optionally, stop the search. Meaningful in combination with
+// -sd and must follow it, if specified in the command line.
+//
// -i
-// Pass psflags::ignorable_components to the match/search functions.
+// Pass path_match_flags::match_absent to the match/search functions.
// Meaningful in combination with -sd or -sp options and must follow it, if
// specified in the command line.
//
@@ -108,6 +101,9 @@ try
bool sort (true);
path_match_flags flags (path_match_flags::follow_symlinks);
+ bool dangle_stop (false);
+ function<bool (const dir_entry&)> dangle_func;
+
int i (2);
for (; i != argc; ++i)
{
@@ -116,6 +112,34 @@ try
sort = false;
else if (o == "-i")
flags |= path_match_flags::match_absent;
+ else if (o == "-d")
+ {
+ ++i;
+
+ assert (op == "-sd" && i != argc);
+
+ string v (argv[i]);
+
+ if (v == "print")
+ {
+ dangle_func = [] (const dir_entry& de)
+ {
+ cerr << de.base () / de.path () << endl;
+ return true;
+ };
+ }
+ else if (v == "stop")
+ {
+ dangle_func = [&dangle_stop] (const dir_entry& de)
+ {
+ cerr << de.base () / de.path () << endl;
+ dangle_stop = true;
+ return false;
+ };
+ }
+ else
+ assert (false);
+ }
else
break; // End of options.
}
@@ -181,10 +205,13 @@ try
};
if (!entry)
- path_search (pattern, add, start, flags);
+ path_search (pattern, add, start, flags, dangle_func);
else
path_search (pattern, *entry, add, start, flags);
+ if (dangle_stop)
+ return 1;
+
// It the search succeeds, then test search in the directory tree
// represented by each matched path. Otherwise, if the directory tree is
// specified, then make sure that it doesn't match the pattern.
@@ -245,8 +272,13 @@ catch (const invalid_path& e)
cerr << e << ": " << e.path << endl;
return 2;
}
+catch (const system_error& e)
+{
+ cerr << e << endl;
+ return 3;
+}
catch (const exception& e)
{
cerr << e << endl;
- return 2;
+ return 4;
}
diff --git a/tests/wildcard/testscript b/tests/wildcard/testscript
index 5f6a767..baa51aa 100644
--- a/tests/wildcard/testscript
+++ b/tests/wildcard/testscript
@@ -650,12 +650,14 @@
{
mkdir a;
touch --no-cleanup a/b;
- ^ln -s b a/l &a/l;
+ ln -s b a/l &a/l;
rm a/b;
touch a/c;
- $* a/* >/'a/c'
+ $* a/* 2>! == 3;
+ $* -d 'print' a/* >/'a/c' 2>/'a/l';
+ $* -d 'stop' a/* >! 2>/'a/l' == 1
}
}