From c09cd7512491cee1e82c1ad8128ce9fd4bc3f79b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 22 Sep 2017 23:32:28 +0200 Subject: Initial modularization with both Clang and VC hacks Note: gave up on VC about half way though. --- build/root.build | 5 + libbutl/base64.cxx | 26 +- libbutl/base64.hxx | 50 -- libbutl/base64.mxx | 63 ++ libbutl/buildfile | 18 +- libbutl/char-scanner.cxx | 28 +- libbutl/char-scanner.hxx | 127 ---- libbutl/char-scanner.mxx | 141 ++++ libbutl/const-ptr.hxx | 80 --- libbutl/const-ptr.mxx | 94 +++ libbutl/curl.cxx | 33 +- libbutl/curl.hxx | 174 ----- libbutl/curl.ixx | 5 +- libbutl/curl.mxx | 195 ++++++ libbutl/curl.txx | 5 +- libbutl/diagnostics.cxx | 37 +- libbutl/diagnostics.hxx | 265 -------- libbutl/diagnostics.mxx | 275 ++++++++ libbutl/export.hxx | 9 +- libbutl/fdstream.cxx | 35 +- libbutl/fdstream.hxx | 699 ------------------- libbutl/fdstream.ixx | 3 - libbutl/fdstream.mxx | 720 ++++++++++++++++++++ libbutl/filesystem.cxx | 47 +- libbutl/filesystem.hxx | 626 ----------------- libbutl/filesystem.ixx | 2 - libbutl/filesystem.mxx | 647 ++++++++++++++++++ libbutl/ft/exception.hxx | 7 +- libbutl/ft/shared_mutex.hxx | 7 +- libbutl/manifest-forward.hxx | 5 +- libbutl/manifest-parser.cxx | 30 +- libbutl/manifest-parser.hxx | 102 --- libbutl/manifest-parser.mxx | 116 ++++ libbutl/manifest-serializer.cxx | 31 +- libbutl/manifest-serializer.hxx | 82 --- libbutl/manifest-serializer.mxx | 95 +++ libbutl/multi-index.hxx | 59 -- libbutl/multi-index.mxx | 73 ++ libbutl/openssl.cxx | 28 +- libbutl/openssl.hxx | 164 ----- libbutl/openssl.ixx | 5 +- libbutl/openssl.mxx | 184 +++++ libbutl/openssl.txx | 4 +- libbutl/optional.hxx | 96 --- libbutl/optional.mxx | 113 +++ libbutl/pager.cxx | 51 +- libbutl/pager.hxx | 88 --- libbutl/pager.mxx | 103 +++ libbutl/path-io.hxx | 29 - libbutl/path-io.mxx | 45 ++ libbutl/path-map.hxx | 148 ---- libbutl/path-map.mxx | 164 +++++ libbutl/path.cxx | 45 +- libbutl/path.hxx | 1214 --------------------------------- libbutl/path.ixx | 28 +- libbutl/path.mxx | 1245 ++++++++++++++++++++++++++++++++++ libbutl/path.txx | 10 +- libbutl/prefix-map.hxx | 138 ---- libbutl/prefix-map.mxx | 152 +++++ libbutl/prefix-map.txx | 2 +- libbutl/process-details.hxx | 13 +- libbutl/process-io.hxx | 28 - libbutl/process-io.mxx | 44 ++ libbutl/process-run.cxx | 27 +- libbutl/process-run.txx | 5 +- libbutl/process.cxx | 74 +- libbutl/process.hxx | 649 ------------------ libbutl/process.ixx | 9 +- libbutl/process.mxx | 669 ++++++++++++++++++ libbutl/regex.cxx | 35 +- libbutl/regex.hxx | 76 --- libbutl/regex.ixx | 4 +- libbutl/regex.mxx | 98 +++ libbutl/regex.txx | 5 +- libbutl/sendmail.cxx | 27 +- libbutl/sendmail.hxx | 120 ---- libbutl/sendmail.ixx | 5 +- libbutl/sendmail.mxx | 138 ++++ libbutl/sha256.cxx | 69 +- libbutl/sha256.hxx | 135 ---- libbutl/sha256.mxx | 146 ++++ libbutl/small-vector.hxx | 297 -------- libbutl/small-vector.mxx | 310 +++++++++ libbutl/standard-version.cxx | 34 +- libbutl/standard-version.hxx | 267 -------- libbutl/standard-version.mxx | 281 ++++++++ libbutl/string-parser.cxx | 193 +++--- libbutl/string-parser.hxx | 56 -- libbutl/string-parser.mxx | 67 ++ libbutl/string-table.hxx | 98 --- libbutl/string-table.mxx | 114 ++++ libbutl/string-table.txx | 4 - libbutl/tab-parser.cxx | 35 +- libbutl/tab-parser.hxx | 72 -- libbutl/tab-parser.mxx | 85 +++ libbutl/target-triplet.cxx | 26 +- libbutl/target-triplet.hxx | 155 ----- libbutl/target-triplet.mxx | 169 +++++ libbutl/timestamp.cxx | 270 ++++---- libbutl/timestamp.hxx | 169 ----- libbutl/timestamp.mxx | 191 ++++++ libbutl/utility.cxx | 28 +- libbutl/utility.hxx | 264 ------- libbutl/utility.ixx | 8 - libbutl/utility.mxx | 285 ++++++++ libbutl/vector-view.hxx | 120 ---- libbutl/vector-view.mxx | 134 ++++ libbutl/version.hxx.in | 4 +- libbutl/win32-utility.cxx | 4 + libbutl/win32-utility.hxx | 9 +- tests/base64/buildfile | 6 + tests/base64/driver.cxx | 18 +- tests/build/root.build | 5 + tests/cpfile/buildfile | 6 + tests/cpfile/driver.cxx | 23 +- tests/curl/buildfile | 6 + tests/curl/driver.cxx | 31 +- tests/dir-iterator/buildfile | 6 + tests/dir-iterator/driver.cxx | 26 +- tests/fdstream/buildfile | 6 + tests/fdstream/driver.cxx | 32 +- tests/link/buildfile | 6 + tests/link/driver.cxx | 22 +- tests/manifest-parser/buildfile | 6 + tests/manifest-parser/driver.cxx | 19 +- tests/manifest-roundtrip/buildfile | 6 + tests/manifest-roundtrip/driver.cxx | 25 +- tests/manifest-serializer/buildfile | 6 + tests/manifest-serializer/driver.cxx | 19 +- tests/mventry/buildfile | 6 + tests/mventry/driver.cxx | 22 +- tests/openssl/buildfile | 6 + tests/openssl/driver.cxx | 28 +- tests/pager/buildfile | 6 + tests/pager/driver.cxx | 19 +- tests/path-entry/buildfile | 6 + tests/path-entry/driver.cxx | 21 +- tests/path/buildfile | 6 + tests/path/driver.cxx | 18 +- tests/prefix-map/buildfile | 6 + tests/prefix-map/driver.cxx | 19 +- tests/process-run/buildfile | 6 + tests/process-run/driver.cxx | 27 +- tests/process/buildfile | 6 + tests/process/driver.cxx | 27 +- tests/progress/buildfile | 6 + tests/progress/driver.cxx | 35 +- tests/regex/buildfile | 6 + tests/regex/driver.cxx | 20 +- tests/sendmail/buildfile | 6 + tests/sendmail/driver.cxx | 28 +- tests/sha256/buildfile | 6 + tests/sha256/driver.cxx | 19 +- tests/small-vector/buildfile | 6 + tests/small-vector/driver.cxx | 20 +- tests/standard-version/buildfile | 6 + tests/standard-version/driver.cxx | 22 +- tests/strcase/buildfile | 6 + tests/strcase/driver.cxx | 16 +- tests/string-parser/buildfile | 6 + tests/string-parser/driver.cxx | 28 +- tests/tab-parser/buildfile | 6 + tests/tab-parser/driver.cxx | 20 +- tests/target-triplet/buildfile | 6 + tests/target-triplet/driver.cxx | 18 +- tests/timestamp/buildfile | 6 + tests/timestamp/driver.cxx | 17 +- tests/wildcard/buildfile | 6 + tests/wildcard/driver.cxx | 28 +- 169 files changed, 8865 insertions(+), 7178 deletions(-) delete mode 100644 libbutl/base64.hxx create mode 100644 libbutl/base64.mxx delete mode 100644 libbutl/char-scanner.hxx create mode 100644 libbutl/char-scanner.mxx delete mode 100644 libbutl/const-ptr.hxx create mode 100644 libbutl/const-ptr.mxx delete mode 100644 libbutl/curl.hxx create mode 100644 libbutl/curl.mxx delete mode 100644 libbutl/diagnostics.hxx create mode 100644 libbutl/diagnostics.mxx delete mode 100644 libbutl/fdstream.hxx create mode 100644 libbutl/fdstream.mxx delete mode 100644 libbutl/filesystem.hxx create mode 100644 libbutl/filesystem.mxx delete mode 100644 libbutl/manifest-parser.hxx create mode 100644 libbutl/manifest-parser.mxx delete mode 100644 libbutl/manifest-serializer.hxx create mode 100644 libbutl/manifest-serializer.mxx delete mode 100644 libbutl/multi-index.hxx create mode 100644 libbutl/multi-index.mxx delete mode 100644 libbutl/openssl.hxx create mode 100644 libbutl/openssl.mxx delete mode 100644 libbutl/optional.hxx create mode 100644 libbutl/optional.mxx delete mode 100644 libbutl/pager.hxx create mode 100644 libbutl/pager.mxx delete mode 100644 libbutl/path-io.hxx create mode 100644 libbutl/path-io.mxx delete mode 100644 libbutl/path-map.hxx create mode 100644 libbutl/path-map.mxx delete mode 100644 libbutl/path.hxx create mode 100644 libbutl/path.mxx delete mode 100644 libbutl/prefix-map.hxx create mode 100644 libbutl/prefix-map.mxx delete mode 100644 libbutl/process-io.hxx create mode 100644 libbutl/process-io.mxx delete mode 100644 libbutl/process.hxx create mode 100644 libbutl/process.mxx delete mode 100644 libbutl/regex.hxx create mode 100644 libbutl/regex.mxx delete mode 100644 libbutl/sendmail.hxx create mode 100644 libbutl/sendmail.mxx delete mode 100644 libbutl/sha256.hxx create mode 100644 libbutl/sha256.mxx delete mode 100644 libbutl/small-vector.hxx create mode 100644 libbutl/small-vector.mxx delete mode 100644 libbutl/standard-version.hxx create mode 100644 libbutl/standard-version.mxx delete mode 100644 libbutl/string-parser.hxx create mode 100644 libbutl/string-parser.mxx delete mode 100644 libbutl/string-table.hxx create mode 100644 libbutl/string-table.mxx delete mode 100644 libbutl/tab-parser.hxx create mode 100644 libbutl/tab-parser.mxx delete mode 100644 libbutl/target-triplet.hxx create mode 100644 libbutl/target-triplet.mxx delete mode 100644 libbutl/timestamp.hxx create mode 100644 libbutl/timestamp.mxx delete mode 100644 libbutl/utility.hxx create mode 100644 libbutl/utility.mxx delete mode 100644 libbutl/vector-view.hxx create mode 100644 libbutl/vector-view.mxx diff --git a/build/root.build b/build/root.build index 84c236f..5129a40 100644 --- a/build/root.build +++ b/build/root.build @@ -2,6 +2,11 @@ # copyright : Copyright (c) 2014-2017 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file +using cxx.guess + +if ($force_modules != true && $cxx.id == 'clang') + cxx.features.modules = false + cxx.std = experimental using cxx diff --git a/libbutl/base64.cxx b/libbutl/base64.cxx index 580090c..a8f7757 100644 --- a/libbutl/base64.cxx +++ b/libbutl/base64.cxx @@ -2,13 +2,37 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include #include // size_t #include #include #include // {istreambuf, ostreambuf, back_insert}_iterator #include // invalid_argument +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.base64; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +#endif using namespace std; diff --git a/libbutl/base64.hxx b/libbutl/base64.hxx deleted file mode 100644 index 748dcfa..0000000 --- a/libbutl/base64.hxx +++ /dev/null @@ -1,50 +0,0 @@ -// file : libbutl/base64.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_BASE64_HXX -#define LIBBUTL_BASE64_HXX - -#include -#include -#include - -#include - -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 - // badbit, failbit, or eofbit set and throw invalid_argument if that's the - // case. Otherwise, set eofbit on completion. If writing to a stream, check - // if it has badbit, failbit, or eofbit set and throw invalid_argument if - // that's the case. Otherwise set badbit if the write operation fails. - // - LIBBUTL_SYMEXPORT void - base64_encode (std::ostream&, std::istream&); - - LIBBUTL_SYMEXPORT std::string - base64_encode (std::istream&); - - LIBBUTL_SYMEXPORT std::string - base64_encode (const std::vector&); - - // 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 - // that's the case. Otherwise, set eofbit on completion. If writing to a - // stream, check if it has badbit, failbit, or eofbit set and throw - // invalid_argument if that's the case. Otherwise set badbit if the write - // operation fails. - // - LIBBUTL_SYMEXPORT void - base64_decode (std::ostream&, std::istream&); - - LIBBUTL_SYMEXPORT void - base64_decode (std::ostream&, const std::string&); - - LIBBUTL_SYMEXPORT std::vector - base64_decode (const std::string&); -} - -#endif // LIBBUTL_BASE64_HXX diff --git a/libbutl/base64.mxx b/libbutl/base64.mxx new file mode 100644 index 0000000..ae43ba6 --- /dev/null +++ b/libbutl/base64.mxx @@ -0,0 +1,63 @@ +// file : libbutl/base64.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.base64; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT 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 + // badbit, failbit, or eofbit set and throw invalid_argument if that's the + // case. Otherwise, set eofbit on completion. If writing to a stream, check + // if it has badbit, failbit, or eofbit set and throw invalid_argument if + // that's the case. Otherwise set badbit if the write operation fails. + // + LIBBUTL_SYMEXPORT void + base64_encode (std::ostream&, std::istream&); + + LIBBUTL_SYMEXPORT std::string + base64_encode (std::istream&); + + LIBBUTL_SYMEXPORT std::string + base64_encode (const std::vector&); + + // 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 + // that's the case. Otherwise, set eofbit on completion. If writing to a + // stream, check if it has badbit, failbit, or eofbit set and throw + // invalid_argument if that's the case. Otherwise set badbit if the write + // operation fails. + // + LIBBUTL_SYMEXPORT void + base64_decode (std::ostream&, std::istream&); + + LIBBUTL_SYMEXPORT void + base64_decode (std::ostream&, const std::string&); + + LIBBUTL_SYMEXPORT std::vector + base64_decode (const std::string&); +} diff --git a/libbutl/buildfile b/libbutl/buildfile index fe2e606..6086fd7 100644 --- a/libbutl/buildfile +++ b/libbutl/buildfile @@ -2,7 +2,16 @@ # copyright : Copyright (c) 2014-2017 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -lib{butl}: {hxx ixx txx cxx}{** -win32-utility -version} {hxx}{version} +int_libs = + +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import int_libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + +lib{butl}: {mxx hxx ixx txx cxx}{** -win32-utility -version} {hxx}{version} \ + $int_libs # Exclude these from compilation on non-Windows targets. # @@ -33,7 +42,7 @@ if $version.pre_release else lib{butl}: bin.lib.version = @"-$version.major.$version.minor" -bmi{*}: cxx.poptions += -DLIBBUTL_MODULE_BUILD +lib{butl}: cxx.export.libs = $int_libs cxx.poptions =+ "-I$out_root" "-I$src_root" obja{*} bmia{*}: cxx.poptions += -DLIBBUTL_STATIC_BUILD @@ -53,6 +62,11 @@ if ($cxx.target.class == "windows") else cxx.libs += -lpthread +#@@ MOD bogus warning if module and dll-exported function called within DLL. +# +if ($cxx.features.modules && $cxx.id == 'msvc') + cxx.loptions += /ignore:4217 + # Install into the libbutl/ subdirectory of, say, /usr/include/ recreating # subdirectories. # diff --git a/libbutl/char-scanner.cxx b/libbutl/char-scanner.cxx index a97ed48..2e680b6 100644 --- a/libbutl/char-scanner.cxx +++ b/libbutl/char-scanner.cxx @@ -2,7 +2,33 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include // char_traits +#include // uint64_t +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.char_scanner; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.fdstream; +#endif + +#endif using namespace std; diff --git a/libbutl/char-scanner.hxx b/libbutl/char-scanner.hxx deleted file mode 100644 index 3aa1e0d..0000000 --- a/libbutl/char-scanner.hxx +++ /dev/null @@ -1,127 +0,0 @@ -// file : libbutl/char-scanner.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_CHAR_SCANNER_HXX -#define LIBBUTL_CHAR_SCANNER_HXX - -#include // char_traits -#include // uint64_t -#include - -#include - -#include - -namespace butl -{ - // Low-level character stream scanner. Normally used as a base for - // higher-level lexers. - // - class LIBBUTL_SYMEXPORT char_scanner - { - public: - // If the crlf argument is true, then recognize Windows newlines (0x0D - // 0x0A) and convert them to just '\n' (0x0A). Note that a standalone - // 0x0D is treated "as if" it was followed by 0x0A. - // - // Note also that if the stream happens to be ifdstream, then it includes - // a number of optimizations that assume nobody else is messing with the - // stream. - // - char_scanner (std::istream& is, bool crlf = true); - - char_scanner (const char_scanner&) = delete; - char_scanner& operator= (const char_scanner&) = delete; - - // Scanner interface. - // - public: - - // Extended character. It includes line/column information and is capable - // of representing EOF. - // - // Note that implicit conversion of EOF to char_type results in NUL - // character (which means in most cases it is safe to compare xchar to - // char without checking for EOF). - // - class xchar - { - public: - using traits_type = std::char_traits; - using int_type = traits_type::int_type; - using char_type = traits_type::char_type; - - int_type value; - std::uint64_t line; - std::uint64_t column; - - operator char_type () const - { - return value != traits_type::eof () - ? static_cast (value) - : char_type (0); - } - - xchar (int_type v, std::uint64_t l = 0, std::uint64_t c = 0) - : value (v), line (l), column (c) {} - }; - - xchar - get (); - - void - get (const xchar& peeked); // Get previously peeked character (faster). - - void - unget (const xchar&); - - // Note that if there is an "ungot" character, peek() will return - // that. - // - xchar - peek (); - - // Tests. In the future we can add tests line alpha(), alnum(), - // etc. - // - static bool - eos (const xchar& c) {return c.value == xchar::traits_type::eof ();} - - // Line and column of the next character to be extracted from the stream - // by peek() or get(). - // - std::uint64_t line = 1; - std::uint64_t column = 1; - - protected: - using int_type = xchar::int_type; - using char_type = xchar::char_type; - - int_type - peek_ (); - - void - get_ (); - - protected: - std::istream& is_; - - fdbuf* buf_; // NULL if not ifdstream. - const char_type* gptr_; - const char_type* egptr_; - - bool crlf_; - bool eos_ = false; - - bool unget_ = false; - bool unpeek_ = false; - - xchar ungetc_ = '\0'; - xchar unpeekc_ = '\0'; - }; -} - -#include - -#endif // LIBBUTL_CHAR_SCANNER_HXX diff --git a/libbutl/char-scanner.mxx b/libbutl/char-scanner.mxx new file mode 100644 index 0000000..af4dad9 --- /dev/null +++ b/libbutl/char-scanner.mxx @@ -0,0 +1,141 @@ +// file : libbutl/char-scanner.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include // char_traits +#include // uint64_t +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.char_scanner; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.fdstream; +#else +#include +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // Low-level character stream scanner. Normally used as a base for + // higher-level lexers. + // + class LIBBUTL_SYMEXPORT char_scanner + { + public: + // If the crlf argument is true, then recognize Windows newlines (0x0D + // 0x0A) and convert them to just '\n' (0x0A). Note that a standalone + // 0x0D is treated "as if" it was followed by 0x0A. + // + // Note also that if the stream happens to be ifdstream, then it includes + // a number of optimizations that assume nobody else is messing with the + // stream. + // + char_scanner (std::istream& is, bool crlf = true); + + char_scanner (const char_scanner&) = delete; + char_scanner& operator= (const char_scanner&) = delete; + + // Scanner interface. + // + public: + + // Extended character. It includes line/column information and is capable + // of representing EOF. + // + // Note that implicit conversion of EOF to char_type results in NUL + // character (which means in most cases it is safe to compare xchar to + // char without checking for EOF). + // + class xchar + { + public: + using traits_type = std::char_traits; + using int_type = traits_type::int_type; + using char_type = traits_type::char_type; + + int_type value; + std::uint64_t line; + std::uint64_t column; + + operator char_type () const + { + return value != traits_type::eof () + ? static_cast (value) + : char_type (0); + } + + xchar (int_type v, std::uint64_t l = 0, std::uint64_t c = 0) + : value (v), line (l), column (c) {} + }; + + xchar + get (); + + void + get (const xchar& peeked); // Get previously peeked character (faster). + + void + unget (const xchar&); + + // Note that if there is an "ungot" character, peek() will return + // that. + // + xchar + peek (); + + // Tests. In the future we can add tests line alpha(), alnum(), + // etc. + // + static bool + eos (const xchar& c) {return c.value == xchar::traits_type::eof ();} + + // Line and column of the next character to be extracted from the stream + // by peek() or get(). + // + std::uint64_t line = 1; + std::uint64_t column = 1; + + protected: + using int_type = xchar::int_type; + using char_type = xchar::char_type; + + int_type + peek_ (); + + void + get_ (); + + protected: + std::istream& is_; + + fdbuf* buf_; // NULL if not ifdstream. + const char_type* gptr_; + const char_type* egptr_; + + bool crlf_; + bool eos_ = false; + + bool unget_ = false; + bool unpeek_ = false; + + xchar ungetc_ = '\0'; + xchar unpeekc_ = '\0'; + }; +} + +#include diff --git a/libbutl/const-ptr.hxx b/libbutl/const-ptr.hxx deleted file mode 100644 index e50704d..0000000 --- a/libbutl/const-ptr.hxx +++ /dev/null @@ -1,80 +0,0 @@ -// file : libbutl/const-ptr.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_CONST_PTR_HXX -#define LIBBUTL_CONST_PTR_HXX - -#include // nullptr_t - -namespace butl -{ - // Const-propagating pointer. - // - // It has the semantics of a raw pointer except that it passes on its own - // const-ness to the pointed-to object. In other words, if you have a const - // instance of this pointer, then you can only obtain a const raw pointer to - // the underlying object. It is normally used as a data member, for example: - // - // struct tree - // { - // const_ptr left; - // const_ptr right; - // - // void modify (); - // }; - // - // tree* x = ...; - // const tree* y = ...; - // - // x.left->modify (); // Ok. - // y.left->modify (); // Error. - // - // Note that due to this semantics, copy construction/assignment requires - // a non-const instance of const_ptr. - // - // Note that this type is standard layout (which means we can reinterpret - // it as a raw pointer). - // - // Known drawbacks/issues: - // - // 1. Cannot do static_cast (x.left). - // - template - class const_ptr - { - public: - const_ptr () = default; - explicit const_ptr (T* p): p_ (p) {} - const_ptr (std::nullptr_t): p_ (nullptr) {} - - const_ptr& operator= (T* p) {p_ = p; return *this;} - const_ptr& operator= (std::nullptr_t) {p_ = nullptr; return *this;} - - template explicit const_ptr (T1* p): p_ (p) {} - template const_ptr (const_ptr& p): p_ (p.p_) {} - - template const_ptr& operator= (T1* p) {p_ = p; return *this;} - template const_ptr& operator= (const_ptr& p) { - p_ = p.p_; return *this;} - - T* operator-> () {return p_;} - const T* operator-> () const {return p_;} - - T& operator* () {return *p_;} - const T& operator* () const {return *p_;} - - operator T* () {return p_;} - operator const T* () const {return p_;} - - explicit operator bool () const {return p_ != nullptr;} - - T* get () {return p_;} - const T* get () const {return p_;} - - private: - T* p_; - }; -} - -#endif // LIBBUTL_CONST_PTR_HXX diff --git a/libbutl/const-ptr.mxx b/libbutl/const-ptr.mxx new file mode 100644 index 0000000..a4b86be --- /dev/null +++ b/libbutl/const-ptr.mxx @@ -0,0 +1,94 @@ +// file : libbutl/const-ptr.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include // nullptr_t +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.const_ptr; +#ifdef __cpp_lib_modules +import std.core; // @@ MOD std.fundamental. +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // Const-propagating pointer. + // + // It has the semantics of a raw pointer except that it passes on its own + // const-ness to the pointed-to object. In other words, if you have a const + // instance of this pointer, then you can only obtain a const raw pointer to + // the underlying object. It is normally used as a data member, for example: + // + // struct tree + // { + // const_ptr left; + // const_ptr right; + // + // void modify (); + // }; + // + // tree* x = ...; + // const tree* y = ...; + // + // x.left->modify (); // Ok. + // y.left->modify (); // Error. + // + // Note that due to this semantics, copy construction/assignment requires + // a non-const instance of const_ptr. + // + // Note that this type is standard layout (which means we can reinterpret + // it as a raw pointer). + // + // Known drawbacks/issues: + // + // 1. Cannot do static_cast (x.left). + // + template + class const_ptr + { + public: + const_ptr () = default; + explicit const_ptr (T* p): p_ (p) {} + const_ptr (std::nullptr_t): p_ (nullptr) {} + + const_ptr& operator= (T* p) {p_ = p; return *this;} + const_ptr& operator= (std::nullptr_t) {p_ = nullptr; return *this;} + + template explicit const_ptr (T1* p): p_ (p) {} + template const_ptr (const_ptr& p): p_ (p.p_) {} + + template const_ptr& operator= (T1* p) {p_ = p; return *this;} + template const_ptr& operator= (const_ptr& p) { + p_ = p.p_; return *this;} + + T* operator-> () {return p_;} + const T* operator-> () const {return p_;} + + T& operator* () {return *p_;} + const T& operator* () const {return *p_;} + + operator T* () {return p_;} + operator const T* () const {return p_;} + + explicit operator bool () const {return p_ != nullptr;} + + T* get () {return p_;} + const T* get () const {return p_;} + + private: + T* p_; + }; +} diff --git a/libbutl/curl.cxx b/libbutl/curl.cxx index a04d52e..5ff81ab 100644 --- a/libbutl/curl.cxx +++ b/libbutl/curl.cxx @@ -2,12 +2,39 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include #include // move() #include // invalid_argument - -#include // casecmp() +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.curl; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +#endif +import butl.path; +import butl.process; +import butl.fdstream; +import butl.small_vector; +#endif + +import butl.utility; // casecmp() +#else +#include +#endif using namespace std; diff --git a/libbutl/curl.hxx b/libbutl/curl.hxx deleted file mode 100644 index dad0bf5..0000000 --- a/libbutl/curl.hxx +++ /dev/null @@ -1,174 +0,0 @@ -// file : libbutl/curl.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_CURL_HXX -#define LIBBUTL_CURL_HXX - -#include -#include - -#include - -#include -#include -#include - -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 - // of errors. - // - // The I (in) and O (out) can be of the following types/values: - // - // nullfd Signal that no input/output is expected. - // - // path Read input/write output from/to a file. If the special "-" - // value is used, then instead input is connected to the curl::out - // ofdstream member and output -- to the curl::in ifdstream member. - // Note that the argument type should be path, not string (i.e., - // pass path("-")). - // - // other Forwarded as is to process_start(). Normally either int or - // auto_fd. - // - // For example: - // - // curl (nullfd, // No input expected for GET. - // path ("-"), // Write response to curl::in. - // 2, - // curl::get, - // "http://example.org"); - // - // curl (path ("-"), // Read request from curl::out. - // path::temp_path (), // Write result to a file. - // 2, - // curl::post, - // "http://example.org"); - // - // curl (nullfd, - // fdnull (), // Write result to /dev/null. - // 2, - // curl::get, - // "tftp://localhost/foo"); - // - // Typical usage: - // - // try - // { - // curl c (nullfd, // No input expected. - // path ("-"), // Output to curl::in. - // 2, // Diagnostics to stderr. - // curl::get, // GET method. - // "https://example.org", - // "-A", "foobot/1.2.3"); // Additional curl(1) options. - // - // for (string s; getline (c.in, s); ) - // cout << s << endl; - // - // c.in.close (); - // - // if (!c.wait ()) - // ... // curl returned non-zero status. - // } - // catch (const std::system_error& e) - // { - // cerr << "curl error: " << e << endl; - // } - // - // Notes: - // - // 1. If opened, in/out streams are in the binary mode. - // - // 2. If opened, in/out must be explicitly closed before calling wait(). - // - // 3. Only binary data HTTP POST is currently supported (the --data-binary - // curl option). - // - class LIBBUTL_SYMEXPORT curl: public process - { - public: - enum method_type {get, put, post}; - - ifdstream in; - ofdstream out; - - template - curl (I&& in, - O&& out, - E&& err, - method_type, - const std::string& url, - A&&... options); - - // Version with the command line callback (see process_run_callback() for - // details). - // - template - curl (const C&, - I&& in, - O&& out, - E&& err, - method_type, - const std::string& url, - A&&... options); - - private: - enum method_proto {ftp_get, ftp_put, http_get, http_post}; - using method_proto_options = small_vector; - - method_proto - translate (method_type, const std::string& url, method_proto_options&); - - private: - template - struct is_other - { - using type = typename std::remove_reference< - typename std::remove_cv::type>::type; - - static const bool value = !(std::is_same::value || - std::is_same::value); - }; - - struct io_data - { - fdpipe pipe; - method_proto_options options; - std::string storage; - }; - - int - map_in (nullfd_t, method_proto, io_data&); - - int - map_in (const path&, method_proto, io_data&); - - template - typename std::enable_if::value, I>::type - map_in (I&&, method_proto, io_data&); - - int - map_out (nullfd_t, method_proto, io_data&); - - int - map_out (const path&, method_proto, io_data&); - - template - typename std::enable_if::value, O>::type - map_out (O&&, method_proto, io_data&); - }; -} - -#include -#include - -#endif // LIBBUTL_CURL_HXX diff --git a/libbutl/curl.ixx b/libbutl/curl.ixx index 204dfa1..0ad3bf5 100644 --- a/libbutl/curl.ixx +++ b/libbutl/curl.ixx @@ -2,10 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // size_t -#include // forward() - -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { template +#include + +#include // size_t +#include // forward() +#include // invalid_argument +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.curl; +#ifdef __cpp_lib_modules +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 +#include +#include +#include +#endif + +#include + +LIBBUTL_MODEXPORT 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 + // of errors. + // + // The I (in) and O (out) can be of the following types/values: + // + // nullfd Signal that no input/output is expected. + // + // path Read input/write output from/to a file. If the special "-" + // value is used, then instead input is connected to the curl::out + // ofdstream member and output -- to the curl::in ifdstream member. + // Note that the argument type should be path, not string (i.e., + // pass path("-")). + // + // other Forwarded as is to process_start(). Normally either int or + // auto_fd. + // + // For example: + // + // curl (nullfd, // No input expected for GET. + // path ("-"), // Write response to curl::in. + // 2, + // curl::get, + // "http://example.org"); + // + // curl (path ("-"), // Read request from curl::out. + // path::temp_path (), // Write result to a file. + // 2, + // curl::post, + // "http://example.org"); + // + // curl (nullfd, + // fdnull (), // Write result to /dev/null. + // 2, + // curl::get, + // "tftp://localhost/foo"); + // + // Typical usage: + // + // try + // { + // curl c (nullfd, // No input expected. + // path ("-"), // Output to curl::in. + // 2, // Diagnostics to stderr. + // curl::get, // GET method. + // "https://example.org", + // "-A", "foobot/1.2.3"); // Additional curl(1) options. + // + // for (string s; getline (c.in, s); ) + // cout << s << endl; + // + // c.in.close (); + // + // if (!c.wait ()) + // ... // curl returned non-zero status. + // } + // catch (const std::system_error& e) + // { + // cerr << "curl error: " << e << endl; + // } + // + // Notes: + // + // 1. If opened, in/out streams are in the binary mode. + // + // 2. If opened, in/out must be explicitly closed before calling wait(). + // + // 3. Only binary data HTTP POST is currently supported (the --data-binary + // curl option). + // + class LIBBUTL_SYMEXPORT curl: public process + { + public: + enum method_type {get, put, post}; + + ifdstream in; + ofdstream out; + + template + curl (I&& in, + O&& out, + E&& err, + method_type, + const std::string& url, + A&&... options); + + // Version with the command line callback (see process_run_callback() for + // details). + // + template + curl (const C&, + I&& in, + O&& out, + E&& err, + method_type, + const std::string& url, + A&&... options); + + private: + enum method_proto {ftp_get, ftp_put, http_get, http_post}; + using method_proto_options = small_vector; + + method_proto + translate (method_type, const std::string& url, method_proto_options&); + + private: + template + struct is_other + { + using type = typename std::remove_reference< + typename std::remove_cv::type>::type; + + static const bool value = !(std::is_same::value || + std::is_same::value); + }; + + struct io_data + { + fdpipe pipe; + method_proto_options options; + std::string storage; + }; + + int + map_in (nullfd_t, method_proto, io_data&); + + int + map_in (const path&, method_proto, io_data&); + + template + typename std::enable_if::value, I>::type + map_in (I&&, method_proto, io_data&); + + int + map_out (nullfd_t, method_proto, io_data&); + + int + map_out (const path&, method_proto, io_data&); + + template + typename std::enable_if::value, O>::type + map_out (O&&, method_proto, io_data&); + }; +} + +#include +#include diff --git a/libbutl/curl.txx b/libbutl/curl.txx index 0c5fb2f..9686d0b 100644 --- a/libbutl/curl.txx +++ b/libbutl/curl.txx @@ -2,10 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // forward() -#include // invalid_argument - -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { template typename std::enable_if::value, I>::type curl:: diff --git a/libbutl/diagnostics.cxx b/libbutl/diagnostics.cxx index 33d8e3a..6189775 100644 --- a/libbutl/diagnostics.cxx +++ b/libbutl/diagnostics.cxx @@ -2,25 +2,52 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif #ifndef _WIN32 # include // write() #else # include - # include //_write() #endif +#include + +#ifndef __cpp_lib_modules +#include +#include + #include // ios::failure #include #include -#include #include // size_t #include // cerr +#endif -#include -#include // stderr_fd(), fdterm() +// Other includes. + +#ifdef __cpp_modules +module butl.diagnostics; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +//@@ MOD TODO: std.threading +import butl.utility; +import butl.optional; +import butl.fdstream; // stderr_fd(), fdterm() +#else +#include +#include +#include +#endif using namespace std; diff --git a/libbutl/diagnostics.hxx b/libbutl/diagnostics.hxx deleted file mode 100644 index c1491d6..0000000 --- a/libbutl/diagnostics.hxx +++ /dev/null @@ -1,265 +0,0 @@ -// file : libbutl/diagnostics.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_DIAGNOSTICS_HXX -#define LIBBUTL_DIAGNOSTICS_HXX - -#include -#include -#include -#include // move(), forward() -#include // uncaught_exceptions -#include // uncaught_exception(s)() - -#include -#include - -namespace butl -{ - // Diagnostic facility base infrastructure. - // - - // Diagnostics destination stream (std::cerr by default). Note that its - // modification is not MT-safe. Also note that concurrent writing to the - // stream from multiple threads can result in interleaved characters. To - // prevent this an object of diag_stream_lock type (see below) must be - // created prior to write operation. - // - 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). - // - struct LIBBUTL_SYMEXPORT diag_stream_lock - { - diag_stream_lock (); - ~diag_stream_lock (); - - // Support for one-liners, for example: - // - // diag_stream_lock () << "Hello, World!" << endl; - // - template - std::ostream& - operator<< (const T& x) const - { - return *diag_stream << x; - } - }; - - // Progress line facility. - // - // The idea is to keep a progress line at the bottom of the terminal with - // other output scrolling above it. For a non-terminal STDERR the progress - // line is printed as a regular one terminated with the newline character. - // The printing of the progress line is integrated into diag_stream_lock and - // diag_progress_lock. To print or update the progress acquire - // diag_progress_lock and update the diag_progress string. To remove the - // progress line, set this string to empty. For better readability start the - // progress line with a space (which is where the cursor will be parked). - // Should only be used if diag_stream points to std::cerr. - // - // Note that child processes writing to the same stream may not completely - // overwrite the progress line so in this case it makes sense to keep the - // line as short as possible. - // - // To restore the progress line being overwritten by an independent writer - // (such as a child process), create and destroy the diag_progress_lock. - // - LIBBUTL_SYMEXPORT extern std::string diag_progress; - - struct LIBBUTL_SYMEXPORT diag_progress_lock - { - diag_progress_lock (); - ~diag_progress_lock (); - }; - - // - // - struct diag_record; - template struct diag_prologue; - template struct diag_mark; - - using diag_epilogue = void (const diag_record&); - - struct LIBBUTL_SYMEXPORT diag_record - { - template - const diag_record& - operator<< (const T& x) const - { - os << x; - return *this; - } - - diag_record () - : -#ifdef __cpp_lib_uncaught_exceptions - uncaught_ (std::uncaught_exceptions ()), -#endif - empty_ (true), - epilogue_ (nullptr) {} - - template - explicit - diag_record (const diag_prologue& p): diag_record () { *this << p;} - - template - explicit - diag_record (const diag_mark& m): diag_record () { *this << m;} - - ~diag_record () noexcept (false); - - bool - empty () const {return empty_;} - - bool - full () const {return !empty_;} - - void - flush () const; - - void - append (const char* indent, diag_epilogue* e) const - { - // Ignore subsequent epilogues (e.g., from nested marks, etc). - // - if (empty_) - { - epilogue_ = e; - empty_ = false; - } - else if (indent != nullptr) - os << indent; - } - - // Move constructible-only type. - // - // Older versions of libstdc++ don't have the ostringstream move support - // and accuratly detecting its version is non-trivial. So we always use - // the pessimized implementation with libstdc++. Luckily, GCC doesn't seem - // to be needing move due to copy/move elision. - // -#ifdef __GLIBCXX__ - diag_record (diag_record&&); -#else - diag_record (diag_record&& r) - : -#ifdef __cpp_lib_uncaught_exceptions - uncaught_ (r.uncaught_), -#endif - empty_ (r.empty_), - epilogue_ (r.epilogue_), - os (std::move (r.os)) - { - if (!empty_) - { - r.empty_ = true; - r.epilogue_ = nullptr; - } - } -#endif - - diag_record& operator= (diag_record&&) = delete; - - diag_record (const diag_record&) = delete; - diag_record& operator= (const diag_record&) = delete; - - protected: -#ifdef __cpp_lib_uncaught_exceptions - const int uncaught_; -#endif - mutable bool empty_; - mutable diag_epilogue* epilogue_; - - public: - mutable std::ostringstream os; - }; - - template - struct diag_prologue: B - { - const char* indent; - diag_epilogue* epilogue; - - diag_prologue (const char* i = "\n ", diag_epilogue* e = nullptr) - : B (), indent (i), epilogue (e) {} - - template - diag_prologue (A&&... a) - : B (std::forward (a)...), indent ("\n "), epilogue (nullptr) {} - - template - diag_prologue (diag_epilogue* e, A&&... a) - : B (std::forward (a)...), indent ("\n "), epilogue (e) {} - - template - diag_prologue (const char* i, diag_epilogue* e, A&&... a) - : B (std::forward (a)...), indent (i), epilogue (e) {} - - template - diag_record - operator<< (const T& x) const - { - diag_record r; - r.append (indent, epilogue); - B::operator() (r); - r << x; - return r; - } - - friend const diag_record& - operator<< (const diag_record& r, const diag_prologue& p) - { - r.append (p.indent, p.epilogue); - p (r); - return r; - } - }; - - template - struct diag_mark: B - { - diag_mark (): B () {} - - template - diag_mark (A&&... a): B (std::forward (a)...) {} - - template - diag_record - operator<< (const T& x) const - { - return B::operator() () << x; - } - - friend const diag_record& - operator<< (const diag_record& r, const diag_mark& m) - { - return r << m (); - } - }; - - template - struct diag_noreturn_end: B - { - diag_noreturn_end (): B () {} - - template - diag_noreturn_end (A&&... a): B (std::forward (a)...) {} - - [[noreturn]] friend void - operator<< (const diag_record& r, const diag_noreturn_end& e) - { - // We said that we never return which means this end mark cannot be used - // to "maybe not return". And not returning without any diagnostics is - // probably a mistake. - // - assert (r.full ()); - e.B::operator() (r); - } - }; -} - -#endif // LIBBUTL_DIAGNOSTICS_HXX diff --git a/libbutl/diagnostics.mxx b/libbutl/diagnostics.mxx new file mode 100644 index 0000000..af74956 --- /dev/null +++ b/libbutl/diagnostics.mxx @@ -0,0 +1,275 @@ +// file : libbutl/diagnostics.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +#include + +#ifndef __cpp_lib_modules +#include +#include +#include // move(), forward() +#include // uncaught_exception[s]() +#endif + +#include // uncaught_exceptions + +#ifdef __cpp_modules +export module butl.diagnostics; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // Diagnostic facility base infrastructure. + // + + // Diagnostics destination stream (std::cerr by default). Note that its + // modification is not MT-safe. Also note that concurrent writing to the + // stream from multiple threads can result in interleaved characters. To + // prevent this an object of diag_stream_lock type (see below) must be + // created prior to write operation. + // + 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). + // + struct LIBBUTL_SYMEXPORT diag_stream_lock + { + diag_stream_lock (); + ~diag_stream_lock (); + + // Support for one-liners, for example: + // + // diag_stream_lock () << "Hello, World!" << endl; + // + template + std::ostream& + operator<< (const T& x) const + { + return *diag_stream << x; + } + }; + + // Progress line facility. + // + // The idea is to keep a progress line at the bottom of the terminal with + // other output scrolling above it. For a non-terminal STDERR the progress + // line is printed as a regular one terminated with the newline character. + // The printing of the progress line is integrated into diag_stream_lock and + // diag_progress_lock. To print or update the progress acquire + // diag_progress_lock and update the diag_progress string. To remove the + // progress line, set this string to empty. For better readability start the + // progress line with a space (which is where the cursor will be parked). + // Should only be used if diag_stream points to std::cerr. + // + // Note that child processes writing to the same stream may not completely + // overwrite the progress line so in this case it makes sense to keep the + // line as short as possible. + // + // To restore the progress line being overwritten by an independent writer + // (such as a child process), create and destroy the diag_progress_lock. + // + LIBBUTL_SYMEXPORT extern std::string diag_progress; + + struct LIBBUTL_SYMEXPORT diag_progress_lock + { + diag_progress_lock (); + ~diag_progress_lock (); + }; + + // + // + struct diag_record; + template struct diag_prologue; + template struct diag_mark; + + using diag_epilogue = void (const diag_record&); + + struct LIBBUTL_SYMEXPORT diag_record + { + template + const diag_record& + operator<< (const T& x) const + { + os << x; + return *this; + } + + diag_record () + : +#ifdef __cpp_lib_uncaught_exceptions + uncaught_ (std::uncaught_exceptions ()), +#endif + empty_ (true), + epilogue_ (nullptr) {} + + template + explicit + diag_record (const diag_prologue& p): diag_record () { *this << p;} + + template + explicit + diag_record (const diag_mark& m): diag_record () { *this << m;} + + ~diag_record () noexcept (false); + + bool + empty () const {return empty_;} + + bool + full () const {return !empty_;} + + void + flush () const; + + void + append (const char* indent, diag_epilogue* e) const + { + // Ignore subsequent epilogues (e.g., from nested marks, etc). + // + if (empty_) + { + epilogue_ = e; + empty_ = false; + } + else if (indent != nullptr) + os << indent; + } + + // Move constructible-only type. + // + // Older versions of libstdc++ don't have the ostringstream move support + // and accuratly detecting its version is non-trivial. So we always use + // the pessimized implementation with libstdc++. Luckily, GCC doesn't seem + // to be needing move due to copy/move elision. + // +#ifdef __GLIBCXX__ + diag_record (diag_record&&); +#else + diag_record (diag_record&& r) + : +#ifdef __cpp_lib_uncaught_exceptions + uncaught_ (r.uncaught_), +#endif + empty_ (r.empty_), + epilogue_ (r.epilogue_), + os (std::move (r.os)) + { + if (!empty_) + { + r.empty_ = true; + r.epilogue_ = nullptr; + } + } +#endif + + diag_record& operator= (diag_record&&) = delete; + + diag_record (const diag_record&) = delete; + diag_record& operator= (const diag_record&) = delete; + + protected: +#ifdef __cpp_lib_uncaught_exceptions + const int uncaught_; +#endif + mutable bool empty_; + mutable diag_epilogue* epilogue_; + + public: + mutable std::ostringstream os; + }; + + template + struct diag_prologue: B + { + const char* indent; + diag_epilogue* epilogue; + + diag_prologue (const char* i = "\n ", diag_epilogue* e = nullptr) + : B (), indent (i), epilogue (e) {} + + template + diag_prologue (A&&... a) + : B (std::forward (a)...), indent ("\n "), epilogue (nullptr) {} + + template + diag_prologue (diag_epilogue* e, A&&... a) + : B (std::forward (a)...), indent ("\n "), epilogue (e) {} + + template + diag_prologue (const char* i, diag_epilogue* e, A&&... a) + : B (std::forward (a)...), indent (i), epilogue (e) {} + + template + diag_record + operator<< (const T& x) const + { + diag_record r; + r.append (indent, epilogue); + B::operator() (r); + r << x; + return r; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_prologue& p) + { + r.append (p.indent, p.epilogue); + p (r); + return r; + } + }; + + template + struct diag_mark: B + { + diag_mark (): B () {} + + template + diag_mark (A&&... a): B (std::forward (a)...) {} + + template + diag_record + operator<< (const T& x) const + { + return B::operator() () << x; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_mark& m) + { + return r << m (); + } + }; + + template + struct diag_noreturn_end: B + { + diag_noreturn_end (): B () {} + + template + diag_noreturn_end (A&&... a): B (std::forward (a)...) {} + + [[noreturn]] friend void + operator<< (const diag_record& r, const diag_noreturn_end& e) + { + // We said that we never return which means this end mark cannot be used + // to "maybe not return". And not returning without any diagnostics is + // probably a mistake. + // + assert (r.full ()); + e.B::operator() (r); + } + }; +} diff --git a/libbutl/export.hxx b/libbutl/export.hxx index 5c59539..82e06cb 100644 --- a/libbutl/export.hxx +++ b/libbutl/export.hxx @@ -2,12 +2,11 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#ifndef LIBBUTL_EXPORT_HXX -#define LIBBUTL_EXPORT_HXX +#pragma once -// If we are compiling a module interface, setup the module export. +// If modules are available, setup the module export. // -#ifdef LIBBUTL_MODULE_BUILD +#ifdef __cpp_modules # define LIBBUTL_MODEXPORT export #else # define LIBBUTL_MODEXPORT @@ -45,5 +44,3 @@ // # define LIBBUTL_SYMEXPORT // Using static or shared. #endif - -#endif // LIBBUTL_EXPORT_HXX diff --git a/libbutl/fdstream.cxx b/libbutl/fdstream.cxx index ab78e6a..905c36f 100644 --- a/libbutl/fdstream.cxx +++ b/libbutl/fdstream.cxx @@ -2,7 +2,11 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif + +#include // errno, E* #ifndef _WIN32 # include // open(), O_*, fcntl() @@ -21,23 +25,46 @@ # include // _O_* # include // S_I* -# include // wcsncmp(), wcsstr() +# include // wcsncmp(), wcsstr() #endif -#include // errno, E* +#include + +#ifndef __cpp_lib_modules +#include +#include +#include +#include +#include +#include #include // ios_base::openmode, ios_base::failure #include // bad_alloc #include // numeric_limits -#include #include // memcpy(), memmove() #include // uncaught_exception() #include // invalid_argument #include #include +#endif #include +#ifdef __cpp_modules +module butl.fdstream; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.path; +import butl.filesystem; +#endif + +#endif + using namespace std; #ifdef _WIN32 diff --git a/libbutl/fdstream.hxx b/libbutl/fdstream.hxx deleted file mode 100644 index 3bd8e8b..0000000 --- a/libbutl/fdstream.hxx +++ /dev/null @@ -1,699 +0,0 @@ -// file : libbutl/fdstream.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_FDSTREAM_HXX -#define LIBBUTL_FDSTREAM_HXX - -#include -#include -#include -#include -#include // move() -#include // uint16_t, uint64_t - -#include - -#include -#include // permissions - -namespace butl -{ - // RAII type for file descriptors. Note that failure to close the descriptor - // is silently ignored by both the destructor and reset(). - // - // The descriptor can be negative. Such a descriptor is treated as unopened - // and is not closed. - // - struct nullfd_t {constexpr explicit nullfd_t (int) {}}; - constexpr const nullfd_t nullfd (-1); - - class LIBBUTL_SYMEXPORT auto_fd - { - public: - auto_fd (nullfd_t = nullfd) noexcept: fd_ (-1) {} - - explicit - auto_fd (int fd) noexcept: fd_ (fd) {} - - auto_fd (auto_fd&& fd) noexcept: fd_ (fd.release ()) {} - auto_fd& operator= (auto_fd&&) noexcept; - - auto_fd (const auto_fd&) = delete; - auto_fd& operator= (const auto_fd&) = delete; - - ~auto_fd () noexcept; - - int - get () const noexcept {return fd_;} - - void - reset (int fd = -1) noexcept; - - int - release () noexcept - { - int r (fd_); - fd_ = -1; - return r; - } - - // Close an open file descriptor. Throw ios::failure on the underlying OS - // error. Reset the descriptor to -1 whether the exception is thrown or - // not. - // - void - close (); - - private: - int fd_; - }; - - // An [io]fstream that can be initialized with a file descriptor in addition - // to a file name and that also by default enables exceptions on badbit and - // failbit. So instead of a dance like this: - // - // ifstream ifs; - // ifs.exceptions (ifstream::badbit | ifstream::failbit); - // ifs.open (path.string ()); - // - // You can simply do: - // - // ifdstream ifs (path); - // - // Notes and limitations: - // - // - char only - // - input or output but not both - // - no support for put back - // - non-blocking file descriptor is supported only by showmanyc() function - // and only on POSIX - // - throws ios::failure in case of open()/read()/write()/close() errors - // - exception mask has at least badbit - // - after catching an exception caused by badbit the stream is no longer - // used - // - not movable, though can be easily supported - // - passing to constructor auto_fd with a negative file descriptor is valid - // and results in the creation of an unopened object - // - class LIBBUTL_SYMEXPORT fdbuf: public std::basic_streambuf - { - public: - fdbuf () = default; - fdbuf (auto_fd&&); - - // Before we invented auto_fd into fdstreams we keept fdbuf opened on - // faulty close attempt. Now fdbuf is always closed by close() function. - // This semantics change seems to be the right one as there is no reason to - // expect fdclose() to succeed after it has already failed once. - // - void - close () {fd_.close ();} - - auto_fd - release (); - - void - open (auto_fd&&); - - bool - is_open () const {return fd_.get () >= 0;} - - int - fd () const {return fd_.get ();} - - public: - using base = std::basic_streambuf; - - using int_type = base::int_type; - using traits_type = base::traits_type; - - // basic_streambuf input interface. - // - public: - virtual std::streamsize - showmanyc (); - - virtual int_type - underflow (); - - // Direct access to the get area. Use with caution. - // - using base::gptr; - using base::egptr; - using base::gbump; - - // Return the (logical) position of the next byte to be read. - // - uint64_t - tellg () const {return off_ - (egptr () - gptr ());} - - private: - bool - load (); - - // basic_streambuf output interface. - // - public: - virtual int_type - overflow (int_type); - - virtual int - sync (); - - virtual std::streamsize - xsputn (const char_type*, std::streamsize); - - // Return the (logical) position of the next byte to be written. - // - uint64_t - tellp () const {return off_ + (pptr () - buf_);} - - private: - bool - save (); - - private: - auto_fd fd_; - uint64_t off_; - char buf_[8192]; - bool non_blocking_ = false; - }; - - // File stream mode. - // - // The text/binary flags have the same semantics as those in std::fstream. - // Specifically, this is a noop for POSIX systems where the two modes are - // the same. On Windows, when reading in the text mode the sequence of 0xD, - // 0xA characters is translated into the single OxA character and 0x1A is - // interpreted as EOF. When writing in the text mode the OxA character is - // translated into the 0xD, 0xA sequence. - // - // The skip flag instructs the stream to skip to the end before closing the - // file descriptor. This is primarily useful when working with pipes where - // you may want not to "offend" the other end by closing your end before - // reading all the data. - // - // The blocking/non_blocking flags determine whether the IO operation should - // block or return control if currently there is no data to read or no room - // to write. Only the istream::readsome() function supports the semantics of - // non-blocking operations. We also only support this on POSIX (Windows does - // not provide means for the non-blocking reading from a file descriptor so - // these flags are noop there). IO stream operations other than readsome() - // are illegal for non_blocking mode and result in the badbit being set. - // - enum class fdstream_mode: std::uint16_t - { - text = 0x01, - binary = 0x02, - skip = 0x04, - blocking = 0x08, - non_blocking = 0x10 - }; - - inline fdstream_mode operator& (fdstream_mode, fdstream_mode); - inline fdstream_mode operator| (fdstream_mode, fdstream_mode); - inline fdstream_mode operator&= (fdstream_mode&, fdstream_mode); - inline fdstream_mode operator|= (fdstream_mode&, fdstream_mode); - - // Extended (compared to ios::openmode) file open flags. - // - enum class fdopen_mode: std::uint16_t - { - in = 0x01, // Open for reading. - out = 0x02, // Open for writing. - append = 0x04, // Seek to the end of file before each write. - truncate = 0x08, // Discard the file contents on open. - create = 0x10, // Create a file if not exists. - exclusive = 0x20, // Fail if the file exists and the create flag is set. - binary = 0x40, // Set binary translation mode. - at_end = 0x80, // Seek to the end of stream immediately after open. - - none = 0 // Usefull when build the mode incrementally. - }; - - inline fdopen_mode operator& (fdopen_mode, fdopen_mode); - inline fdopen_mode operator| (fdopen_mode, fdopen_mode); - inline fdopen_mode operator&= (fdopen_mode&, fdopen_mode); - inline fdopen_mode operator|= (fdopen_mode&, fdopen_mode); - - class LIBBUTL_SYMEXPORT fdstream_base - { - protected: - fdstream_base () = default; - fdstream_base (auto_fd&& fd): buf_ (std::move (fd)) {} - fdstream_base (auto_fd&&, fdstream_mode); - - public: - int - fd () const {return buf_.fd ();} - - protected: - fdbuf buf_; - }; - - // iofdstream constructors and open() functions that take openmode as an - // argument mimic the corresponding iofstream functions in terms of the - // openmode mask interpretation. They throw std::invalid_argument for an - // invalid combination of flags (as per the standard). Note that the in and - // out flags are always added implicitly for ifdstream and ofdstream, - // respectively. - // - // iofdstream constructors and open() functions that take fdopen_mode as an - // argument interpret the mask literally just ignoring some flags which are - // meaningless in the absense of others (read more on that in the comment - // for fdopen()). Note that the in and out flags are always added implicitly - // for ifdstream and ofdstream, respectively. - // - // iofdstream constructors and open() functions that take file path as a - // const std::string& or const char* may throw the invalid_path exception. - // - // Passing auto_fd with a negative file descriptor is valid and results in - // the creation of an unopened object. - // - // Also note that open() and close() functions can be successfully called - // for an opened and unopened objects respectively. That is in contrast with - // iofstream that sets failbit in such cases. - // - - // Note that ifdstream destructor will close an open file descriptor but - // will ignore any errors. To detect such errors, call close() explicitly. - // - // This is a sample usage of iofdstreams with process. Note that here it is - // expected that the child process reads from STDIN first and writes to - // STDOUT afterwards. - // - // try - // { - // process pr (args, -1, -1); - // - // try - // { - // // In case of exception, skip and close input after output. - // // - // ifdstream is (move (pr.in_ofd), fdstream_mode::skip); - // ofdstream os (move (pr.out_fd)); - // - // // Write. - // - // os.close (); // Don't block the other end. - // - // // Read. - // - // is.close (); // Skip till end and close. - // - // if (pr.wait ()) - // { - // return ...; // Good. - // } - // - // // Non-zero exit, diagnostics presumably issued, fall through. - // } - // catch (const failure&) - // { - // // IO failure, child exit status doesn't matter. Just wait for the - // // process completion and fall through. - // // - // // Note that this is optional if the process_error handler simply - // // falls through since process destructor will wait (but will ignore - // // any errors). - // // - // pr.wait (); - // } - // - // error << .... ; - // - // // Fall through. - // } - // catch (const process_error& e) - // { - // error << ... << e; - // - // if (e.child ()) - // exit (1); - // - // // Fall through. - // } - // - // throw failed (); - // - class LIBBUTL_SYMEXPORT ifdstream: public fdstream_base, public std::istream - { - public: - // Create an unopened object. - // - explicit - ifdstream (iostate e = badbit | failbit); - - explicit - ifdstream (auto_fd&&, iostate e = badbit | failbit); - ifdstream (auto_fd&&, fdstream_mode m, iostate e = badbit | failbit); - - explicit - ifdstream (const char*, - openmode = in, - iostate e = badbit | failbit); - - explicit - ifdstream (const std::string&, - openmode = in, - iostate e = badbit | failbit); - - explicit - ifdstream (const path&, - openmode = in, - iostate e = badbit | failbit); - - ifdstream (const char*, - fdopen_mode, - iostate e = badbit | failbit); - - ifdstream (const std::string&, - fdopen_mode, - iostate e = badbit | failbit); - - ifdstream (const path&, - fdopen_mode, - iostate e = badbit | failbit); - - ~ifdstream () override; - - void - open (const char*, openmode = in); - - void - open (const std::string&, openmode = in); - - void - open (const path&, openmode = in); - - void - open (const char*, fdopen_mode); - - void - open (const std::string&, fdopen_mode); - - void - open (const path&, fdopen_mode); - - void - open (auto_fd&& fd) {buf_.open (std::move (fd)); clear ();} - - void - open (auto_fd&& fd, fdstream_mode m) - { - open (std::move (fd)); - skip_ = (m & fdstream_mode::skip) == fdstream_mode::skip; - } - - void close (); - auto_fd release (); // Note: no skipping. - bool is_open () const {return buf_.is_open ();} - - // Read the textual stream. The stream is supposed not to contain the null - // character. - // - std::string - read_text (); - - // Read the binary stream. - // - std::vector - read_binary (); - - private: - bool skip_ = false; - }; - - // Note that ofdstream requires that you explicitly call close() before - // destroying it. Or, more specifically, the ofdstream object should not be - // in the opened state by the time its destructor is called, unless it is in - // the "not good" state (good() == false) or the destructor is being called - // during the stack unwinding due to an exception being thrown - // (std::uncaught_exception() == true). This is enforced with assert() in - // the ofdstream destructor. - // - class LIBBUTL_SYMEXPORT ofdstream: public fdstream_base, public std::ostream - { - public: - // Create an unopened object. - // - explicit - ofdstream (iostate e = badbit | failbit); - - explicit - ofdstream (auto_fd&&, iostate e = badbit | failbit); - ofdstream (auto_fd&&, fdstream_mode m, iostate e = badbit | failbit); - - explicit - ofdstream (const char*, - openmode = out, - iostate e = badbit | failbit); - - explicit - ofdstream (const std::string&, - openmode = out, - iostate e = badbit | failbit); - - explicit - ofdstream (const path&, - openmode = out, - iostate e = badbit | failbit); - - ofdstream (const char*, - fdopen_mode, - iostate e = badbit | failbit); - - ofdstream (const std::string&, - fdopen_mode, - iostate e = badbit | failbit); - - ofdstream (const path&, - fdopen_mode, - iostate e = badbit | failbit); - - ~ofdstream () override; - - void - open (const char*, openmode = out); - - void - open (const std::string&, openmode = out); - - void - open (const path&, openmode = out); - - void - open (const char*, fdopen_mode); - - void - open (const std::string&, fdopen_mode); - - void - open (const path&, fdopen_mode); - - void - open (auto_fd&& fd) {buf_.open (std::move (fd)); clear ();} - - void close () {if (is_open ()) flush (); buf_.close ();} - auto_fd release (); - bool is_open () const {return buf_.is_open ();} - }; - - // The std::getline() replacement that provides a workaround for libstdc++'s - // ios::failure ABI fiasco (#66145) by throwing ios::failure, as it is - // defined at libbutl build time (new ABI on recent distributions) rather - // than libstdc++ build time (still old ABI on most distributions). - // - // Notes: - // - // - This relies of ADL so if the stream is used via the std::istream - // interface, then std::getline() will still be used. To put it another - // way, this is "the best we can do" until GCC folks get their act - // together. - // - // - The fail and eof bits may be left cleared in the stream exception mask - // when the function throws because of badbit. - // - LIBBUTL_SYMEXPORT ifdstream& - getline (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. - // - // The mode argument should have at least one of the in or out flags set. - // The append and truncate flags are meaningless in the absense of the out - // flag and are ignored without it. The exclusive flag is meaningless in the - // absense of the create flag and is ignored without it. Note also that if - // the exclusive flag is specified then a dangling symbolic link is treated - // as an existing file. - // - // The permissions argument is taken into account only if the file is - // created. Note also that permissions can be adjusted while being set in a - // way specific for the OS. On POSIX systems they are modified with the - // process' umask, so effective permissions are permissions & ~umask. On - // Windows permissions other than ru and wu are unlikelly to have effect. - // - // Also note that on POSIX the FD_CLOEXEC flag is set for the file descriptor - // to prevent its leakage into child processes. On Windows, for the same - // purpose, the _O_NOINHERIT flag is set. Note that the process class, that - // passes such a descriptor to the child, makes it inheritable for a while. - // - LIBBUTL_SYMEXPORT auto_fd - fdopen (const char*, - fdopen_mode, - permissions = permissions::ru | permissions::wu | - permissions::rg | permissions::wg | - permissions::ro | permissions::wo); - - LIBBUTL_SYMEXPORT auto_fd - fdopen (const std::string&, - fdopen_mode, - permissions = permissions::ru | permissions::wu | - permissions::rg | permissions::wg | - permissions::ro | permissions::wo); - - LIBBUTL_SYMEXPORT auto_fd - fdopen (const path&, - fdopen_mode, - permissions = permissions::ru | permissions::wu | - permissions::rg | permissions::wg | - permissions::ro | permissions::wo); - - // Duplicate an open file descriptor. Throw ios::failure on the underlying - // OS error. - // - // Note that on POSIX the FD_CLOEXEC flag is set for the new descriptor if it - // is present for the source one. That's in contrast to POSIX dup() that - // doesn't copy file descriptor flags. Also note that duplicating descriptor - // and setting the flag is not an atomic operation generally, but it is in - // regards to child process spawning (to prevent file descriptor leakage into - // a child process). - // - // Note that on Windows the _O_NOINHERIT flag is set for the new descriptor - // if it is present for the source one. That's in contrast to Windows _dup() - // that doesn't copy the flag. Also note that duplicating descriptor and - // setting the flag is not an atomic operation generally, but it is in - // regards to child process spawning (to prevent file descriptor leakage into - // a child process). - // - LIBBUTL_SYMEXPORT auto_fd - fddup (int fd); - - // Set the translation mode for the file descriptor. Throw invalid_argument - // for an invalid combination of flags. Return the previous mode on success, - // throw ios::failure otherwise. - // - // The text and binary flags are mutually exclusive on Windows. Due to - // implementation details at least one of them should be specified. On POSIX - // system the two modes are the same and so no check is performed. - // - // The blocking and non-blocking flags are mutually exclusive on POSIX - // system. Non-blocking mode is not supported on Windows and so the blocking - // mode is assumed regardless of the flags. - // - LIBBUTL_SYMEXPORT fdstream_mode - fdmode (int, fdstream_mode); - - // Portable functions for obtaining file descriptors of standard streams. - // Throw ios::failure on the underlying OS error. - // - // Note that you normally wouldn't want to close them using fddup() to - // convert them to auto_fd, for example: - // - // ifdstream is (fddup (stdin_fd ())); - // - LIBBUTL_SYMEXPORT int stdin_fd (); - LIBBUTL_SYMEXPORT int stdout_fd (); - LIBBUTL_SYMEXPORT int stderr_fd (); - - // Convenience functions for setting the translation mode for standard - // streams. - // - LIBBUTL_SYMEXPORT fdstream_mode stdin_fdmode (fdstream_mode); - LIBBUTL_SYMEXPORT fdstream_mode stdout_fdmode (fdstream_mode); - LIBBUTL_SYMEXPORT fdstream_mode stderr_fdmode (fdstream_mode); - - // Low-level, nothrow file descriptor API. - // - - // Close the file descriptor. Return true on success, set errno and return - // false otherwise. - // - LIBBUTL_SYMEXPORT bool - fdclose (int) noexcept; - - // Open the null device (e.g., /dev/null) that discards all data written to - // it and provides no data for read operations (i.e., yelds EOF on read). - // Return an auto_fd that holds its file descriptor on success and throwing - // ios::failure otherwise. - // - // On Windows the null device is NUL and writing anything substantial to it - // (like redirecting a process' output) is extremely slow, as in, an order - // of magnitude slower than writing to disk. If you are using the descriptor - // yourself this can be mitigated by setting the binary mode (already done - // by fdopen()) and using a buffer of around 64K. However, sometimes you - // have no control of how the descriptor will be used. For instance, it can - // be used to redirect a child's stdout and the way the child sets up its - // stdout is out of your control (on Windows). For such cases, there is an - // emulation via a temporary file. Mostly it functions as a proper null - // device with the file automatically removed once the descriptor is - // closed. One difference, however, would be if you were to both write to - // and read from the descriptor. - // - // Note that on POSIX the FD_CLOEXEC flag is set for the file descriptor to - // prevent its leakage into child processes. On Windows, for the same - // purpose, the _O_NOINHERIT flag is set. - // -#ifndef _WIN32 - LIBBUTL_SYMEXPORT auto_fd - fdnull () noexcept; -#else - LIBBUTL_SYMEXPORT auto_fd - fdnull (bool temp = false) noexcept; -#endif - - struct fdpipe - { - auto_fd in; - auto_fd out; - - void - close () - { - in.close (); - out.close (); - } - }; - - // Create a pipe. Throw ios::failure on the underlying OS error. By default - // both ends of the pipe are opened in the text mode. Pass the binary flag - // to instead open them in the binary mode. Passing a mode other than none - // or binary is illegal. - // - // Note that on Windows both ends of the created pipe are not inheritable. - // In particular, the process class that uses fdpipe underneath makes the - // appropriate end (the one being passed to the child) inheritable. - // - // Note that on POSIX the FD_CLOEXEC flag is set for both ends, so they get - // automatically closed by the child process to prevent undesired behaviors - // (such as child deadlock on read from a pipe due to the write-end leakage - // into the child process). Opening a pipe and setting the flag is not an - // atomic operation generally, but it is in regards to child process spawning - // (to prevent file descriptor leakage into child processes spawned from - // other threads). Also note that you don't need to reset the flag for a pipe - // end being passed to the process class ctor. - // - LIBBUTL_SYMEXPORT fdpipe - fdopen_pipe (fdopen_mode = fdopen_mode::none); - - // Test whether a file descriptor refers to a terminal. Throw ios::failure on - // the underlying OS error. - // - LIBBUTL_SYMEXPORT bool - fdterm (int); -} - -#include - -#endif // LIBBUTL_FDSTREAM_HXX diff --git a/libbutl/fdstream.ixx b/libbutl/fdstream.ixx index 212ec98..5678fba 100644 --- a/libbutl/fdstream.ixx +++ b/libbutl/fdstream.ixx @@ -2,9 +2,6 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include - namespace butl { // auto_fd diff --git a/libbutl/fdstream.mxx b/libbutl/fdstream.mxx new file mode 100644 index 0000000..f84b88e --- /dev/null +++ b/libbutl/fdstream.mxx @@ -0,0 +1,720 @@ +// file : libbutl/fdstream.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +#include + +#ifndef __cpp_lib_modules +#include +#include +#include +#include +#include // move() +#include // uint16_t, uint64_t + +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.fdstream; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.path; +import butl.filesystem; // permissions +#else +#include +#include +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // RAII type for file descriptors. Note that failure to close the descriptor + // is silently ignored by both the destructor and reset(). + // + // The descriptor can be negative. Such a descriptor is treated as unopened + // and is not closed. + // + struct nullfd_t {constexpr explicit nullfd_t (int) {}}; +#if defined(__cpp_modules) && defined(__clang__) //@@ MOD Clang duplicate sym. + inline constexpr nullfd_t nullfd (-1); +#else + const/*expr*/ nullfd_t nullfd (-1); //@@ MOD VC multiple defs. +#endif + + class LIBBUTL_SYMEXPORT auto_fd + { + public: + auto_fd (nullfd_t = nullfd) noexcept: fd_ (-1) {} + + explicit + auto_fd (int fd) noexcept: fd_ (fd) {} + + auto_fd (auto_fd&& fd) noexcept: fd_ (fd.release ()) {} + auto_fd& operator= (auto_fd&&) noexcept; + + auto_fd (const auto_fd&) = delete; + auto_fd& operator= (const auto_fd&) = delete; + + ~auto_fd () noexcept; + + int + get () const noexcept {return fd_;} + + void + reset (int fd = -1) noexcept; + + int + release () noexcept + { + int r (fd_); + fd_ = -1; + return r; + } + + // Close an open file descriptor. Throw ios::failure on the underlying OS + // error. Reset the descriptor to -1 whether the exception is thrown or + // not. + // + void + close (); + + private: + int fd_; + }; + + // An [io]fstream that can be initialized with a file descriptor in addition + // to a file name and that also by default enables exceptions on badbit and + // failbit. So instead of a dance like this: + // + // ifstream ifs; + // ifs.exceptions (ifstream::badbit | ifstream::failbit); + // ifs.open (path.string ()); + // + // You can simply do: + // + // ifdstream ifs (path); + // + // Notes and limitations: + // + // - char only + // - input or output but not both + // - no support for put back + // - non-blocking file descriptor is supported only by showmanyc() function + // and only on POSIX + // - throws ios::failure in case of open()/read()/write()/close() errors + // - exception mask has at least badbit + // - after catching an exception caused by badbit the stream is no longer + // used + // - not movable, though can be easily supported + // - passing to constructor auto_fd with a negative file descriptor is valid + // and results in the creation of an unopened object + // + class LIBBUTL_SYMEXPORT fdbuf: public std::basic_streambuf + { + public: + fdbuf () = default; + fdbuf (auto_fd&&); + + // Before we invented auto_fd into fdstreams we keept fdbuf opened on + // faulty close attempt. Now fdbuf is always closed by close() function. + // This semantics change seems to be the right one as there is no reason to + // expect fdclose() to succeed after it has already failed once. + // + void + close () {fd_.close ();} + + auto_fd + release (); + + void + open (auto_fd&&); + + bool + is_open () const {return fd_.get () >= 0;} + + int + fd () const {return fd_.get ();} + + public: + using base = std::basic_streambuf; + + using int_type = base::int_type; + using traits_type = base::traits_type; + + // basic_streambuf input interface. + // + public: + virtual std::streamsize + showmanyc (); + + virtual int_type + underflow (); + + // Direct access to the get area. Use with caution. + // + using base::gptr; + using base::egptr; + using base::gbump; + + // Return the (logical) position of the next byte to be read. + // + uint64_t + tellg () const {return off_ - (egptr () - gptr ());} + + private: + bool + load (); + + // basic_streambuf output interface. + // + public: + virtual int_type + overflow (int_type); + + virtual int + sync (); + + virtual std::streamsize + xsputn (const char_type*, std::streamsize); + + // Return the (logical) position of the next byte to be written. + // + uint64_t + tellp () const {return off_ + (pptr () - buf_);} + + private: + bool + save (); + + private: + auto_fd fd_; + uint64_t off_; + char buf_[8192]; + bool non_blocking_ = false; + }; + + // File stream mode. + // + // The text/binary flags have the same semantics as those in std::fstream. + // Specifically, this is a noop for POSIX systems where the two modes are + // the same. On Windows, when reading in the text mode the sequence of 0xD, + // 0xA characters is translated into the single OxA character and 0x1A is + // interpreted as EOF. When writing in the text mode the OxA character is + // translated into the 0xD, 0xA sequence. + // + // The skip flag instructs the stream to skip to the end before closing the + // file descriptor. This is primarily useful when working with pipes where + // you may want not to "offend" the other end by closing your end before + // reading all the data. + // + // The blocking/non_blocking flags determine whether the IO operation should + // block or return control if currently there is no data to read or no room + // to write. Only the istream::readsome() function supports the semantics of + // non-blocking operations. We also only support this on POSIX (Windows does + // not provide means for the non-blocking reading from a file descriptor so + // these flags are noop there). IO stream operations other than readsome() + // are illegal for non_blocking mode and result in the badbit being set. + // + enum class fdstream_mode: std::uint16_t + { + text = 0x01, + binary = 0x02, + skip = 0x04, + blocking = 0x08, + non_blocking = 0x10 + }; + + inline fdstream_mode operator& (fdstream_mode, fdstream_mode); + inline fdstream_mode operator| (fdstream_mode, fdstream_mode); + inline fdstream_mode operator&= (fdstream_mode&, fdstream_mode); + inline fdstream_mode operator|= (fdstream_mode&, fdstream_mode); + + // Extended (compared to ios::openmode) file open flags. + // + enum class fdopen_mode: std::uint16_t + { + in = 0x01, // Open for reading. + out = 0x02, // Open for writing. + append = 0x04, // Seek to the end of file before each write. + truncate = 0x08, // Discard the file contents on open. + create = 0x10, // Create a file if not exists. + exclusive = 0x20, // Fail if the file exists and the create flag is set. + binary = 0x40, // Set binary translation mode. + at_end = 0x80, // Seek to the end of stream immediately after open. + + none = 0 // Usefull when build the mode incrementally. + }; + + inline fdopen_mode operator& (fdopen_mode, fdopen_mode); + inline fdopen_mode operator| (fdopen_mode, fdopen_mode); + inline fdopen_mode operator&= (fdopen_mode&, fdopen_mode); + inline fdopen_mode operator|= (fdopen_mode&, fdopen_mode); + + class LIBBUTL_SYMEXPORT fdstream_base + { + protected: + fdstream_base () = default; + fdstream_base (auto_fd&& fd): buf_ (std::move (fd)) {} + fdstream_base (auto_fd&&, fdstream_mode); + + public: + int + fd () const {return buf_.fd ();} + + protected: + fdbuf buf_; + }; + + // iofdstream constructors and open() functions that take openmode as an + // argument mimic the corresponding iofstream functions in terms of the + // openmode mask interpretation. They throw std::invalid_argument for an + // invalid combination of flags (as per the standard). Note that the in and + // out flags are always added implicitly for ifdstream and ofdstream, + // respectively. + // + // iofdstream constructors and open() functions that take fdopen_mode as an + // argument interpret the mask literally just ignoring some flags which are + // meaningless in the absense of others (read more on that in the comment + // for fdopen()). Note that the in and out flags are always added implicitly + // for ifdstream and ofdstream, respectively. + // + // iofdstream constructors and open() functions that take file path as a + // const std::string& or const char* may throw the invalid_path exception. + // + // Passing auto_fd with a negative file descriptor is valid and results in + // the creation of an unopened object. + // + // Also note that open() and close() functions can be successfully called + // for an opened and unopened objects respectively. That is in contrast with + // iofstream that sets failbit in such cases. + // + + // Note that ifdstream destructor will close an open file descriptor but + // will ignore any errors. To detect such errors, call close() explicitly. + // + // This is a sample usage of iofdstreams with process. Note that here it is + // expected that the child process reads from STDIN first and writes to + // STDOUT afterwards. + // + // try + // { + // process pr (args, -1, -1); + // + // try + // { + // // In case of exception, skip and close input after output. + // // + // ifdstream is (move (pr.in_ofd), fdstream_mode::skip); + // ofdstream os (move (pr.out_fd)); + // + // // Write. + // + // os.close (); // Don't block the other end. + // + // // Read. + // + // is.close (); // Skip till end and close. + // + // if (pr.wait ()) + // { + // return ...; // Good. + // } + // + // // Non-zero exit, diagnostics presumably issued, fall through. + // } + // catch (const failure&) + // { + // // IO failure, child exit status doesn't matter. Just wait for the + // // process completion and fall through. + // // + // // Note that this is optional if the process_error handler simply + // // falls through since process destructor will wait (but will ignore + // // any errors). + // // + // pr.wait (); + // } + // + // error << .... ; + // + // // Fall through. + // } + // catch (const process_error& e) + // { + // error << ... << e; + // + // if (e.child ()) + // exit (1); + // + // // Fall through. + // } + // + // throw failed (); + // + class LIBBUTL_SYMEXPORT ifdstream: public fdstream_base, public std::istream + { + public: + // Create an unopened object. + // + explicit + ifdstream (iostate e = badbit | failbit); + + explicit + ifdstream (auto_fd&&, iostate e = badbit | failbit); + ifdstream (auto_fd&&, fdstream_mode m, iostate e = badbit | failbit); + + explicit + ifdstream (const char*, + openmode = in, + iostate e = badbit | failbit); + + explicit + ifdstream (const std::string&, + openmode = in, + iostate e = badbit | failbit); + + explicit + ifdstream (const path&, + openmode = in, + iostate e = badbit | failbit); + + ifdstream (const char*, + fdopen_mode, + iostate e = badbit | failbit); + + ifdstream (const std::string&, + fdopen_mode, + iostate e = badbit | failbit); + + ifdstream (const path&, + fdopen_mode, + iostate e = badbit | failbit); + + ~ifdstream () override; + + void + open (const char*, openmode = in); + + void + open (const std::string&, openmode = in); + + void + open (const path&, openmode = in); + + void + open (const char*, fdopen_mode); + + void + open (const std::string&, fdopen_mode); + + void + open (const path&, fdopen_mode); + + void + open (auto_fd&& fd) {buf_.open (std::move (fd)); clear ();} + + void + open (auto_fd&& fd, fdstream_mode m) + { + open (std::move (fd)); + skip_ = (m & fdstream_mode::skip) == fdstream_mode::skip; + } + + void close (); + auto_fd release (); // Note: no skipping. + bool is_open () const {return buf_.is_open ();} + + // Read the textual stream. The stream is supposed not to contain the null + // character. + // + std::string + read_text (); + + // Read the binary stream. + // + std::vector + read_binary (); + + private: + bool skip_ = false; + }; + + // Note that ofdstream requires that you explicitly call close() before + // destroying it. Or, more specifically, the ofdstream object should not be + // in the opened state by the time its destructor is called, unless it is in + // the "not good" state (good() == false) or the destructor is being called + // during the stack unwinding due to an exception being thrown + // (std::uncaught_exception() == true). This is enforced with assert() in + // the ofdstream destructor. + // + class LIBBUTL_SYMEXPORT ofdstream: public fdstream_base, public std::ostream + { + public: + // Create an unopened object. + // + explicit + ofdstream (iostate e = badbit | failbit); + + explicit + ofdstream (auto_fd&&, iostate e = badbit | failbit); + ofdstream (auto_fd&&, fdstream_mode m, iostate e = badbit | failbit); + + explicit + ofdstream (const char*, + openmode = out, + iostate e = badbit | failbit); + + explicit + ofdstream (const std::string&, + openmode = out, + iostate e = badbit | failbit); + + explicit + ofdstream (const path&, + openmode = out, + iostate e = badbit | failbit); + + ofdstream (const char*, + fdopen_mode, + iostate e = badbit | failbit); + + ofdstream (const std::string&, + fdopen_mode, + iostate e = badbit | failbit); + + ofdstream (const path&, + fdopen_mode, + iostate e = badbit | failbit); + + ~ofdstream () override; + + void + open (const char*, openmode = out); + + void + open (const std::string&, openmode = out); + + void + open (const path&, openmode = out); + + void + open (const char*, fdopen_mode); + + void + open (const std::string&, fdopen_mode); + + void + open (const path&, fdopen_mode); + + void + open (auto_fd&& fd) {buf_.open (std::move (fd)); clear ();} + + void close () {if (is_open ()) flush (); buf_.close ();} + auto_fd release (); + bool is_open () const {return buf_.is_open ();} + }; + + // The std::getline() replacement that provides a workaround for libstdc++'s + // ios::failure ABI fiasco (#66145) by throwing ios::failure, as it is + // defined at libbutl build time (new ABI on recent distributions) rather + // than libstdc++ build time (still old ABI on most distributions). + // + // Notes: + // + // - This relies of ADL so if the stream is used via the std::istream + // interface, then std::getline() will still be used. To put it another + // way, this is "the best we can do" until GCC folks get their act + // together. + // + // - The fail and eof bits may be left cleared in the stream exception mask + // when the function throws because of badbit. + // + LIBBUTL_SYMEXPORT ifdstream& + getline (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. + // + // The mode argument should have at least one of the in or out flags set. + // The append and truncate flags are meaningless in the absense of the out + // flag and are ignored without it. The exclusive flag is meaningless in the + // absense of the create flag and is ignored without it. Note also that if + // the exclusive flag is specified then a dangling symbolic link is treated + // as an existing file. + // + // The permissions argument is taken into account only if the file is + // created. Note also that permissions can be adjusted while being set in a + // way specific for the OS. On POSIX systems they are modified with the + // process' umask, so effective permissions are permissions & ~umask. On + // Windows permissions other than ru and wu are unlikelly to have effect. + // + // Also note that on POSIX the FD_CLOEXEC flag is set for the file descriptor + // to prevent its leakage into child processes. On Windows, for the same + // purpose, the _O_NOINHERIT flag is set. Note that the process class, that + // passes such a descriptor to the child, makes it inheritable for a while. + // + LIBBUTL_SYMEXPORT auto_fd + fdopen (const char*, + fdopen_mode, + permissions = permissions::ru | permissions::wu | + permissions::rg | permissions::wg | + permissions::ro | permissions::wo); + + LIBBUTL_SYMEXPORT auto_fd + fdopen (const std::string&, + fdopen_mode, + permissions = permissions::ru | permissions::wu | + permissions::rg | permissions::wg | + permissions::ro | permissions::wo); + + LIBBUTL_SYMEXPORT auto_fd + fdopen (const path&, + fdopen_mode, + permissions = permissions::ru | permissions::wu | + permissions::rg | permissions::wg | + permissions::ro | permissions::wo); + + // Duplicate an open file descriptor. Throw ios::failure on the underlying + // OS error. + // + // Note that on POSIX the FD_CLOEXEC flag is set for the new descriptor if it + // is present for the source one. That's in contrast to POSIX dup() that + // doesn't copy file descriptor flags. Also note that duplicating descriptor + // and setting the flag is not an atomic operation generally, but it is in + // regards to child process spawning (to prevent file descriptor leakage into + // a child process). + // + // Note that on Windows the _O_NOINHERIT flag is set for the new descriptor + // if it is present for the source one. That's in contrast to Windows _dup() + // that doesn't copy the flag. Also note that duplicating descriptor and + // setting the flag is not an atomic operation generally, but it is in + // regards to child process spawning (to prevent file descriptor leakage into + // a child process). + // + LIBBUTL_SYMEXPORT auto_fd + fddup (int fd); + + // Set the translation mode for the file descriptor. Throw invalid_argument + // for an invalid combination of flags. Return the previous mode on success, + // throw ios::failure otherwise. + // + // The text and binary flags are mutually exclusive on Windows. Due to + // implementation details at least one of them should be specified. On POSIX + // system the two modes are the same and so no check is performed. + // + // The blocking and non-blocking flags are mutually exclusive on POSIX + // system. Non-blocking mode is not supported on Windows and so the blocking + // mode is assumed regardless of the flags. + // + LIBBUTL_SYMEXPORT fdstream_mode + fdmode (int, fdstream_mode); + + // Portable functions for obtaining file descriptors of standard streams. + // Throw ios::failure on the underlying OS error. + // + // Note that you normally wouldn't want to close them using fddup() to + // convert them to auto_fd, for example: + // + // ifdstream is (fddup (stdin_fd ())); + // + LIBBUTL_SYMEXPORT int stdin_fd (); + LIBBUTL_SYMEXPORT int stdout_fd (); + LIBBUTL_SYMEXPORT int stderr_fd (); + + // Convenience functions for setting the translation mode for standard + // streams. + // + LIBBUTL_SYMEXPORT fdstream_mode stdin_fdmode (fdstream_mode); + LIBBUTL_SYMEXPORT fdstream_mode stdout_fdmode (fdstream_mode); + LIBBUTL_SYMEXPORT fdstream_mode stderr_fdmode (fdstream_mode); + + // Low-level, nothrow file descriptor API. + // + + // Close the file descriptor. Return true on success, set errno and return + // false otherwise. + // + LIBBUTL_SYMEXPORT bool + fdclose (int) noexcept; + + // Open the null device (e.g., /dev/null) that discards all data written to + // it and provides no data for read operations (i.e., yelds EOF on read). + // Return an auto_fd that holds its file descriptor on success and throwing + // ios::failure otherwise. + // + // On Windows the null device is NUL and writing anything substantial to it + // (like redirecting a process' output) is extremely slow, as in, an order + // of magnitude slower than writing to disk. If you are using the descriptor + // yourself this can be mitigated by setting the binary mode (already done + // by fdopen()) and using a buffer of around 64K. However, sometimes you + // have no control of how the descriptor will be used. For instance, it can + // be used to redirect a child's stdout and the way the child sets up its + // stdout is out of your control (on Windows). For such cases, there is an + // emulation via a temporary file. Mostly it functions as a proper null + // device with the file automatically removed once the descriptor is + // closed. One difference, however, would be if you were to both write to + // and read from the descriptor. + // + // Note that on POSIX the FD_CLOEXEC flag is set for the file descriptor to + // prevent its leakage into child processes. On Windows, for the same + // purpose, the _O_NOINHERIT flag is set. + // +#ifndef _WIN32 + LIBBUTL_SYMEXPORT auto_fd + fdnull () noexcept; +#else + LIBBUTL_SYMEXPORT auto_fd + fdnull (bool temp = false) noexcept; +#endif + + struct fdpipe + { + auto_fd in; + auto_fd out; + + void + close () + { + in.close (); + out.close (); + } + }; + + // Create a pipe. Throw ios::failure on the underlying OS error. By default + // both ends of the pipe are opened in the text mode. Pass the binary flag + // to instead open them in the binary mode. Passing a mode other than none + // or binary is illegal. + // + // Note that on Windows both ends of the created pipe are not inheritable. + // In particular, the process class that uses fdpipe underneath makes the + // appropriate end (the one being passed to the child) inheritable. + // + // Note that on POSIX the FD_CLOEXEC flag is set for both ends, so they get + // automatically closed by the child process to prevent undesired behaviors + // (such as child deadlock on read from a pipe due to the write-end leakage + // into the child process). Opening a pipe and setting the flag is not an + // atomic operation generally, but it is in regards to child process spawning + // (to prevent file descriptor leakage into child processes spawned from + // other threads). Also note that you don't need to reset the flag for a pipe + // end being passed to the process class ctor. + // + LIBBUTL_SYMEXPORT fdpipe + fdopen_pipe (fdopen_mode = fdopen_mode::none); + + // Test whether a file descriptor refers to a terminal. Throw ios::failure on + // the underlying OS error. + // + LIBBUTL_SYMEXPORT bool + fdterm (int); +} + +#include diff --git a/libbutl/filesystem.cxx b/libbutl/filesystem.cxx index 7ba5275..8c3abc4 100644 --- a/libbutl/filesystem.cxx +++ b/libbutl/filesystem.cxx @@ -2,7 +2,11 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif + +#include // errno, E* #ifndef _WIN32 # include // rename() @@ -25,25 +29,46 @@ # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) # endif - -# include // lcase() #endif -#include // errno, E* +#include + +#ifndef __cpp_lib_modules +#include +#include +#include +#include +#include #include #include #include // unique_ptr -#include // size_t -#include // pair -#include // reverse_iterator #include +#endif +// Other includes. -#include -#include // throw_generic_error() -#include -#include +#ifdef __cpp_modules +module butl.filesystem; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +#endif +import butl.path; +import butl.timestamp; +#endif + +import butl.utility; // throw_generic_error(), lcase()[_WIN32] +import butl.fdstream; +import butl.small_vector; +#else +#include +#include +#include +#include +#endif using namespace std; diff --git a/libbutl/filesystem.hxx b/libbutl/filesystem.hxx deleted file mode 100644 index ef1b22b..0000000 --- a/libbutl/filesystem.hxx +++ /dev/null @@ -1,626 +0,0 @@ -// file : libbutl/filesystem.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_FILESYSTEM_HXX -#define LIBBUTL_FILESYSTEM_HXX - -#ifndef _WIN32 -# include // DIR -#else -# include // intptr_t -#endif - -// VC's sys/types.h header file doesn't define mode_t type. So let's define it -// ourselves according to the POSIX specification. -// -#ifndef _MSC_VER -# include // mode_t -#else - typedef int mode_t; -#endif - -#include // ptrdiff_t -#include // uint16_t, etc -#include // move(), pair -#include -#include - -#include - -#include -#include - -namespace butl -{ - // Return true if the path is to an existing regular file. Note that by - // default this function follows symlinks. - // - LIBBUTL_SYMEXPORT bool - file_exists (const char*, bool follow_symlinks = true); - - inline bool - file_exists (const path& p, bool fs = true) { - return file_exists (p.string ().c_str (), fs);} - - // Return true if the path is to an existing directory. Note that this - // function follows symlinks. - // - LIBBUTL_SYMEXPORT bool - dir_exists (const char*); - - inline bool - dir_exists (const path& p) {return dir_exists (p.string ().c_str ());} - - // Return true if the path is to an existing file system entry. Note that by - // default this function doesn't follow symlinks. - // - LIBBUTL_SYMEXPORT bool - entry_exists (const char*, bool follow_symlinks = false); - - inline bool - entry_exists (const path& p, bool fs = false) { - return entry_exists (p.string ().c_str (), fs);} - - // Filesystem entry type. - // - enum class entry_type - { - unknown, - regular, - directory, - symlink, - other - }; - - // Filesystem entry info. The size is only meaningful for regular files. - // - struct entry_stat - { - entry_type type; - std::uint64_t size; - }; - - // Return a flag indicating if the path is to an existing file system entry - // and its type if so. Note that by default this function doesn't follow - // symlinks. - // - LIBBUTL_SYMEXPORT std::pair - path_entry (const char*, bool follow_symlinks = false); - - inline std::pair - path_entry (const path& p, bool fs = false) { - return path_entry (p.string ().c_str (), fs);} - - // Return true if the directory is empty. Note that the path must exist - // and be a directory. This function follows symlinks. - // - bool - dir_empty (const dir_path&); - - // Return true if the file is empty. Note that the path must exist and be a - // regular file. This function follows symlinks. - // - bool - file_empty (const path&); - - // Set the file access and modification times to the current time. If the - // file does not exist and create is true, create it and fail otherwise. - // Return true if the file was created and false otherwise. Errors are - // reported by throwing std::system_error. - // - LIBBUTL_SYMEXPORT bool - touch_file (const path&, bool create = true); - - // Try to create a directory unless it already exists. If you expect - // the directory to exist and performance is important, then you - // should first call dir_exists() above since that's what this - // implementation will do to make sure the path is actually a - // directory. - // - // You should also probably use the default mode 0777 and let the - // umask mechanism adjust it to the user's preferences. - // - // Errors are reported by throwing std::system_error. - // - enum class mkdir_status {success, already_exists}; - - LIBBUTL_SYMEXPORT mkdir_status - try_mkdir (const dir_path&, mode_t = 0777); - - // The '-p' version of the above (i.e., it creates the parent - // directories if necessary). - // - LIBBUTL_SYMEXPORT mkdir_status - try_mkdir_p (const dir_path&, mode_t = 0777); - - // Try to remove the directory returning not_exist if it does not exist - // and not_empty if it is not empty. Unless ignore_error is true, all - // other errors are reported by throwing std::system_error. - // - enum class rmdir_status {success, not_exist, not_empty}; - - LIBBUTL_SYMEXPORT rmdir_status - try_rmdir (const dir_path&, bool ignore_error = false); - - // The '-r' (recursive) version of the above. Note that it will - // never return not_empty. - // - LIBBUTL_SYMEXPORT rmdir_status - try_rmdir_r (const dir_path&, bool ignore_error = false); - - // As above but throws rather than returns not_exist if the directory - // does not exist (unless ignore_error is true), so check before calling. - // If the second argument is false, then the directory itself is not removed. - // - LIBBUTL_SYMEXPORT void - rmdir_r (const dir_path&, bool dir = true, bool ignore_error = false); - - // Try to remove the file (or symlinks) returning not_exist if - // it does not exist. Unless ignore_error is true, all other - // errors are reported by throwing std::system_error. - // - enum class rmfile_status {success, not_exist}; - - LIBBUTL_SYMEXPORT rmfile_status - try_rmfile (const path&, bool ignore_error = false); - - // Automatically try to remove a non-empty the path on destruction unless - // cancelled. Since the non-cancelled destruction will normally happen as a - // result of an exception, the failure to remove the path is silently - // ignored. - // - template - struct auto_rm - { - P path; - bool active; - - explicit - auto_rm (P p = P (), bool a = true): path (std::move (p)), active (a) {} - - void - cancel () {active = false;} - - // Movable-only type. Move-assignment cancels the lhs object. - // - auto_rm (auto_rm&&); - auto_rm& operator= (auto_rm&&); - auto_rm (const auto_rm&) = delete; - auto_rm& operator= (const auto_rm&) = delete; - - ~auto_rm (); - }; - - using auto_rmfile = auto_rm; - using auto_rmdir = auto_rm; // Note: recursive (rm_r). - - // Create a symbolic link to a file (default) or directory (third argument - // is true). Throw std::system_error on failures. - // - // Note that Windows symlinks are currently not supported. - // - LIBBUTL_SYMEXPORT void - mksymlink (const path& target, const path& link, bool dir = false); - - // Create a symbolic link to a directory. Throw std::system_error on - // failures. - // - inline void - mksymlink (const dir_path& target, const dir_path& link) - { - mksymlink (target, link, true); - } - - // Create a hard link to a file (default) or directory (third argument is - // true). Throw std::system_error on failures. - // - // Note that on Linix, FreeBSD and some other platforms the target can not - // be a directory. While Windows support directories (via junktions), this - // is currently not implemented. - // - LIBBUTL_SYMEXPORT void - mkhardlink (const path& target, const path& link, bool dir = false); - - // Create a hard link to a directory. Throw std::system_error on failures. - // - inline void - mkhardlink (const dir_path& target, const dir_path& link) - { - mkhardlink (target, link, true); - } - - // File copy flags. - // - enum class cpflags: std::uint16_t - { - overwrite_content = 0x1, - overwrite_permissions = 0x2, - - none = 0 - }; - - inline cpflags operator& (cpflags, cpflags); - inline cpflags operator| (cpflags, cpflags); - inline cpflags operator&= (cpflags&, cpflags); - inline cpflags operator|= (cpflags&, cpflags); - - // Copy a regular file, including its permissions. 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. - // - // Note that in case of overwriting, the existing destination file gets - // truncated (not deleted) prior to being overwritten. As a side-effect, - // hard link to the destination file will still reference the same file - // system node after the copy. - // - // Also note that if the overwrite_content flag is not set and the - // destination is a dangling symbolic link, then this function will still - // fail. - // - LIBBUTL_SYMEXPORT void - cpfile (const path& from, const path& to, cpflags = cpflags::none); - - // Copy a regular file into (inside) an existing directory. - // - inline void - cpfile_into (const path& from, - const dir_path& into, - cpflags fl = cpflags::none) - { - cpfile (from, into / from.leaf (), fl); - } - - // Rename a filesystem entry (file, symlink, or directory). Throw - // std::system_error on failure. - // - // If the source path refers to a directory, then the destination path must - // either not exist, or refer to an empty directory. If the source path - // refers to an entry that is not a directory, then the destination path must - // not exist or not refer to a directory. - // - // If the source path refers to a symlink, then the link is renamed. If the - // destination path refers to a symlink, then the link will be overwritten. - // - // If the source and destination paths are on different file systems (or - // different drives on Windows) and the underlying OS does not support move - // for the source entry, then fail unless the source paths refers to a file - // or a file symlink. In this case fall back to copying the source file - // (content, permissions, access and modification times) and removing the - // source entry afterwards. - // - // Note that the operation is atomic only on POSIX, only if source and - // destination paths are on the same file system, and only if the - // overwrite_content flag is specified. - // - LIBBUTL_SYMEXPORT void - mventry (const path& from, - const path& to, - cpflags = cpflags::overwrite_permissions); - - // Move a filesystem entry into (inside) an existing directory. - // - inline void - mventry_into (const path& from, - const dir_path& into, - cpflags f = cpflags::overwrite_permissions) - { - mventry (from, into / from.leaf (), f); - } - - // Raname file or file symlink. - // - inline void - mvfile (const path& from, - const path& to, - cpflags f = cpflags::overwrite_permissions) - { - mventry (from, to, f); - } - - inline void - mvfile_into (const path& from, - const dir_path& into, - cpflags f = cpflags::overwrite_permissions) - { - mventry_into (from, into, f); - } - - // Raname directory or directory symlink. - // - inline void - mvdir (const dir_path& from, - const dir_path& to, - cpflags f = cpflags::overwrite_permissions) - { - mventry (from, to, f); - } - - inline void - mvdir_into (const path& from, - const dir_path& into, - cpflags f = cpflags::overwrite_permissions) - { - mventry_into (from, into, f); - } - - // Return timestamp_nonexistent if the entry at the specified path - // does not exist or is not a path. All other errors are reported - // by throwing std::system_error. Note that this function resolves - // symlinks. - // - LIBBUTL_SYMEXPORT timestamp - file_mtime (const char*); - - inline timestamp - file_mtime (const path& p) {return file_mtime (p.string ().c_str ());} - - // 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. - // - LIBBUTL_SYMEXPORT permissions - path_permissions (const path&); - - // Set path permissions. Throw std::system_error on failure. Note that this - // function resolves symlinks. - // - LIBBUTL_SYMEXPORT void - path_permissions (const path&, permissions); - - // Directory entry iteration. - // - class LIBBUTL_SYMEXPORT dir_entry - { - public: - typedef butl::path path_type; - - // Symlink target type in case of the symlink, ltype() otherwise. - // - entry_type - type () const; - - entry_type - ltype () const; - - // Entry path (excluding the base). To get the full path, do - // base () / path (). - // - const path_type& - path () const {return p_;} - - const dir_path& - 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)) {} - - private: - entry_type - type (bool link) const; - - private: - friend class dir_iterator; - - mutable entry_type t_ = entry_type::unknown; // Lazy evaluation. - mutable entry_type lt_ = entry_type::unknown; // Lazy evaluation. - path_type p_; - dir_path b_; - }; - - class LIBBUTL_SYMEXPORT dir_iterator - { - public: - typedef dir_entry value_type; - typedef const dir_entry* pointer; - typedef const dir_entry& reference; - typedef std::ptrdiff_t difference_type; - typedef std::input_iterator_tag iterator_category; - - ~dir_iterator (); - dir_iterator () = default; - - explicit - dir_iterator (const dir_path&); - - dir_iterator (const dir_iterator&) = delete; - dir_iterator& operator= (const dir_iterator&) = delete; - - dir_iterator (dir_iterator&&) noexcept; - dir_iterator& operator= (dir_iterator&&); - - dir_iterator& operator++ () {next (); return *this;} - - reference operator* () const {return e_;} - pointer operator-> () const {return &e_;} - - friend bool operator== (const dir_iterator&, const dir_iterator&); - friend bool operator!= (const dir_iterator&, const dir_iterator&); - - private: - void - next (); - - private: - dir_entry e_; - -#ifndef _WIN32 - DIR* h_ = nullptr; -#else - intptr_t h_ = -1; -#endif - }; - - // Range-based for loop support. - // - // for (const auto& de: dir_iterator (dir_path ("/tmp"))) ... - // - // Note that the "range" (which is the "begin" iterator), is no - // longer usable. In other words, don't do this: - // - // dir_iterator i (...); - // for (...: i) ... - // ++i; // Invalid. - // - inline dir_iterator begin (dir_iterator&); - inline dir_iterator end (const dir_iterator&); - - // Wildcard pattern match and search (aka glob). - // - // Currently the following wildcard characters are supported: - // - // * - match any number of characters (including zero) - // ? - match any single character - - // Return true if name matches pattern. Both must be single path components, - // possibly with a trailing directory separator to indicate a directory. - // - // If the pattern ends with a directory separator, then it only matches a - // directory name (i.e., ends with a directory separator, but potentially - // different). Otherwise, it only matches a non-directory name (no trailing - // directory separator). - // - LIBBUTL_SYMEXPORT bool - path_match (const std::string& pattern, const std::string& name); - - // Return true if path entry matches pattern. Note that the match is - // performed literally, with no paths normalization being performed. The - // start directory is used if the first pattern component is a self-matching - // wildcard (see below for the start directory and wildcard semantics). - // - LIBBUTL_SYMEXPORT bool - path_match (const path& pattern, - const path& entry, - const dir_path& start = dir_path ()); - - // Search for paths matching the pattern calling the specified function for - // each matching path (see below for details). - // - // If the pattern is relative, then search in the start directory. If the - // start directory is empty, then search in the current working directory. - // Searching in non-existent directories is not an error. Throw - // std::system_error in case of a failure (insufficient permissions, etc). - // - // The pattern may contain multiple components that include wildcards. On - // Windows the drive letter may not be a wildcard. - // - // In addition to the wildcard characters listed in path_match(), - // path_search() also recognizes the ** and *** wildcard sequences. If a - // path component contains **, then it is matched just like * but in all the - // subdirectories, recursively. The *** wildcard behaves like ** but also - // matches the start directory itself. Note that if the first pattern - // component contains ***, then the start directory must be empty or be - // terminated with a "meaningful" component (e.g., probably not '.' or - // '..'). - // - // So, for example, foo/bar-**.txt will return all the files matching the - // bar-*.txt pattern in all the subdirectoris of foo/. And foo/f***/ will - // return all the subdirectories matching the f*/ pattern plus foo/ itself. - // - // Note that having multiple recursive components in the pattern we can end - // up with calling func() multiple times (once per such a component) for the - // same path. For example the search with pattern f***/b**/ starting in - // directory foo, that has the foo/fox/box/ structure, will result in - // calling func(foo/fox/box/) twice: first time for being a child of fox/, - // second time for being a child of foo/. - // - // The callback function is called for both intermediate matches (interm is - // true) and final matches (interm is false). Pattern is what matched the - // last component in the path and is empty if the last component is not a - // pattern (final match only; say as in */foo.txt). - // - // If the callback function returns false for an intermediate path, then no - // further search is performed at or below this path. If false is returned - // for a final match, then the entire search is stopped. - // - // The path can be moved for the final match or for an intermediate match - // but only if false is returned. - // - // As an example, consider pattern f*/bar/b*/*.txt and path - // foo/bar/baz/x.txt. The sequence of calls in this case will be: - // - // (foo/, f*/, true) - // (foo/bar/baz/, b*/, true) - // (foo/bar/baz/x.txt, *.txt, false) - // - // If the pattern contains a recursive wildcard, then the callback function - // can be called for the same directory twice: first time as an intermediate - // match with */ pattern to decide if to recursively traverse the directory, - // and the second time if the directory matches the pattern component (either - // as an intermediate or a final match). As an example, consider pattern - // b**/c* and directory tree a/b/c/. The sequence of calls in this case will - // be: - // - // (a/, */, true) - // (a/b/, */ true) - // (a/b/c/, */, true) - // (a/b/, b*/, true) - // (a/b/c/, c*/, false) - // - // Symlinks are not followed if the follow_symlinks argument is false. This - // rule is only applied for symlinks that are matched against the rightmost - // component of the pattern. In particular, this mean that such symlinks will - // never match a directory pattern, and some results can be missing for the - // recursive rightmost component. - // - // 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. - // - LIBBUTL_SYMEXPORT void - path_search (const path& pattern, - const std::function&, - const dir_path& start = dir_path (), - bool follow_symlinks = true); - - // Same as above, but behaves as if the directory tree being searched - // through contains only the specified entry. The start directory is used if - // the first pattern component is a self-matching wildcard (see above). - // - // If pattern or entry is relative, then it is assumed to be relative to the - // start directory (which, if relative itself, is assumed to be relative to - // the current directory). Note that the implementation can optimize the - // case when pattern and entry are both non-empty and relative. - // - LIBBUTL_SYMEXPORT void - path_search (const path& pattern, - const path& entry, - const std::function&, - const dir_path& start = dir_path ()); -} - -#include - -#endif // LIBBUTL_FILESYSTEM_HXX diff --git a/libbutl/filesystem.ixx b/libbutl/filesystem.ixx index a618d5b..170c108 100644 --- a/libbutl/filesystem.ixx +++ b/libbutl/filesystem.ixx @@ -2,8 +2,6 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // E* - namespace butl { inline bool diff --git a/libbutl/filesystem.mxx b/libbutl/filesystem.mxx new file mode 100644 index 0000000..271220f --- /dev/null +++ b/libbutl/filesystem.mxx @@ -0,0 +1,647 @@ +// file : libbutl/filesystem.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +#include // E* + +#ifndef _WIN32 +# include // DIR +#else +# include // intptr_t +#endif + +// VC's sys/types.h header file doesn't define mode_t type. So we define it +// ourselves according to the POSIX specification. +// +#ifndef _MSC_VER +# include // mode_t +#else + typedef int mode_t; +#endif + +#ifndef __cpp_lib_modules +#include // ptrdiff_t +#include // uint16_t, etc +#include // move(), pair +#include +#include + +#include //@@ MOD needed by timestamp module. +#endif + +// Other includes. +#ifdef __cpp_modules +export module butl.filesystem; + +#ifdef __cpp_lib_modules +import std.core; +#endif + +import butl.path; +import butl.timestamp; + +import butl.utility; +#else +#include +#include + +#include +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // Return true if the path is to an existing regular file. Note that by + // default this function follows symlinks. + // + LIBBUTL_SYMEXPORT bool + file_exists (const char*, bool follow_symlinks = true); + + inline bool + file_exists (const path& p, bool fs = true) { + return file_exists (p.string ().c_str (), fs);} + + // Return true if the path is to an existing directory. Note that this + // function follows symlinks. + // + LIBBUTL_SYMEXPORT bool + dir_exists (const char*); + + inline bool + dir_exists (const path& p) {return dir_exists (p.string ().c_str ());} + + // Return true if the path is to an existing file system entry. Note that by + // default this function doesn't follow symlinks. + // + LIBBUTL_SYMEXPORT bool + entry_exists (const char*, bool follow_symlinks = false); + + inline bool + entry_exists (const path& p, bool fs = false) { + return entry_exists (p.string ().c_str (), fs);} + + // Filesystem entry type. + // + enum class entry_type + { + unknown, + regular, + directory, + symlink, + other + }; + + // Filesystem entry info. The size is only meaningful for regular files. + // + struct entry_stat + { + entry_type type; + std::uint64_t size; + }; + + // Return a flag indicating if the path is to an existing file system entry + // and its type if so. Note that by default this function doesn't follow + // symlinks. + // + LIBBUTL_SYMEXPORT std::pair + path_entry (const char*, bool follow_symlinks = false); + + inline std::pair + path_entry (const path& p, bool fs = false) { + return path_entry (p.string ().c_str (), fs);} + + // Return true if the directory is empty. Note that the path must exist + // and be a directory. This function follows symlinks. + // + bool + dir_empty (const dir_path&); + + // Return true if the file is empty. Note that the path must exist and be a + // regular file. This function follows symlinks. + // + bool + file_empty (const path&); + + // Set the file access and modification times to the current time. If the + // file does not exist and create is true, create it and fail otherwise. + // Return true if the file was created and false otherwise. Errors are + // reported by throwing std::system_error. + // + LIBBUTL_SYMEXPORT bool + touch_file (const path&, bool create = true); + + // Try to create a directory unless it already exists. If you expect + // the directory to exist and performance is important, then you + // should first call dir_exists() above since that's what this + // implementation will do to make sure the path is actually a + // directory. + // + // You should also probably use the default mode 0777 and let the + // umask mechanism adjust it to the user's preferences. + // + // Errors are reported by throwing std::system_error. + // + enum class mkdir_status {success, already_exists}; + + LIBBUTL_SYMEXPORT mkdir_status + try_mkdir (const dir_path&, mode_t = 0777); + + // The '-p' version of the above (i.e., it creates the parent + // directories if necessary). + // + LIBBUTL_SYMEXPORT mkdir_status + try_mkdir_p (const dir_path&, mode_t = 0777); + + // Try to remove the directory returning not_exist if it does not exist + // and not_empty if it is not empty. Unless ignore_error is true, all + // other errors are reported by throwing std::system_error. + // + enum class rmdir_status {success, not_exist, not_empty}; + + LIBBUTL_SYMEXPORT rmdir_status + try_rmdir (const dir_path&, bool ignore_error = false); + + // The '-r' (recursive) version of the above. Note that it will + // never return not_empty. + // + LIBBUTL_SYMEXPORT rmdir_status + try_rmdir_r (const dir_path&, bool ignore_error = false); + + // As above but throws rather than returns not_exist if the directory + // does not exist (unless ignore_error is true), so check before calling. + // If the second argument is false, then the directory itself is not removed. + // + LIBBUTL_SYMEXPORT void + rmdir_r (const dir_path&, bool dir = true, bool ignore_error = false); + + // Try to remove the file (or symlinks) returning not_exist if + // it does not exist. Unless ignore_error is true, all other + // errors are reported by throwing std::system_error. + // + enum class rmfile_status {success, not_exist}; + + LIBBUTL_SYMEXPORT rmfile_status + try_rmfile (const path&, bool ignore_error = false); + + // Automatically try to remove a non-empty the path on destruction unless + // cancelled. Since the non-cancelled destruction will normally happen as a + // result of an exception, the failure to remove the path is silently + // ignored. + // + template + struct auto_rm + { + P path; + bool active; + + explicit + auto_rm (P p = P (), bool a = true): path (std::move (p)), active (a) {} + + void + cancel () {active = false;} + + // Movable-only type. Move-assignment cancels the lhs object. + // + auto_rm (auto_rm&&); + auto_rm& operator= (auto_rm&&); + auto_rm (const auto_rm&) = delete; + auto_rm& operator= (const auto_rm&) = delete; + + ~auto_rm (); + }; + + using auto_rmfile = auto_rm; + using auto_rmdir = auto_rm; // Note: recursive (rm_r). + + // Create a symbolic link to a file (default) or directory (third argument + // is true). Throw std::system_error on failures. + // + // Note that Windows symlinks are currently not supported. + // + LIBBUTL_SYMEXPORT void + mksymlink (const path& target, const path& link, bool dir = false); + + // Create a symbolic link to a directory. Throw std::system_error on + // failures. + // + inline void + mksymlink (const dir_path& target, const dir_path& link) + { + mksymlink (target, link, true); + } + + // Create a hard link to a file (default) or directory (third argument is + // true). Throw std::system_error on failures. + // + // Note that on Linix, FreeBSD and some other platforms the target can not + // be a directory. While Windows support directories (via junktions), this + // is currently not implemented. + // + LIBBUTL_SYMEXPORT void + mkhardlink (const path& target, const path& link, bool dir = false); + + // Create a hard link to a directory. Throw std::system_error on failures. + // + inline void + mkhardlink (const dir_path& target, const dir_path& link) + { + mkhardlink (target, link, true); + } + + // File copy flags. + // + enum class cpflags: std::uint16_t + { + overwrite_content = 0x1, + overwrite_permissions = 0x2, + + none = 0 + }; + + inline cpflags operator& (cpflags, cpflags); + inline cpflags operator| (cpflags, cpflags); + inline cpflags operator&= (cpflags&, cpflags); + inline cpflags operator|= (cpflags&, cpflags); + + // Copy a regular file, including its permissions. 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. + // + // Note that in case of overwriting, the existing destination file gets + // truncated (not deleted) prior to being overwritten. As a side-effect, + // hard link to the destination file will still reference the same file + // system node after the copy. + // + // Also note that if the overwrite_content flag is not set and the + // destination is a dangling symbolic link, then this function will still + // fail. + // + LIBBUTL_SYMEXPORT void + cpfile (const path& from, const path& to, cpflags = cpflags::none); + + // Copy a regular file into (inside) an existing directory. + // + inline void + cpfile_into (const path& from, + const dir_path& into, + cpflags fl = cpflags::none) + { + cpfile (from, into / from.leaf (), fl); + } + + // Rename a filesystem entry (file, symlink, or directory). Throw + // std::system_error on failure. + // + // If the source path refers to a directory, then the destination path must + // either not exist, or refer to an empty directory. If the source path + // refers to an entry that is not a directory, then the destination path must + // not exist or not refer to a directory. + // + // If the source path refers to a symlink, then the link is renamed. If the + // destination path refers to a symlink, then the link will be overwritten. + // + // If the source and destination paths are on different file systems (or + // different drives on Windows) and the underlying OS does not support move + // for the source entry, then fail unless the source paths refers to a file + // or a file symlink. In this case fall back to copying the source file + // (content, permissions, access and modification times) and removing the + // source entry afterwards. + // + // Note that the operation is atomic only on POSIX, only if source and + // destination paths are on the same file system, and only if the + // overwrite_content flag is specified. + // + LIBBUTL_SYMEXPORT void + mventry (const path& from, + const path& to, + cpflags = cpflags::overwrite_permissions); + + // Move a filesystem entry into (inside) an existing directory. + // + inline void + mventry_into (const path& from, + const dir_path& into, + cpflags f = cpflags::overwrite_permissions) + { + mventry (from, into / from.leaf (), f); + } + + // Raname file or file symlink. + // + inline void + mvfile (const path& from, + const path& to, + cpflags f = cpflags::overwrite_permissions) + { + mventry (from, to, f); + } + + inline void + mvfile_into (const path& from, + const dir_path& into, + cpflags f = cpflags::overwrite_permissions) + { + mventry_into (from, into, f); + } + + // Raname directory or directory symlink. + // + inline void + mvdir (const dir_path& from, + const dir_path& to, + cpflags f = cpflags::overwrite_permissions) + { + mventry (from, to, f); + } + + inline void + mvdir_into (const path& from, + const dir_path& into, + cpflags f = cpflags::overwrite_permissions) + { + mventry_into (from, into, f); + } + + // Return timestamp_nonexistent if the entry at the specified path + // does not exist or is not a path. All other errors are reported + // by throwing std::system_error. Note that this function resolves + // symlinks. + // + LIBBUTL_SYMEXPORT timestamp + file_mtime (const char*); + + inline timestamp + file_mtime (const path& p) {return file_mtime (p.string ().c_str ());} + + // 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. + // + LIBBUTL_SYMEXPORT permissions + path_permissions (const path&); + + // Set path permissions. Throw std::system_error on failure. Note that this + // function resolves symlinks. + // + LIBBUTL_SYMEXPORT void + path_permissions (const path&, permissions); + + // Directory entry iteration. + // + class LIBBUTL_SYMEXPORT dir_entry + { + public: + typedef butl::path path_type; + + // Symlink target type in case of the symlink, ltype() otherwise. + // + entry_type + type () const; + + entry_type + ltype () const; + + // Entry path (excluding the base). To get the full path, do + // base () / path (). + // + const path_type& + path () const {return p_;} + + const dir_path& + 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)) {} + + private: + entry_type + type (bool link) const; + + private: + friend class dir_iterator; + + mutable entry_type t_ = entry_type::unknown; // Lazy evaluation. + mutable entry_type lt_ = entry_type::unknown; // Lazy evaluation. + path_type p_; + dir_path b_; + }; + + class LIBBUTL_SYMEXPORT dir_iterator + { + public: + typedef dir_entry value_type; + typedef const dir_entry* pointer; + typedef const dir_entry& reference; + typedef std::ptrdiff_t difference_type; + typedef std::input_iterator_tag iterator_category; + + ~dir_iterator (); + dir_iterator () = default; + + explicit + dir_iterator (const dir_path&); + + dir_iterator (const dir_iterator&) = delete; + dir_iterator& operator= (const dir_iterator&) = delete; + + dir_iterator (dir_iterator&&) noexcept; + dir_iterator& operator= (dir_iterator&&); + + dir_iterator& operator++ () {next (); return *this;} + + reference operator* () const {return e_;} + pointer operator-> () const {return &e_;} + + friend bool operator== (const dir_iterator&, const dir_iterator&); + friend bool operator!= (const dir_iterator&, const dir_iterator&); + + private: + void + next (); + + private: + dir_entry e_; + +#ifndef _WIN32 + DIR* h_ = nullptr; +#else + intptr_t h_ = -1; +#endif + }; + + // Range-based for loop support. + // + // for (const auto& de: dir_iterator (dir_path ("/tmp"))) ... + // + // Note that the "range" (which is the "begin" iterator), is no + // longer usable. In other words, don't do this: + // + // dir_iterator i (...); + // for (...: i) ... + // ++i; // Invalid. + // + inline dir_iterator begin (dir_iterator&); + inline dir_iterator end (const dir_iterator&); + + // Wildcard pattern match and search (aka glob). + // + // Currently the following wildcard characters are supported: + // + // * - match any number of characters (including zero) + // ? - match any single character + + // Return true if name matches pattern. Both must be single path components, + // possibly with a trailing directory separator to indicate a directory. + // + // If the pattern ends with a directory separator, then it only matches a + // directory name (i.e., ends with a directory separator, but potentially + // different). Otherwise, it only matches a non-directory name (no trailing + // directory separator). + // + LIBBUTL_SYMEXPORT bool + path_match (const std::string& pattern, const std::string& name); + + // Return true if path entry matches pattern. Note that the match is + // performed literally, with no paths normalization being performed. The + // start directory is used if the first pattern component is a self-matching + // wildcard (see below for the start directory and wildcard semantics). + // + LIBBUTL_SYMEXPORT bool + path_match (const path& pattern, + const path& entry, + const dir_path& start = dir_path ()); + + // Search for paths matching the pattern calling the specified function for + // each matching path (see below for details). + // + // If the pattern is relative, then search in the start directory. If the + // start directory is empty, then search in the current working directory. + // Searching in non-existent directories is not an error. Throw + // std::system_error in case of a failure (insufficient permissions, etc). + // + // The pattern may contain multiple components that include wildcards. On + // Windows the drive letter may not be a wildcard. + // + // In addition to the wildcard characters listed in path_match(), + // path_search() also recognizes the ** and *** wildcard sequences. If a + // path component contains **, then it is matched just like * but in all the + // subdirectories, recursively. The *** wildcard behaves like ** but also + // matches the start directory itself. Note that if the first pattern + // component contains ***, then the start directory must be empty or be + // terminated with a "meaningful" component (e.g., probably not '.' or + // '..'). + // + // So, for example, foo/bar-**.txt will return all the files matching the + // bar-*.txt pattern in all the subdirectoris of foo/. And foo/f***/ will + // return all the subdirectories matching the f*/ pattern plus foo/ itself. + // + // Note that having multiple recursive components in the pattern we can end + // up with calling func() multiple times (once per such a component) for the + // same path. For example the search with pattern f***/b**/ starting in + // directory foo, that has the foo/fox/box/ structure, will result in + // calling func(foo/fox/box/) twice: first time for being a child of fox/, + // second time for being a child of foo/. + // + // The callback function is called for both intermediate matches (interm is + // true) and final matches (interm is false). Pattern is what matched the + // last component in the path and is empty if the last component is not a + // pattern (final match only; say as in */foo.txt). + // + // If the callback function returns false for an intermediate path, then no + // further search is performed at or below this path. If false is returned + // for a final match, then the entire search is stopped. + // + // The path can be moved for the final match or for an intermediate match + // but only if false is returned. + // + // As an example, consider pattern f*/bar/b*/*.txt and path + // foo/bar/baz/x.txt. The sequence of calls in this case will be: + // + // (foo/, f*/, true) + // (foo/bar/baz/, b*/, true) + // (foo/bar/baz/x.txt, *.txt, false) + // + // If the pattern contains a recursive wildcard, then the callback function + // can be called for the same directory twice: first time as an intermediate + // match with */ pattern to decide if to recursively traverse the directory, + // and the second time if the directory matches the pattern component (either + // as an intermediate or a final match). As an example, consider pattern + // b**/c* and directory tree a/b/c/. The sequence of calls in this case will + // be: + // + // (a/, */, true) + // (a/b/, */ true) + // (a/b/c/, */, true) + // (a/b/, b*/, true) + // (a/b/c/, c*/, false) + // + // Symlinks are not followed if the follow_symlinks argument is false. This + // rule is only applied for symlinks that are matched against the rightmost + // component of the pattern. In particular, this mean that such symlinks will + // never match a directory pattern, and some results can be missing for the + // recursive rightmost component. + // + // 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. + // + LIBBUTL_SYMEXPORT void + path_search (const path& pattern, + const std::function&, + const dir_path& start = dir_path (), + bool follow_symlinks = true); + + // Same as above, but behaves as if the directory tree being searched + // through contains only the specified entry. The start directory is used if + // the first pattern component is a self-matching wildcard (see above). + // + // If pattern or entry is relative, then it is assumed to be relative to the + // start directory (which, if relative itself, is assumed to be relative to + // the current directory). Note that the implementation can optimize the + // case when pattern and entry are both non-empty and relative. + // + LIBBUTL_SYMEXPORT void + path_search (const path& pattern, + const path& entry, + const std::function&, + const dir_path& start = dir_path ()); +} + +#include diff --git a/libbutl/ft/exception.hxx b/libbutl/ft/exception.hxx index a5f6156..14d9ced 100644 --- a/libbutl/ft/exception.hxx +++ b/libbutl/ft/exception.hxx @@ -5,8 +5,11 @@ #ifndef LIBBUTL_FT_EXCEPTION_HXX #define LIBBUTL_FT_EXCEPTION_HXX -#include // _LIBCPP_VERSION -#include +#if defined(__clang__) +# if __has_include(<__config>) +# include <__config> // _LIBCPP_VERSION +# endif +#endif // __cpp_lib_uncaught_exceptions // diff --git a/libbutl/ft/shared_mutex.hxx b/libbutl/ft/shared_mutex.hxx index 6c6d415..a0346fc 100644 --- a/libbutl/ft/shared_mutex.hxx +++ b/libbutl/ft/shared_mutex.hxx @@ -5,8 +5,11 @@ #ifndef LIBBUTL_FT_SHARED_MUTEX_HXX #define LIBBUTL_FT_SHARED_MUTEX_HXX -#include // _LIBCPP_VERSION -#include +#if defined(__clang__) +# if __has_include(<__config>) +# include <__config> // _LIBCPP_VERSION +# endif +#endif // __cpp_lib_shared_mutex // diff --git a/libbutl/manifest-forward.hxx b/libbutl/manifest-forward.hxx index c5aa356..e221fdf 100644 --- a/libbutl/manifest-forward.hxx +++ b/libbutl/manifest-forward.hxx @@ -2,8 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#ifndef LIBBUTL_MANIFEST_FORWARD_HXX -#define LIBBUTL_MANIFEST_FORWARD_HXX +#pragma once namespace butl { @@ -21,5 +20,3 @@ namespace butl fail }; } - -#endif // LIBBUTL_MANIFEST_FORWARD_HXX diff --git a/libbutl/manifest-parser.cxx b/libbutl/manifest-parser.cxx index 304815a..cea71b6 100644 --- a/libbutl/manifest-parser.cxx +++ b/libbutl/manifest-parser.cxx @@ -2,10 +2,36 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif #include + +#ifndef __cpp_lib_modules +#include +#include +#include +#include + #include +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.manifest_parser; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.char_scanner; +#endif + +#endif using namespace std; @@ -410,7 +436,7 @@ namespace butl // manifest_parsing // - static string + static inline string format (const string& n, uint64_t l, uint64_t c, const string& d) { ostringstream os; diff --git a/libbutl/manifest-parser.hxx b/libbutl/manifest-parser.hxx deleted file mode 100644 index f84badd..0000000 --- a/libbutl/manifest-parser.hxx +++ /dev/null @@ -1,102 +0,0 @@ -// file : libbutl/manifest-parser.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_MANIFEST_PARSER_HXX -#define LIBBUTL_MANIFEST_PARSER_HXX - -#include -#include -#include // uint64_t -#include // pair -#include // runtime_error - -#include - -#include - -namespace butl -{ - class LIBBUTL_SYMEXPORT manifest_parsing: public std::runtime_error - { - public: - manifest_parsing (const std::string& name, - std::uint64_t line, - std::uint64_t column, - const std::string& description); - - std::string name; - std::uint64_t line; - std::uint64_t column; - std::string description; - }; - - class manifest_name_value - { - public: - std::string name; - std::string value; - - std::uint64_t name_line; - std::uint64_t name_column; - - std::uint64_t value_line; - std::uint64_t value_column; - - bool - empty () const {return name.empty () && value.empty ();} - }; - - class LIBBUTL_SYMEXPORT manifest_parser: protected butl::char_scanner - { - public: - manifest_parser (std::istream& is, const std::string& name) - : char_scanner (is), name_ (name) {} - - const std::string& - name () const {return name_;} - - // The first returned pair is special "start-of-manifest" with - // empty name and value being the format version: {"", ""}. - // After that we have a sequence of ordinary pairs which are - // the manifest. At the end of the manifest we have the special - // "end-of-manifest" pair with empty name and value: {"", ""}. - // After that we can either get another start-of-manifest pair - // (in which case the whole sequence repeats from the beginning) - // or we get another end-of-manifest pair which signals the end - // of stream (aka EOF). To put it another way, the parse sequence - // always has the following form: - // - // ({"", ""} {"", ""}* {"", ""})* {"", ""} - // - manifest_name_value - next (); - - // Split the manifest value, optionally followed by ';' character and a - // comment into the value/comment pair. Note that ';' characters in the - // value must be escaped by the backslash. - // - static std::pair - split_comment (const std::string&); - - private: - void - parse_name (manifest_name_value&); - - void - parse_value (manifest_name_value&); - - // Skip spaces and return the first peeked non-space character. - // - xchar - skip_spaces (); - - private: - const std::string name_; - - enum {start, body, end} s_ = start; - std::string version_; // Current format version. - }; -} - -#endif // LIBBUTL_MANIFEST_PARSER_HXX diff --git a/libbutl/manifest-parser.mxx b/libbutl/manifest-parser.mxx new file mode 100644 index 0000000..c21ccc1 --- /dev/null +++ b/libbutl/manifest-parser.mxx @@ -0,0 +1,116 @@ +// file : libbutl/manifest-parser.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#include // uint64_t +#include // pair +#include // runtime_error +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.manifest_parser; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.char_scanner; +#else +#include +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + class LIBBUTL_SYMEXPORT manifest_parsing: public std::runtime_error + { + public: + manifest_parsing (const std::string& name, + std::uint64_t line, + std::uint64_t column, + const std::string& description); + + std::string name; + std::uint64_t line; + std::uint64_t column; + std::string description; + }; + + class manifest_name_value + { + public: + std::string name; + std::string value; + + std::uint64_t name_line; + std::uint64_t name_column; + + std::uint64_t value_line; + std::uint64_t value_column; + + bool + empty () const {return name.empty () && value.empty ();} + }; + + class LIBBUTL_SYMEXPORT manifest_parser: protected butl::char_scanner + { + public: + manifest_parser (std::istream& is, const std::string& name) + : char_scanner (is), name_ (name) {} + + const std::string& + name () const {return name_;} + + // The first returned pair is special "start-of-manifest" with + // empty name and value being the format version: {"", ""}. + // After that we have a sequence of ordinary pairs which are + // the manifest. At the end of the manifest we have the special + // "end-of-manifest" pair with empty name and value: {"", ""}. + // After that we can either get another start-of-manifest pair + // (in which case the whole sequence repeats from the beginning) + // or we get another end-of-manifest pair which signals the end + // of stream (aka EOF). To put it another way, the parse sequence + // always has the following form: + // + // ({"", ""} {"", ""}* {"", ""})* {"", ""} + // + manifest_name_value + next (); + + // Split the manifest value, optionally followed by ';' character and a + // comment into the value/comment pair. Note that ';' characters in the + // value must be escaped by the backslash. + // + static std::pair + split_comment (const std::string&); + + private: + void + parse_name (manifest_name_value&); + + void + parse_value (manifest_name_value&); + + // Skip spaces and return the first peeked non-space character. + // + xchar + skip_spaces (); + + private: + const std::string name_; + + enum {start, body, end} s_ = start; + std::string version_; // Current format version. + }; +} diff --git a/libbutl/manifest-serializer.cxx b/libbutl/manifest-serializer.cxx index d8f640b..0693c2e 100644 --- a/libbutl/manifest-serializer.cxx +++ b/libbutl/manifest-serializer.cxx @@ -2,11 +2,36 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif -#include #include +#ifndef __cpp_lib_modules +#include +#include +#include + +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.manifest_serializer; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.char_scanner; +#endif + +#endif + using namespace std; namespace butl @@ -257,7 +282,7 @@ namespace butl // manifest_serialization // - static string + static inline string format (const string& n, const string& d) { string r; diff --git a/libbutl/manifest-serializer.hxx b/libbutl/manifest-serializer.hxx deleted file mode 100644 index d95ddf1..0000000 --- a/libbutl/manifest-serializer.hxx +++ /dev/null @@ -1,82 +0,0 @@ -// file : libbutl/manifest-serializer.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_MANIFEST_SERIALIZER_HXX -#define LIBBUTL_MANIFEST_SERIALIZER_HXX - -#include -#include -#include // size_t -#include // runtime_error - -#include - -namespace butl -{ - class LIBBUTL_SYMEXPORT manifest_serialization: public std::runtime_error - { - public: - manifest_serialization (const std::string& name, - const std::string& description); - - std::string name; - std::string description; - }; - - class LIBBUTL_SYMEXPORT manifest_serializer - { - public: - manifest_serializer (std::ostream& os, const std::string& name) - : os_ (os), name_ (name) {} - - const std::string& - name () const {return name_;} - - // The first name-value pair should be the special "start-of-manifest" - // with empty name and value being the format version. After that we - // have a sequence of ordinary pairs which are the manifest. At the - // end of the manifest we have the special "end-of-manifest" pair - // with empty name and value. After that we can either have another - // start-of-manifest pair (in which case the whole sequence repeats - // from the beginning) or we get another end-of-manifest pair which - // signals the end of stream. - // - void - next (const std::string& name, const std::string& value); - - // Write a comment. The supplied text is prefixed with "# " and - // terminated with a newline. - // - void - comment (const std::string&); - - // Merge the manifest value and a comment into the single string, having - // the '; ' form. Escape ';' characters in the value with - // the backslash. - // - static std::string - merge_comment (const std::string& value, const std::string& comment); - - private: - void - check_name (const std::string&); - - // Write 'n' characters from 's' (assuming there are no newlines) - // split into multiple lines at or near the 78 characters - // boundary. The first line starts at the 'column' offset. - // - void - write_value (std::size_t column, const char* s, std::size_t n); - - private: - enum {start, body, end} s_ = start; - std::string version_; // Current format version. - - private: - std::ostream& os_; - const std::string name_; - }; -} - -#endif // LIBBUTL_MANIFEST_SERIALIZER_HXX diff --git a/libbutl/manifest-serializer.mxx b/libbutl/manifest-serializer.mxx new file mode 100644 index 0000000..e468786 --- /dev/null +++ b/libbutl/manifest-serializer.mxx @@ -0,0 +1,95 @@ +// file : libbutl/manifest-serializer.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#include // size_t +#include // runtime_error +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.manifest_serializer; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + class LIBBUTL_SYMEXPORT manifest_serialization: public std::runtime_error + { + public: + manifest_serialization (const std::string& name, + const std::string& description); + + std::string name; + std::string description; + }; + + class LIBBUTL_SYMEXPORT manifest_serializer + { + public: + manifest_serializer (std::ostream& os, const std::string& name) + : os_ (os), name_ (name) {} + + const std::string& + name () const {return name_;} + + // The first name-value pair should be the special "start-of-manifest" + // with empty name and value being the format version. After that we + // have a sequence of ordinary pairs which are the manifest. At the + // end of the manifest we have the special "end-of-manifest" pair + // with empty name and value. After that we can either have another + // start-of-manifest pair (in which case the whole sequence repeats + // from the beginning) or we get another end-of-manifest pair which + // signals the end of stream. + // + void + next (const std::string& name, const std::string& value); + + // Write a comment. The supplied text is prefixed with "# " and + // terminated with a newline. + // + void + comment (const std::string&); + + // Merge the manifest value and a comment into the single string, having + // the '; ' form. Escape ';' characters in the value with + // the backslash. + // + static std::string + merge_comment (const std::string& value, const std::string& comment); + + private: + void + check_name (const std::string&); + + // Write 'n' characters from 's' (assuming there are no newlines) + // split into multiple lines at or near the 78 characters + // boundary. The first line starts at the 'column' offset. + // + void + write_value (std::size_t column, const char* s, std::size_t n); + + private: + enum {start, body, end} s_ = start; + std::string version_; // Current format version. + + private: + std::ostream& os_; + const std::string name_; + }; +} diff --git a/libbutl/multi-index.hxx b/libbutl/multi-index.hxx deleted file mode 100644 index 448e1f7..0000000 --- a/libbutl/multi-index.hxx +++ /dev/null @@ -1,59 +0,0 @@ -// file : libbutl/multi-index.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_MULTI_INDEX_HXX -#define LIBBUTL_MULTI_INDEX_HXX - -#include // declval() -#include // hash - -namespace butl -{ - // Google the "Emulating Boost.MultiIndex with Standard Containers" blog - // post for details. - // - - template - struct map_key - { - mutable const T* p; - - map_key (const T* v = 0): p (v) {} - bool operator< (const map_key& x) const {return *p < *x.p;} - bool operator== (const map_key& x) const {return *p == *x.p;} - }; - - template - struct map_iterator_adapter: I - { - typedef const typename I::value_type::second_type value_type; - typedef value_type* pointer; - typedef value_type& reference; - - map_iterator_adapter () {} - map_iterator_adapter (I i): I (i) {} - - map_iterator_adapter& - operator= (I i) {static_cast (*this) = i; return *this;} - - reference operator* () const {return I::operator* ().second;} - pointer operator-> () const {return &I::operator-> ()->second;} - }; -} - -namespace std -{ - template - struct hash>: hash - { - size_t - operator() (butl::map_key x) const - noexcept (noexcept (declval> () (*x.p))) - { - return hash::operator() (*x.p); - } - }; -} - -#endif // LIBBUTL_MULTI_INDEX_HXX diff --git a/libbutl/multi-index.mxx b/libbutl/multi-index.mxx new file mode 100644 index 0000000..f0ef298 --- /dev/null +++ b/libbutl/multi-index.mxx @@ -0,0 +1,73 @@ +// file : libbutl/multi-index.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include // declval() +#include // hash +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.multi_index; +#ifdef __cpp_lib_modules +import std.core; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // Google the "Emulating Boost.MultiIndex with Standard Containers" blog + // post for details. + // + + template + struct map_key + { + mutable const T* p; + + map_key (const T* v = 0): p (v) {} + bool operator< (const map_key& x) const {return *p < *x.p;} + bool operator== (const map_key& x) const {return *p == *x.p;} + }; + + template + struct map_iterator_adapter: I + { + typedef const typename I::value_type::second_type value_type; + typedef value_type* pointer; + typedef value_type& reference; + + map_iterator_adapter () {} + map_iterator_adapter (I i): I (i) {} + + map_iterator_adapter& + operator= (I i) {static_cast (*this) = i; return *this;} + + reference operator* () const {return I::operator* ().second;} + pointer operator-> () const {return &I::operator-> ()->second;} + }; +} + +LIBBUTL_MODEXPORT namespace std +{ + template + struct hash>: hash + { + size_t + operator() (butl::map_key x) const + noexcept (noexcept (declval> () (*x.p))) + { + return hash::operator() (*x.p); + } + }; +} diff --git a/libbutl/openssl.cxx b/libbutl/openssl.cxx index aa4e720..89cf8ed 100644 --- a/libbutl/openssl.cxx +++ b/libbutl/openssl.cxx @@ -2,9 +2,35 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif + +#include + +#ifndef __cpp_lib_modules +#include #include // move() +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.openssl; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +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.hxx b/libbutl/openssl.hxx deleted file mode 100644 index 9ab248c..0000000 --- a/libbutl/openssl.hxx +++ /dev/null @@ -1,164 +0,0 @@ -// file : libbutl/openssl.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_OPENSSL_HXX -#define LIBBUTL_OPENSSL_HXX - -#include -#include - -#include - -#include -#include -#include - -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 - // errors. - // - // The I (in) and O (out) can be of the following types/values: - // - // nullfd Signal that no input/output is expected. - // - // path Read input/write output from/to a file. If the special "-" - // value is used, then instead input is connected to the - // openssl::out ofdstream member and output -- to the - // openssl::in ifdstream member. Note that the argument type - // should be path, not string (i.e., pass path("-")). Also - // note that the streams are opened in the binary mode. To - // change that, use fdstream_mode::text instead (see below). - // - // fdstream_mode Only text and binary values are meaningful. Same as - // path("-"), but also specifies the translation mode. - // - // other Forwarded as is to process_start(). Normally either int or - // auto_fd. - // - // For example: - // - // openssl os (path ("key.pub.pem"), // Read key from file, - // path ("-"), // Write result to openssl::in. - // 2, - // "openssl", "pkey", - // "-pubin", "-outform", "DER"); - // - // Typical usage: - // - // try - // { - // openssl os (nullfd, // No input expected. - // path ("-"), // Output to openssl::in. - // 2, // Diagnostics to stderr. - // path ("openssl"), // Program path. - // "rand", // Command. - // 64); // Command options. - // - // vector r (os.in.read_binary ()); - // os.in.close (); - // - // if (!os.wait ()) - // ... // openssl returned non-zero status. - // } - // catch (const system_error& e) - // { - // cerr << "openssl error: " << e << endl; - // } - // - // Notes: - // - // 1. If opened, in stream is in the skip mode (see fdstream_mode). - // - // 2. If opened, in/out must be explicitly closed before calling wait(). - // - // 3. Normally the order of options is not important (unless they override - // each other). However, openssl 1.0.1 seems to have bugs in that - // department (that were apparently fixed in 1.0.2). To work around these - // bugs pass user-supplied options first. - // - class LIBBUTL_SYMEXPORT openssl: public process - { - public: - ifdstream in; - ofdstream out; - - template - openssl (I&& in, - O&& out, - E&& err, - const process_env&, - const std::string& command, - A&&... options); - - // Version with the command line callback (see process_run_callback() for - // details). - // - template - openssl (const C&, - I&& in, - O&& out, - E&& err, - const process_env&, - const std::string& command, - A&&... options); - - private: - template - struct is_other - { - using type = typename std::remove_reference< - typename std::remove_cv::type>::type; - - static const bool value = !(std::is_same::value || - std::is_same::value || - std::is_same::value); - }; - - struct io_data - { - fdpipe pipe; - small_vector options; - }; - - int - map_in (nullfd_t, io_data&); - - int - map_in (const path&, io_data&); - - int - map_in (fdstream_mode, io_data&); - - template - typename std::enable_if::value, I>::type - map_in (I&&, io_data&); - - int - map_out (nullfd_t, io_data&); - - int - map_out (const path&, io_data&); - - int - map_out (fdstream_mode, io_data&); - - template - typename std::enable_if::value, O>::type - map_out (O&&, io_data&); - }; -} - -#include -#include - -#endif // LIBBUTL_OPENSSL_HXX diff --git a/libbutl/openssl.ixx b/libbutl/openssl.ixx index 2976659..3ef56ed 100644 --- a/libbutl/openssl.ixx +++ b/libbutl/openssl.ixx @@ -2,10 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // size_t -#include // move(), forward() - -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { template +#include + +#include // size_t +#include // move(), forward() +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.openssl; +#ifdef __cpp_lib_modules +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 +#include +#include +#include +#endif + +#include + +LIBBUTL_MODEXPORT 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 + // errors. + // + // The I (in) and O (out) can be of the following types/values: + // + // nullfd Signal that no input/output is expected. + // + // path Read input/write output from/to a file. If the special "-" + // value is used, then instead input is connected to the + // openssl::out ofdstream member and output -- to the + // openssl::in ifdstream member. Note that the argument type + // should be path, not string (i.e., pass path("-")). Also + // note that the streams are opened in the binary mode. To + // change that, use fdstream_mode::text instead (see below). + // + // fdstream_mode Only text and binary values are meaningful. Same as + // path("-"), but also specifies the translation mode. + // + // other Forwarded as is to process_start(). Normally either int or + // auto_fd. + // + // For example: + // + // openssl os (path ("key.pub.pem"), // Read key from file, + // path ("-"), // Write result to openssl::in. + // 2, + // "openssl", "pkey", + // "-pubin", "-outform", "DER"); + // + // Typical usage: + // + // try + // { + // openssl os (nullfd, // No input expected. + // path ("-"), // Output to openssl::in. + // 2, // Diagnostics to stderr. + // path ("openssl"), // Program path. + // "rand", // Command. + // 64); // Command options. + // + // vector r (os.in.read_binary ()); + // os.in.close (); + // + // if (!os.wait ()) + // ... // openssl returned non-zero status. + // } + // catch (const system_error& e) + // { + // cerr << "openssl error: " << e << endl; + // } + // + // Notes: + // + // 1. If opened, in stream is in the skip mode (see fdstream_mode). + // + // 2. If opened, in/out must be explicitly closed before calling wait(). + // + // 3. Normally the order of options is not important (unless they override + // each other). However, openssl 1.0.1 seems to have bugs in that + // department (that were apparently fixed in 1.0.2). To work around these + // bugs pass user-supplied options first. + // + class LIBBUTL_SYMEXPORT openssl: public process + { + public: + ifdstream in; + ofdstream out; + + template + openssl (I&& in, + O&& out, + E&& err, + const process_env&, + const std::string& command, + A&&... options); + + // Version with the command line callback (see process_run_callback() for + // details). + // + template + openssl (const C&, + I&& in, + O&& out, + E&& err, + const process_env&, + const std::string& command, + A&&... options); + + private: + template + struct is_other + { + using type = typename std::remove_reference< + typename std::remove_cv::type>::type; + + static const bool value = !(std::is_same::value || + std::is_same::value || + std::is_same::value); + }; + + struct io_data + { + fdpipe pipe; + small_vector options; + }; + + int + map_in (nullfd_t, io_data&); + + int + map_in (const path&, io_data&); + + int + map_in (fdstream_mode, io_data&); + + template + typename std::enable_if::value, I>::type + map_in (I&&, io_data&); + + int + map_out (nullfd_t, io_data&); + + int + map_out (const path&, io_data&); + + int + map_out (fdstream_mode, io_data&); + + template + typename std::enable_if::value, O>::type + map_out (O&&, io_data&); + }; +} + +#include +#include diff --git a/libbutl/openssl.txx b/libbutl/openssl.txx index dd8a470..29dc4b1 100644 --- a/libbutl/openssl.txx +++ b/libbutl/openssl.txx @@ -2,9 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // move(), forward() - -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { template typename std::enable_if::value, I>::type openssl:: diff --git a/libbutl/optional.hxx b/libbutl/optional.hxx deleted file mode 100644 index c1bc582..0000000 --- a/libbutl/optional.hxx +++ /dev/null @@ -1,96 +0,0 @@ -// file : libbutl/optional.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_OPTIONAL_HXX -#define LIBBUTL_OPTIONAL_HXX - -#include // move() -#include // hash - -namespace butl -{ - // Simple optional class template while waiting for std::optional. - // - struct nullopt_t {constexpr explicit nullopt_t (int) {}}; - constexpr const nullopt_t nullopt (1); - - template - class optional - { - public: - typedef T value_type; - - constexpr optional (): value_ (), null_ (true) {} // VC14 needs value_(). - constexpr optional (nullopt_t): value_ (), null_ (true) {} - constexpr optional (const T& v): value_ (v), null_ (false) {} - constexpr optional (T&& v): value_ (std::move (v)), null_ (false) {} - - optional& operator= (nullopt_t) {value_ = T (); null_ = true; return *this;} - optional& operator= (const T& v) {value_ = v; null_ = false; return *this;} - optional& operator= (T&& v) {value_ = std::move (v); null_ = false; return *this;} - - T& value () {return value_;} - const T& value () const {return value_;} - - T* operator-> () {return &value_;} - const T* operator-> () const {return &value_;} - - T& operator* () {return value_;} - const T& operator* () const {return value_;} - - bool has_value () const {return !null_;} - explicit operator bool () const {return !null_;} - - private: - T value_; - bool null_; - }; - - template - inline auto - operator== (const optional& x, const optional& y) -> decltype (*x == *y) - { - return static_cast (x) == static_cast (y) && (!x || *x == *y); - } - - template - inline auto - operator!= (const optional& x, const optional& y) -> decltype (x == y) - { - return !(x == y); - } - - template - inline auto - operator< (const optional& x, const optional& y) -> decltype (*x < *y) - { - bool px (x), py (y); - return px < py || (px && py && *x < *y); - } - - template - inline auto - operator> (const optional& x, const optional& y) -> decltype (*x > *y) - { - return y < x; - } -} - -namespace std -{ - template - struct hash>: hash - { - using argument_type = butl::optional; - - size_t - operator() (const butl::optional& o) const - noexcept (noexcept (hash {} (*o))) - { - return o ? hash::operator() (*o) : static_cast (-3333); - } - }; -} - -#endif // LIBBUTL_OPTIONAL_HXX diff --git a/libbutl/optional.mxx b/libbutl/optional.mxx new file mode 100644 index 0000000..4478f84 --- /dev/null +++ b/libbutl/optional.mxx @@ -0,0 +1,113 @@ +// file : libbutl/optional.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include // move() +#include // hash +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.optional; +#ifdef __cpp_lib_modules +import std.core; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // Simple optional class template while waiting for std::optional. + // + struct nullopt_t {constexpr explicit nullopt_t (int) {}}; +#if defined(__cpp_modules) && defined(__clang__) //@@ MOD Clang duplicate sym. + inline +#endif + const/*expr*/ nullopt_t nullopt (1); //@@ MOD VC multiple defs. + + template + class optional + { + public: + typedef T value_type; + + constexpr optional (): value_ (), null_ (true) {} // VC14 needs value_(). + constexpr optional (nullopt_t): value_ (), null_ (true) {} + constexpr optional (const T& v): value_ (v), null_ (false) {} + constexpr optional (T&& v): value_ (std::move (v)), null_ (false) {} + + optional& operator= (nullopt_t) {value_ = T (); null_ = true; return *this;} + optional& operator= (const T& v) {value_ = v; null_ = false; return *this;} + optional& operator= (T&& v) {value_ = std::move (v); null_ = false; return *this;} + + T& value () {return value_;} + const T& value () const {return value_;} + + T* operator-> () {return &value_;} + const T* operator-> () const {return &value_;} + + T& operator* () {return value_;} + const T& operator* () const {return value_;} + + bool has_value () const {return !null_;} + explicit operator bool () const {return !null_;} + + private: + T value_; + bool null_; + }; + + template + inline auto + operator== (const optional& x, const optional& y) -> decltype (*x == *y) + { + return static_cast (x) == static_cast (y) && (!x || *x == *y); + } + + template + inline auto + operator!= (const optional& x, const optional& y) -> decltype (x == y) + { + return !(x == y); + } + + template + inline auto + operator< (const optional& x, const optional& y) -> decltype (*x < *y) + { + bool px (x), py (y); + return px < py || (px && py && *x < *y); + } + + template + inline auto + operator> (const optional& x, const optional& y) -> decltype (*x > *y) + { + return y < x; + } +} + +namespace std +{ + template + struct hash>: hash + { + using argument_type = butl::optional; + + size_t + operator() (const butl::optional& o) const + noexcept (noexcept (hash {} (*o))) + { + return o ? hash::operator() (*o) : static_cast (-3333); + } + }; +} diff --git a/libbutl/pager.cxx b/libbutl/pager.cxx index 3910443..6453120 100644 --- a/libbutl/pager.cxx +++ b/libbutl/pager.cxx @@ -2,24 +2,59 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif + +#include // E* #ifndef _WIN32 # include // STDOUT_FILENO # include // ioctl() +#else +# include +#endif +#ifndef __cpp_lib_modules +#include +#include +#include + +#include // strchr() +#include // move() +#ifndef _WIN32 # include # include // this_thread::sleep_for() -#else -# include +#endif #endif -#include // strchr() -#include // move() +// Other includes. -#include // operator<<(ostream, exception), - // throw_generic_error() -#include // fdclose() +#ifdef __cpp_modules +module butl.pager; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.process; +import butl.fdstream; +#endif + +#ifndef _WIN32 +import std.core; //@@ MOD TODO: import std.threading. +#endif + +import butl.utility; // operator<<(ostream, exception), throw_generic_error() +import butl.optional; +import butl.fdstream; // fdclose() +#else +#include +#include +#include +#endif using namespace std; diff --git a/libbutl/pager.hxx b/libbutl/pager.hxx deleted file mode 100644 index f167f10..0000000 --- a/libbutl/pager.hxx +++ /dev/null @@ -1,88 +0,0 @@ -// file : libbutl/pager.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_PAGER_HXX -#define LIBBUTL_PAGER_HXX - -#include -#include -#include - -#include - -#include -#include - -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 - // used, then the output is indented so that 80-character long lines will - // appear centered in the terminal. If the default pager program fails to - // start, then the output is sent directly to STDOUT. - // - // If the pager program is specified and is empty, then no pager is used - // and the output is sent directly to STDOUT. - // - // Throw std::system_error if there are problems with the pager program. - // - // Typical usage: - // - // try - // { - // pager p ("help for foo"); - // ostream& os (p.stream ()); - // - // os << "Foo is such and so ..."; - // - // if (!p.wait ()) - // ... // Pager program returned non-zero status. - // } - // catch (const std::system_error& e) - // { - // cerr << "pager error: " << e << endl; - // } - // - class LIBBUTL_SYMEXPORT pager: protected std::streambuf - { - public: - ~pager () {wait (true);} - - // If verbose is true, then print (to STDERR) the pager command line. - // - pager (const std::string& name, - bool verbose = false, - const std::string* pager = nullptr, - const std::vector* pager_options = nullptr); - - std::ostream& - stream () {return os_.is_open () ? os_ : std::cout;} - - bool - wait (bool ignore_errors = false); - - // The streambuf output interface that implements indentation. You can - // override it to implement custom output pre-processing. - // - protected: - using int_type = std::streambuf::int_type; - using traits_type = std::streambuf::traits_type; - - virtual int_type - overflow (int_type); - - virtual int - sync (); - - private: - process p_; - ofdstream os_; - - std::string indent_; - int_type prev_ = '\n'; // Previous character. - std::streambuf* buf_ = nullptr; - }; -} - -#endif // LIBBUTL_PAGER_HXX diff --git a/libbutl/pager.mxx b/libbutl/pager.mxx new file mode 100644 index 0000000..6955f53 --- /dev/null +++ b/libbutl/pager.mxx @@ -0,0 +1,103 @@ +// file : libbutl/pager.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.pager; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.process; +import butl.fdstream; +#else +#include +#include +#endif + +#include + +LIBBUTL_MODEXPORT 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 + // used, then the output is indented so that 80-character long lines will + // appear centered in the terminal. If the default pager program fails to + // start, then the output is sent directly to STDOUT. + // + // If the pager program is specified and is empty, then no pager is used + // and the output is sent directly to STDOUT. + // + // Throw std::system_error if there are problems with the pager program. + // + // Typical usage: + // + // try + // { + // pager p ("help for foo"); + // ostream& os (p.stream ()); + // + // os << "Foo is such and so ..."; + // + // if (!p.wait ()) + // ... // Pager program returned non-zero status. + // } + // catch (const std::system_error& e) + // { + // cerr << "pager error: " << e << endl; + // } + // + class LIBBUTL_SYMEXPORT pager: protected std::streambuf + { + public: + ~pager () {wait (true);} + + // If verbose is true, then print (to STDERR) the pager command line. + // + pager (const std::string& name, + bool verbose = false, + const std::string* pager = nullptr, + const std::vector* pager_options = nullptr); + + std::ostream& + stream () {return os_.is_open () ? os_ : std::cout;} + + bool + wait (bool ignore_errors = false); + + // The streambuf output interface that implements indentation. You can + // override it to implement custom output pre-processing. + // + protected: + using int_type = std::streambuf::int_type; + using traits_type = std::streambuf::traits_type; + + virtual int_type + overflow (int_type); + + virtual int + sync (); + + private: + process p_; + ofdstream os_; + + std::string indent_; + int_type prev_ = '\n'; // Previous character. + std::streambuf* buf_ = nullptr; + }; +} diff --git a/libbutl/path-io.hxx b/libbutl/path-io.hxx deleted file mode 100644 index 719456d..0000000 --- a/libbutl/path-io.hxx +++ /dev/null @@ -1,29 +0,0 @@ -// file : libbutl/path-io.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_PATH_IO_HXX -#define LIBBUTL_PATH_IO_HXX - -#include - -#include - -namespace butl -{ - // This is the default path IO implementation. The reason it is - // separate is because one often wants a custom implementation. - // For example, we may want to print paths as relative to the - // working directory. Or we may want to print '~' for the home - // directory prefix. Or we may want to print dir_path with a - // trailing '/'. - // - template - inline std::basic_ostream& - operator<< (std::basic_ostream& os, basic_path const& p) - { - return os << p.string (); - } -} - -#endif // LIBBUTL_PATH_IO_HXX diff --git a/libbutl/path-io.mxx b/libbutl/path-io.mxx new file mode 100644 index 0000000..ae1ee84 --- /dev/null +++ b/libbutl/path-io.mxx @@ -0,0 +1,45 @@ +// file : libbutl/path-io.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.path_io; +#ifdef __cpp_lib_modules +import std.core; //@@ MOD TMP (should not be needed). +import std.io; +#endif +import butl.path; +#else +#include +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // This is the default path IO implementation. The reason it is + // separate is because one often wants a custom implementation. + // For example, we may want to print paths as relative to the + // working directory. Or we may want to print '~' for the home + // directory prefix. Or we may want to print dir_path with a + // trailing '/'. + // + template + inline std::basic_ostream& + operator<< (std::basic_ostream& os, basic_path const& p) + { + return os << p.string (); + } +} diff --git a/libbutl/path-map.hxx b/libbutl/path-map.hxx deleted file mode 100644 index fa06bd5..0000000 --- a/libbutl/path-map.hxx +++ /dev/null @@ -1,148 +0,0 @@ -// file : libbutl/path-map.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_PATH_MAP_HXX -#define LIBBUTL_PATH_MAP_HXX - -#include // min() - -#include -#include - -namespace butl -{ - // prefix_map for filesystem paths - // - // Important: the paths should be normalized but can use different directory - // separators and different case on case-insensitive platforms. - // - // Note that the path's representation of POSIX root ('/') is inconsistent - // in that we have a trailing delimiter at the end of the path (its "proper" - // representation would have been an empty string but that would have - // clashed with empty paths). To work around this snag, this implementation, - // during key comparison, detects '/' and treats it as empty. Note that the - // map will still store the key as you have first inserted it. So if you - // want a particular representation (i.e., empty or '/'), pre- populate the - // map with it. - // - template - struct compare_prefix> - { - typedef basic_path key_type; - - typedef C delimiter_type; - typedef typename key_type::string_type string_type; - typedef typename key_type::size_type size_type; - typedef typename key_type::traits traits_type; - - explicit - compare_prefix (delimiter_type) {} - - bool - operator() (const key_type& x, const key_type& y) const - { - const string_type& xs (x.string ()); - const string_type& ys (y.string ()); - - return compare (xs.c_str (), - root (xs) ? 0 : xs.size (), - ys.c_str (), - root (ys) ? 0 : ys.size ()) < 0; - } - - bool - prefix (const key_type& p, const key_type& k) const - { - const string_type& ps (p.string ()); - const string_type& ks (k.string ()); - - return prefix (root (ps) ? string_type () : ps, - root (ks) ? string_type () : ks); - } - - protected: - bool - prefix (const string_type& p, const string_type& k) const - { - // The same code as in prefix_map but using our compare(). - // - size_type pn (p.size ()), kn (k.size ()); - return pn == 0 || // Empty key is always a prefix. - (pn <= kn && - compare (p.c_str (), pn, k.c_str (), pn == kn ? pn : pn + 1) == 0); - } - - int - compare (const C* x, size_type xn, - const C* y, size_type yn) const - { - size_type n (std::min (xn, yn)); - int r (traits_type::compare (x, n, y, n)); - - if (r == 0) - { - // Pretend there is a delimiter characters at the end of the - // shorter string. - // - char xc (xn > n ? x[n] : (xn++, traits_type::directory_separator)); - char yc (yn > n ? y[n] : (yn++, traits_type::directory_separator)); - r = traits_type::compare (&xc, 1, &yc, 1); - - // If we are still equal, then compare the lengths. - // - if (r == 0) - r = (xn == yn ? 0 : (xn < yn ? -1 : 1)); - } - - return r; - } - - static bool - root (const string_type& p) - { - return p.size () == 1 && key_type::traits::is_separator (p[0]); - } - }; - - // Note that the delimiter character is not used (is_delimiter() from - // path_traits is used instead). - // - template - struct path_map_impl: prefix_map - { - using base = prefix_map; - using base::base; - - using iterator = typename base::iterator; - using const_iterator = typename base::const_iterator; - - // Find the most qualified entry that is a superpath of this path. - // - iterator - find_sup (const P& p) - { - // Get the greatest less than, if any. We might still not be a sub. Note - // also that we still have to check the last element if upper_bound() - // returned end(). - // - auto i (this->upper_bound (p)); - return i == this->begin () || !p.sub ((--i)->first) ? this->end () : i; - } - - const_iterator - find_sup (const P& p) const - { - auto i (this->upper_bound (p)); - return i == this->begin () || !p.sub ((--i)->first) ? this->end () : i; - } - }; - - template - using path_map = path_map_impl; - - template - using dir_path_map = path_map_impl; -} - -#endif // LIBBUTL_PATH_MAP_HXX diff --git a/libbutl/path-map.mxx b/libbutl/path-map.mxx new file mode 100644 index 0000000..c526a89 --- /dev/null +++ b/libbutl/path-map.mxx @@ -0,0 +1,164 @@ +// file : libbutl/path-map.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include // min() +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.path_map; +#ifdef __cpp_lib_modules +import std.core; +#endif +import butl.path; +import butl.prefix_map; +#else +#include +#include +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // prefix_map for filesystem paths + // + // Important: the paths should be normalized but can use different directory + // separators and different case on case-insensitive platforms. + // + // Note that the path's representation of POSIX root ('/') is inconsistent + // in that we have a trailing delimiter at the end of the path (its "proper" + // representation would have been an empty string but that would have + // clashed with empty paths). To work around this snag, this implementation, + // during key comparison, detects '/' and treats it as empty. Note that the + // map will still store the key as you have first inserted it. So if you + // want a particular representation (i.e., empty or '/'), pre- populate the + // map with it. + // + template + struct compare_prefix> + { + typedef basic_path key_type; + + typedef C delimiter_type; + typedef typename key_type::string_type string_type; + typedef typename key_type::size_type size_type; + typedef typename key_type::traits traits_type; + + explicit + compare_prefix (delimiter_type) {} + + bool + operator() (const key_type& x, const key_type& y) const + { + const string_type& xs (x.string ()); + const string_type& ys (y.string ()); + + return compare (xs.c_str (), + root (xs) ? 0 : xs.size (), + ys.c_str (), + root (ys) ? 0 : ys.size ()) < 0; + } + + bool + prefix (const key_type& p, const key_type& k) const + { + const string_type& ps (p.string ()); + const string_type& ks (k.string ()); + + return prefix (root (ps) ? string_type () : ps, + root (ks) ? string_type () : ks); + } + + protected: + bool + prefix (const string_type& p, const string_type& k) const + { + // The same code as in prefix_map but using our compare(). + // + size_type pn (p.size ()), kn (k.size ()); + return pn == 0 || // Empty key is always a prefix. + (pn <= kn && + compare (p.c_str (), pn, k.c_str (), pn == kn ? pn : pn + 1) == 0); + } + + int + compare (const C* x, size_type xn, + const C* y, size_type yn) const + { + size_type n (std::min (xn, yn)); + int r (traits_type::compare (x, n, y, n)); + + if (r == 0) + { + // Pretend there is a delimiter characters at the end of the + // shorter string. + // + char xc (xn > n ? x[n] : (xn++, traits_type::directory_separator)); + char yc (yn > n ? y[n] : (yn++, traits_type::directory_separator)); + r = traits_type::compare (&xc, 1, &yc, 1); + + // If we are still equal, then compare the lengths. + // + if (r == 0) + r = (xn == yn ? 0 : (xn < yn ? -1 : 1)); + } + + return r; + } + + static bool + root (const string_type& p) + { + return p.size () == 1 && key_type::traits::is_separator (p[0]); + } + }; + + // Note that the delimiter character is not used (is_delimiter() from + // path_traits is used instead). + // + template + struct path_map_impl: prefix_map + { + using base = prefix_map; + using base::base; + + using iterator = typename base::iterator; + using const_iterator = typename base::const_iterator; + + // Find the most qualified entry that is a superpath of this path. + // + iterator + find_sup (const P& p) + { + // Get the greatest less than, if any. We might still not be a sub. Note + // also that we still have to check the last element if upper_bound() + // returned end(). + // + auto i (this->upper_bound (p)); + return i == this->begin () || !p.sub ((--i)->first) ? this->end () : i; + } + + const_iterator + find_sup (const P& p) const + { + auto i (this->upper_bound (p)); + return i == this->begin () || !p.sub ((--i)->first) ? this->end () : i; + } + }; + + template + using path_map = path_map_impl; + + template + using dir_path_map = path_map_impl; +} diff --git a/libbutl/path.cxx b/libbutl/path.cxx index c1ede40..bbc4754 100644 --- a/libbutl/path.cxx +++ b/libbutl/path.cxx @@ -2,7 +2,9 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif #ifdef _WIN32 # include @@ -21,18 +23,37 @@ # include // strlen(), strcpy() # include // stat(), S_IS* # include // stat - -# include #endif -#include #include + +#ifndef __cpp_lib_modules +#include +#include +#include + +#include #include // strcpy() +#endif -#include +#ifdef __cpp_modules +module butl.path; -#include // throw_*_error() -#include +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +#endif +#endif + +import butl.utility; // throw_*_error() +import butl.process; // process::current_id() +#else +#include +#include +#endif + +#include #ifndef _WIN32 # ifndef PATH_MAX @@ -88,9 +109,11 @@ namespace butl // one of the drive requires the trailing directory separator to be // present. // - string_type const& d (!root (s) - ? s - : string_type (s + directory_separator)); + string_type const& d ( + !root (s) + ? s + //@@ MOD VC ADL does not seem to kick in for some reason... + : string_type (std::operator+ (s, directory_separator))); if (_chdir (d.c_str ()) != 0) throw_generic_error (errno); @@ -168,7 +191,7 @@ namespace butl #endif } - static atomic temp_name_count; + static atomic temp_name_count (0); template <> LIBBUTL_SYMEXPORT path_traits::string_type path_traits:: diff --git a/libbutl/path.hxx b/libbutl/path.hxx deleted file mode 100644 index 69e0428..0000000 --- a/libbutl/path.hxx +++ /dev/null @@ -1,1214 +0,0 @@ -// file : libbutl/path.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_PATH_HXX -#define LIBBUTL_PATH_HXX - -#include -#include // ptrdiff_t -#include // move(), swap() -#include -#include -#include // hash - -#include - -#include - -namespace butl -{ - // Wish list/ideas for improvements. - // - // - Ability to convert to directory/leaf/base in-place, without dynamic - // allocation. One idea is something like this: - // - // p -= "/*"; // directory - // p -= "*/"; // leaf - // p -= ".*"; // base - // - // - Faster normalize() implementation. In many cases (e.g., in build2) - // the path is either already normal or the difference is just slashes - // (i.e., there are no '.' or '..' components). So a fast path case - // might be in order. - // - // - We duplicate the interface for path and dir_path while most of it - // is common. Also, we can implicit-cast dir_path& to path& and use - // non-dir-adapted implementation (see where we call K::cast()). - // - - struct LIBBUTL_SYMEXPORT invalid_path_base: public std::exception - { - virtual char const* - what () const throw (); - }; - - template - struct invalid_basic_path: invalid_path_base - { - using string_type = std::basic_string; - - string_type path; - - invalid_basic_path (const C* p): path (p) {} - invalid_basic_path (const string_type& p): path (p) {} - }; - - // The only currently available specialization is for the char type. - // - template - struct path_traits - { - using string_type = std::basic_string; - using size_type = typename string_type::size_type; - - // Canonical directory and path seperators. - // -#ifdef _WIN32 - static const C directory_separator = '\\'; - static const C path_separator = ';'; -#else - static C const directory_separator = '/'; - static C const path_separator = ':'; -#endif - - // Canonical and alternative directory separators. Canonical should be - // first. - // -#ifdef _WIN32 - static constexpr const char* const directory_separators = "\\/"; -#else - static constexpr const char* const directory_separators = "/"; -#endif - - // Directory separator tests. On some platforms there could be multiple - // seperators. For example, on Windows we check for both '/' and '\'. - // - static bool - is_separator (C c) - { -#ifdef _WIN32 - return c == '\\' || c == '/'; -#else - return c == '/'; -#endif - } - - // Return 1-based index in directory_separators string or 0 if not a - // separator. - // - static size_type - separator_index (C c) - { -#ifdef _WIN32 - return c == '\\' ? 1 : c == '/' ? 2 : 0; -#else - return c == '/' ? 1 : 0; -#endif - } - - static bool - absolute (const string_type& s) - { - return absolute (s.c_str (), s.size ()); - } - - static bool - absolute (const C* s, size_type n) - { -#ifdef _WIN32 - return n > 1 && s[1] == ':'; -#else - return n != 0 && is_separator (s[0]); -#endif - } - - static bool - current (const string_type& s) - { - return current (s.c_str (), s.size ()); - } - - static bool - current (const C* s, size_type n) - { - return n == 1 && s[0] == '.'; - } - - static bool - parent (const string_type& s) - { - return parent (s.c_str (), s.size ()); - } - - static bool - parent (const C* s, size_type n) - { - return n == 2 && s[0] == '.' && s[1] == '.'; - } - - static bool - normalized (const string_type& s, bool sep) - { - return normalized (s.c_str (), s.size (), sep); - } - - static bool - normalized (const C* s, size_type n, bool sep) - { - size_t j (0); // Beginning of path component. - - for (size_t i (0); i != n; ++i) - { - char c (s[i]); - - if (is_separator (c)) - { - if (sep && c != directory_separator) - return false; - - const char* p (s + j); - size_t m (i - j); - j = i + 1; - - if (j != n && is_separator (s[j])) - return false; - - if (current (p, m) || parent (p, m)) - return false; - } - } - - // Last component. - // - const char* p (s + j); - size_t m (n - j); - - return !(current (p, m) || parent (p, m)); - } - - static bool - root (const string_type& s) - { - return root (s.c_str (), s.size ()); - } - - static bool - root (const C* s, size_type n) - { -#ifdef _WIN32 - return n == 2 && s[1] == ':'; -#else - return n == 1 && is_separator (s[0]); -#endif - } - - static size_type - find_separator (string_type const& s, - size_type pos = 0, - size_type n = string_type::npos) - { - if (n == string_type::npos) - n = s.size (); - - const C* r (find_separator (s.c_str () + pos, n - pos)); - return r != nullptr ? r - s.c_str () : string_type::npos; - } - - static const C* - find_separator (const C* s, size_type n) - { - for (const C* e (s + n); s != e; ++s) - { - if (is_separator (*s)) - return s; - } - - return nullptr; - } - - static size_type - rfind_separator (string_type const& s, size_type pos = string_type::npos) - { - if (pos == string_type::npos) - pos = s.size (); - else - pos++; - - const C* r (rfind_separator (s.c_str (), pos)); - return r != nullptr ? r - s.c_str () : string_type::npos; - } - - static const C* - rfind_separator (const C* s, size_type n) - { - for (; n != 0; --n) - { - if (is_separator (s[n - 1])) - return s + n - 1; - } - - return nullptr; - } - - // Return the position of '.' or npos if there is no extension. - // - static size_type - find_extension (string_type const& s) - { - const C* r (find_extension (s.c_str (), s.size ())); - return r != nullptr ? r - s.c_str () : string_type::npos; - } - - static const C* - find_extension (const C* s, size_type n) - { - size_type i (n); - - for (; i > 0; --i) - { - C c (s[i - 1]); - - if (c == '.') - break; - - if (is_separator (c)) - { - i = 0; - break; - } - } - - // Weed out paths like ".txt" (and "/.txt") and "txt.". - // - if (i > 1 && !is_separator (s[i - 2]) && i != n) - return s + i - 1; - else - return nullptr; - } - - // Return the start of the leaf (last path component) in the path. Note - // that the leaf will include the trailing separator, if any (i.e., the - // leaf of /tmp/bar/ is bar/). - // - static size_type - find_leaf (string_type const& s) - { - const C* r (find_leaf (s.c_str (), s.size ())); - return r != nullptr ? r - s.c_str () : string_type::npos; - } - - static const C* - find_leaf (const C* s, size_type n) - { - const C* p; - return n == 0 - ? nullptr - : (p = rfind_separator (s, n - 1)) == nullptr ? s : ++p; - } - - static int - compare (string_type const& l, string_type const& r) - { - return compare (l.c_str (), l.size (), r.c_str (), r.size ()); - } - - // @@ Currently for case-insensitive filesystems (Windows) compare() - // works properly only for ASCII. - // - static int - compare (const C* l, size_type ln, const C* r, size_type rn) - { - for (size_type i (0), n (ln < rn ? ln : rn); i != n; ++i) - { -#ifdef _WIN32 - C lc (lcase (l[i])), rc (lcase (r[i])); -#else - C lc (l[i]), rc (r[i]); -#endif - if (is_separator (lc) && is_separator (rc)) - continue; - - if (lc < rc) return -1; - if (lc > rc) return 1; - } - - return ln < rn ? -1 : (ln > rn ? 1 : 0); - } - - static void - canonicalize (string_type& s) - { - //canonicalize (s.data (), s.size ()); // C++17 - - for (size_t i (0), n (s.size ()); i != n; ++i) - if (is_separator (s[i]) && s[i] != directory_separator) - s[i] = directory_separator; - } - - static void - canonicalize (C* s, size_type n) - { - for (const C* e (s + n); s != e; ++s) - if (is_separator (*s) && *s != directory_separator) - *s = directory_separator; - } - - // Get/set current working directory. Throw std::system_error to report - // the underlying OS errors. - // - static string_type - current_directory (); - - static void - current_directory (string_type const&); - - // Return the user home directory. Throw std::system_error to report the - // underlying OS errors. - // - static string_type - home_directory (); - - // Return the temporary directory. Throw std::system_error to report the - // underlying OS errors. - // - static string_type - temp_directory (); - - // Return a temporary name. The name is constructed by starting with the - // prefix followed by the process id following by a unique counter value - // inside the process (MT-safe). Throw std::system_error to report the - // underlying OS errors. - // - static string_type - temp_name (string_type const& prefix); - - // Make the path real (by calling realpath(3)). Throw invalid_basic_path - // if the path is invalid (e.g., some components do not exist) and - // std::system_error to report other underlying OS errors. - // -#ifndef _WIN32 - static void - realize (string_type&); -#endif - - // Utilities. - // -#ifdef _WIN32 - static C - tolower (C); - - static C - toupper (C); -#endif - }; - - // This implementation of a filesystem path has two types: path, which can - // represent any path (file, directory, etc.) and dir_path, which is derived - // from path. The internal representation of directories maintains a - // trailing slash. However, it is ignored in path comparison, size, and - // string spelling. For example: - // - // path p1 ("foo"); // File path. - // path p2 ("bar/"); // Directory path. - // - // path p3 (p1 / p2); // Throw: p1 is not a directory. - // path p4 (p2 / p1); // Ok, file "bar/foo". - // path p5 (p2 / p2); // Ok, directory "bar/bar/". - // - // dir_path d1 ("foo"); // Directory path "foo/". - // dir_path d2 ("bar\\"); // Directory path "bar\". - // - // dir_path d3 (d2 / d1); // "bar\\foo/" - // - // (p4 == d3); // true - // d3.string (); // "bar\\foo" - // d3.representation (); // "bar\\foo/" - // - template - class basic_path; - - template struct any_path_kind; - template struct dir_path_kind; - - using path = basic_path>; - using dir_path = basic_path>; - using invalid_path = invalid_basic_path; - - // Cast from one path kind to another. Note that no checking is performed - // (e.g., that there is a trailing slash if casting to dir_path) but the - // representation is adjusted if necessary (e.g., the trailing slash is - // added to dir_path if missing). - // - template P path_cast (const basic_path&); - template P path_cast (basic_path&&); - - // Low-level path data storage. It is also by the implementation to pass - // around initialized/valid paths. - // - template - struct path_data - { - using string_type = std::basic_string; - using size_type = typename string_type::size_type; - using difference_type = typename string_type::difference_type; - - // The idea is as follows: path_ is always the "traditional" form; that - // is, "/" for the root directory and "/tmp" (no trailing slash) for the - // rest. This means we can return/store references to path_. - // - // Then we have tsep_ ("trailing separator") which is the size difference - // between path_ and its "pure" part, that is, without any trailing - // slashes, even for "/". So: - // - // tsep_ == -1 -- trailing slash in path_ (the "/" case) - // tsep_ == 0 -- no trailing slash - // - // Finally, to represent non-root ("/") trailing slashes we use positive - // tsep_ values. In this case tsep_ is interpreted as a 1-based index in - // the path_traits::directory_separators string. - // - // Notes: - // - If path_ is empty, then tsep_ can only be 0. - // - We could have used a much narrower integer for tsep_. - // - string_type path_; - difference_type tsep_; - - size_type - _size () const {return path_.size () + (tsep_ < 0 ? -1 : 0);} - - void - _swap (path_data& d) {path_.swap (d.path_); std::swap (tsep_, d.tsep_);} - - void - _clear () {path_.clear (); tsep_ = 0;} - - // Constructors. - // - path_data (): tsep_ (0) {} - - path_data (string_type&& p, difference_type ts) - : path_ (std::move (p)), tsep_ (path_.empty () ? 0 : ts) {} - - explicit - path_data (string_type&& p) - : path_ (std::move (p)), tsep_ (0) - { - size_type n (path_.size ()), i; - - if (n != 0 && (i = path_traits::separator_index (path_[n - 1])) != 0) - { - if (n == 1) // The "/" case. - tsep_ = -1; - else - { - tsep_ = i; - path_.pop_back (); - } - } - } - }; - - template - struct any_path_kind - { - class base_type: protected path_data // In essence protected path_data. - { - protected: - using path_data::path_data; - - base_type () = default; - base_type (path_data&& d): path_data (std::move (d)) {} - }; - - using dir_type = basic_path>; - - // Init and cast. - // - // If exact is true, return the path if the initialization was successful, - // that is, the passed string is a valid path and no modifications were - // necessary. Otherwise, return the empty object and leave the passed - // string untouched. - // - // If extact is false, throw invalid_path if the string is not a valid - // path (e.g., uses an unsupported path notation on Windows). - // - using data_type = path_data; - using string_type = std::basic_string; - - static data_type - init (string_type&&, bool exact = false); - - static void - cast (data_type&) {} - }; - - template - struct dir_path_kind - { - using base_type = basic_path>; - using dir_type = basic_path>; - - // Init and cast. - // - using data_type = path_data; - using string_type = std::basic_string; - - static data_type - init (string_type&&, bool exact = false); - - static void - cast (data_type&); - }; - - template - class basic_path: public K::base_type - { - public: - using string_type = std::basic_string; - using size_type = typename string_type::size_type; - using difference_type = typename string_type::difference_type; - using traits = path_traits; - - struct iterator; - using reverse_iterator = std::reverse_iterator; - - using base_type = typename K::base_type; - using dir_type = typename K::dir_type; - - // Create a special empty path. Note that we have to provide our own - // implementation rather than using '=default' to make clang allow - // default-initialized const instances of this type. - // - basic_path () {} - - // Constructors that initialize a path from a string argument throw the - // invalid_path exception if the string is not a valid path (e.g., uses - // unsupported path notations on Windows). - // - explicit - basic_path (C const* s): base_type (K::init (s)) {} - - basic_path (C const* s, size_type n) - : base_type (K::init (string_type (s, n))) {} - - explicit - basic_path (string_type s): base_type (K::init (std::move (s))) {} - - basic_path (const string_type& s, size_type n) - : base_type (K::init (string_type (s, 0, n))) {} - - basic_path (const string_type& s, size_type p, size_type n) - : base_type (K::init (string_type (s, p, n))) {} - - // Create a path using the exact string representation. If the string is - // not a valid path or if it would require a modification, then empty path - // is created instead and the passed string rvalue-reference is left - // untouched. Note that no exception is thrown if the path is invalid. See - // also representation()&& below. - // - enum exact_type {exact}; - basic_path (string_type&& s, exact_type) - : base_type (K::init (std::move (s), true)) {} - - // Create a path as a sub-path identified by the [begin, end) range of - // components. - // - basic_path (const iterator& begin, const iterator& end); - - basic_path (const reverse_iterator& rbegin, const reverse_iterator& rend) - : basic_path (rend.base (), rbegin.base ()) {} - - void - swap (basic_path& p) {this->_swap (p);} - - void - clear () {this->_clear ();} - - // Get/set current working directory. Throw std::system_error to report - // the underlying OS errors. - // - static dir_type - current_directory () {return dir_type (traits::current_directory ());} - - static void - current_directory (basic_path const&); - - // Return the user home directory. Throw std::system_error to report the - // underlying OS errors. - // - static dir_type - home_directory () {return dir_type (traits::home_directory ());} - - // Return the temporary directory. Throw std::system_error to report the - // underlying OS errors. - // - static dir_type - temp_directory () {return dir_type (traits::temp_directory ());} - - // Return a temporary path. The path is constructed by starting with the - // temporary directory and then appending a path component consisting of - // the prefix followed by the process id following by a unique counter - // value inside the process. Throw std::system_error to report the - // underlying OS errors. - // - static basic_path - temp_path (const string_type& prefix) - { - return temp_directory () / traits::temp_name (prefix); - } - - public: - bool - empty () const {return this->path_.empty ();} - - // Note that size does not include the trailing separator except for - // the root case. - // - size_type - size () const {return this->path_.size ();} - - // Return true if this path doesn't have any directories. Note that "/foo" - // is not a simple path (it is "foo" in root directory) while "/" is (it - // is the root directory). - // - bool - simple () const; - - bool - absolute () const; - - bool - relative () const {return !absolute ();} - - bool - root () const; - - // The following predicates return true for the "." and ".." paths, - // respectively. Note that the result doesn't depend on the presence or - // spelling of the trailing directory separator. - // - // Also note that the path must literally match the specified values rather - // than be semantically current or parent. For example for paths "foo/.." - // or "bar/../.." the predicates return false. - // - bool - current () const; - - bool - parent () const; - - // Return true if the path is normalized, that is, does not contain any - // current or parent directory components or multiple consecutive and, - // unless sep is false, non-canonical directory separators. Empty path - // is considered normalized. - // - bool - normalized (bool sep = true) const; - - // Test, based on the presence/absence of the trailing separator, if the - // path is to a directory. - // - bool - to_directory () const {return this->tsep_ != 0;} - - // Return true if *this is a sub-path of the specified path (i.e., - // the specified path 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. - // - bool - sub (const basic_path&) const; - - // Return true if *this is a super-path of the specified path (i.e., - // the specified path 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 suffix of any path. - // - bool - sup (const basic_path&) const; - - public: - // Return the path without the directory part. Leaf of a directory is - // itself a directory (contains trailing slash). Leaf of a root is the - // path itself. - // - basic_path - leaf () const; - - // Return the path without the specified directory part. Throws - // invalid_path if the directory is not a prefix of *this. Expects both - // paths to be normalized. - // - basic_path - leaf (basic_path const&) const; - - // Return the directory part of the path or empty path if there is no - // directory. Directory of a root is an empty path. - // - dir_type - directory () const; - - // Return the directory part of the path without the specified leaf part. - // Throws invalid_path if the leaf is not a suffix of *this. Expects both - // paths to be normalized. - // - dir_type - directory (basic_path const&) const; - - // Return the root directory of the path or empty path if the directory is - // not absolute. - // - dir_type - root_directory () const; - - // Return the path without the extension, if any. - // - basic_path - base () const; - - // Return the extension or NULL if not present. If not empty, then the - // result starts with the character past the dot. - // - string_type - extension () const; - - // Return the in-place pointer to extension or NULL if not present. If not - // NULL, then the result points to the character past the dot but it is - // legal to decrement it once to obtain the value with the dot. - // - const C* - extension_cstring () const; - - // Return a path relative to the specified path that is equivalent - // to *this. Throws invalid_path if a relative path cannot be derived - // (e.g., paths are on different drives on Windows). - // - basic_path - relative (basic_path) const; - - // Iteration over path components. - // - public: - struct iterator - { - using value_type = string_type ; - using pointer = string_type*; - using reference = string_type ; - using size_type = typename string_type::size_type; - using difference_type = std::ptrdiff_t ; - using iterator_category = std::bidirectional_iterator_tag ; - - using data_type = path_data; - - iterator (): p_ (nullptr) {} - iterator (const data_type* p, size_type b, size_type e) - : p_ (p), b_ (b), e_ (e) {} - - // Create an iterator by "rebasing" an old iterator onto a new path - // object. Can, for example, be used to "move" an iterator when moving - // the path object. Note: potentially dangerous if the old iterator used - // to point to a different path. - // - iterator (const basic_path& p, const iterator& i) - : p_ (&p), b_ (i.b_), e_ (i.e_) {} - - iterator& - operator++ () - { - const string_type& s (p_->path_); - - // Position past trailing separator, if any. - // - b_ = e_ != string_type::npos && ++e_ != s.size () - ? e_ - : string_type::npos; - - // Find next trailing separator. - // - e_ = b_ != string_type::npos ? traits::find_separator (s, b_) : b_; - - return *this; - } - - iterator& - operator-- () - { - const string_type& s (p_->path_); - - // Find the new end. - // - e_ = b_ == string_type::npos // Past end? - ? (traits::is_separator (s.back ()) // Have trailing slash? - ? s.size () - 1 - : string_type::npos) - : b_ - 1; - - // Find the new begin. - // - b_ = e_ == 0 // Empty component? - ? string_type::npos - : traits::rfind_separator (s, e_ != string_type::npos ? e_ - 1 : e_); - - b_ = b_ == string_type::npos // First component? - ? 0 - : b_ + 1; - - return *this; - } - - iterator - operator++ (int) {iterator r (*this); operator++ (); return r;} - - iterator - operator-- (int) {iterator r (*this); operator-- (); return r;} - - string_type - operator* () const - { - return string_type (p_->path_, - b_, - e_ != string_type::npos ? e_ - b_ : e_); - } - - // Return the directory separator after this component or '\0' if there - // is none. This, for example, can be used to determine if the last - // component is a directory. - // - C - separator () const - { - return e_ != string_type::npos - ? p_->path_[e_] - : (p_->tsep_ > 0 - ? path_traits::directory_separators[p_->tsep_ - 1] - : 0); - } - - pointer operator-> () const = delete; - - friend bool - operator== (const iterator& x, const iterator& y) - { - return x.p_ == y.p_ && x.b_ == y.b_ && x.e_ == y.e_; - } - - friend bool - operator!= (const iterator& x, const iterator& y) {return !(x == y);} - - private: - friend class basic_path; - - // b - first character of component - // e - separator after component (or npos if none) - // b == npos && e == npos - one past last component (end) - // - const data_type* p_; - size_type b_; - size_type e_; - }; - - iterator begin () const; - iterator end () const; - - reverse_iterator rbegin () const {return reverse_iterator (end ());} - reverse_iterator rend () const {return reverse_iterator (begin ());} - - public: - // Canonicalize the path and return *this. Canonicalization involves - // converting all directory separators to the canonical form. Note that - // multiple directory separators are not collapsed. - // - basic_path& - canonicalize (); - - // Normalize the path and return *this. Normalization involves collapsing - // the '.' and '..' directories if possible, collapsing multiple - // directory separators, and converting all directory separators to the - // canonical form. If cur_empty is true then collapse relative paths - // representing the current directory (for example, '.', './', 'foo/..') - // to an empty path. Otherwise convert it to the canonical form (./ on - // POSIX systems). Note that a non-empty path cannot become an empty one - // in the latter case. - // - // If actual is true, then for case-insensitive filesystems obtain the - // actual spelling of the path. Only an absolute path can be actualized. - // If a path component does not exist, then its (and all subsequent) - // spelling is unchanged. This is a potentially expensive operation. - // Normally one can assume that "well-known" directories (current, home, - // etc.) are returned in their actual spelling. - // - basic_path& - normalize (bool actual = false, bool cur_empty = false); - - // Make the path absolute using the current directory unless it is already - // absolute. Return *this. - // - basic_path& - complete (); - - // Make the path real, that is, absolute, normalized, and with resolved - // symlinks. On POSIX systems this is accomplished with the call to - // realpath(3). On Windows -- complete() and normalize(). Return *this. - // - basic_path& - realize (); - - public: - basic_path& - operator/= (basic_path const&); - - // Combine a single path component (must not contain directory separators) - // as a string, without first constructing the path object. - // - basic_path& - operator/= (string_type const&); - - basic_path& - operator/= (const C*); - - // Append to the end of the path (normally an extension, etc). - // - basic_path& - operator+= (string_type const&); - - basic_path& - operator+= (const C*); - - basic_path& - operator+= (C); - - void - append (const C*, size_type); - - // Note that comparison is case-insensitive if the filesystem is not - // case-sensitive (e.g., Windows). And it ignored trailing slashes - // except for the root case. - // - template - int - compare (const basic_path& x) const { - return traits::compare (this->path_, x.path_);} - - public: - // Path string and representation. The string does not contain the - // trailing slash except for the root case. In other words, it is the - // "traditional" spelling of the path that can be passed to system calls, - // etc. Representation, on the other hand is the "precise" spelling that - // includes the trailing slash, if any. One cannot always round-trip a - // path using string() but can using representation(). Note also that - // representation() returns a copy while string() returns a (tracking) - // reference. - // - const string_type& - string () const& {return this->path_;} - - string_type - representation () const&; - - // Moves the underlying path string out of the path object. The path - // object becomes empty. Usage: std::move (p).string (). - // - string_type - string () && {string_type r; r.swap (this->path_); return r;} - - string_type - representation () &&; - - // Trailing directory separator or '\0' if there is none. - // - C - separator () const; - - // As above but return it as a (potentially empty) string. - // - string_type - separator_string () const; - - // If possible, return a POSIX version of the path. For example, for a - // Windows path in the form foo\bar this function will return foo/bar. If - // it is not possible to create a POSIX version for this path (e.g., - // c:\foo), this function will throw the invalid_path exception. - // - string_type - posix_string () const&; - - string_type - posix_representation () const&; - - string_type - posix_string () &&; - - string_type - posix_representation () &&; - - // Implementation details. - // - protected: - using data_type = path_data; - - // Direct initialization without init()/cast(). - // - explicit - basic_path (data_type&& d): base_type (std::move (d)) {} - - using base_type::_size; - - // Common implementation for operator/= and operator+=. - // - void - combine (const C*, size_type, difference_type); - - void - combine (const C*, size_type); - - // Friends. - // - template - friend class basic_path; - - template - friend basic_path - path_cast_impl (const basic_path&, basic_path*); - - template - friend basic_path - path_cast_impl (basic_path&&, basic_path*); - }; - - template - inline basic_path - operator/ (const basic_path& x, const basic_path& y) - { - basic_path r (x); - r /= y; - return r; - } - - template - inline basic_path - operator+ (const basic_path& x, const std::basic_string& y) - { - basic_path r (x); - r += y; - return r; - } - - template - inline basic_path - operator+ (const basic_path& x, const C* y) - { - basic_path r (x); - r += y; - return r; - } - - template - inline basic_path - operator+ (const basic_path& x, C y) - { - basic_path r (x); - r += y; - return r; - } - - template - inline bool - operator== (const basic_path& x, const basic_path& y) - { - return x.compare (y) == 0; - } - - template - inline bool - operator!= (const basic_path& x, const basic_path& y) - { - return !(x == y); - } - - template - inline bool - operator< (const basic_path& x, const basic_path& y) - { - return x.compare (y) < 0; - } - - // Additional operators for certain path kind combinations. - // - template - inline basic_path> - operator/ (const basic_path>& x, - const basic_path>& y) - { - basic_path> r (x); - r /= y; - return r; - } - - - // Note that the result of (foo / "bar") is always a path, even if foo - // is dir_path. An idiom to force it dir_path is this: - // - // dir_path foo_bar (dir_path (foo) /= "bar"); - // - template - inline basic_path> - operator/ (const basic_path& x, const std::basic_string& y) - { - basic_path> r (x); - r /= y; - return r; - } - - template - inline basic_path> - operator/ (const basic_path& x, const C* y) - { - basic_path> r (x); - r /= y; - return r; - } - - // For operator<< (ostream) see the path-io header. -} - -namespace std -{ - template - struct hash>: hash> - { - using argument_type = butl::basic_path; - - size_t - operator() (const butl::basic_path& p) const noexcept - { -#ifndef _WIN32 - return hash>::operator() (p.string ()); -#else - // Case-insensitive FNV hash. - // - const auto& s (p.string ()); - - size_t hash (static_cast (2166136261UL)); - for (size_t i (0), n (s.size ()); i != n; ++i) - { - hash ^= static_cast (butl::lcase (s[i])); - - // We are using C-style cast to suppress VC warning for 32-bit target - // (the value is compiled but not used). - // - hash *= sizeof (size_t) == 4 - ? static_cast (16777619UL) - : (size_t) 1099511628211ULL; - } - return hash; -#endif - } - }; -} - -#include -#include - -#endif // LIBBUTL_PATH_HXX diff --git a/libbutl/path.ixx b/libbutl/path.ixx index 7227b72..f983f5d 100644 --- a/libbutl/path.ixx +++ b/libbutl/path.ixx @@ -2,39 +2,25 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#ifdef _WIN32 -# include // towlower(), towupper() -#endif - -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { #ifdef _WIN32 template <> + LIBBUTL_SYMEXPORT //@@ MOD VC needs it for some reason. inline char path_traits:: tolower (char c) { - return lcase (c); - } - - template <> - inline wchar_t path_traits:: - tolower (wchar_t c) - { - return std::towlower (c); + //@@ MOD VC-ICE return lcase (c); + return std::tolower (c); } template <> + LIBBUTL_SYMEXPORT //@@ MOD VC needs it for some reason. inline char path_traits:: toupper (char c) { - return ucase (c); - } - - template <> - inline wchar_t path_traits:: - toupper (wchar_t c) - { - return std::towupper (c); + //@@ MOD VC-ICE return ucase (c); + return std::toupper (c); } #endif diff --git a/libbutl/path.mxx b/libbutl/path.mxx new file mode 100644 index 0000000..31101fc --- /dev/null +++ b/libbutl/path.mxx @@ -0,0 +1,1245 @@ +// file : libbutl/path.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +#include + +#ifndef __cpp_lib_modules +#include +#include // ptrdiff_t +#include // move(), swap() +#include +#include +#include // hash + +#include +#ifdef _WIN32 +#include // toupper/lower() @@ MOD TMP +#include // replace() +#endif +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.path; +#ifdef __cpp_lib_modules +import std.core; +#endif +#ifdef _WIN32 +import butl.utility; +#endif +#else +#ifdef _WIN32 +#include // *case*() +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // Wish list/ideas for improvements. + // + // - Ability to convert to directory/leaf/base in-place, without dynamic + // allocation. One idea is something like this: + // + // p -= "/*"; // directory + // p -= "*/"; // leaf + // p -= ".*"; // base + // + // - Faster normalize() implementation. In many cases (e.g., in build2) + // the path is either already normal or the difference is just slashes + // (i.e., there are no '.' or '..' components). So a fast path case + // might be in order. + // + // - We duplicate the interface for path and dir_path while most of it + // is common. Also, we can implicit-cast dir_path& to path& and use + // non-dir-adapted implementation (see where we call K::cast()). + // + + struct LIBBUTL_SYMEXPORT invalid_path_base: public std::exception + { + virtual char const* + what () const throw (); + }; + + template + struct invalid_basic_path: invalid_path_base + { + using string_type = std::basic_string; + + string_type path; + + invalid_basic_path (const C* p): path (p) {} + invalid_basic_path (const string_type& p): path (p) {} + }; + + // The only currently available specialization is for the char type. + // + template + struct path_traits + { + using string_type = std::basic_string; + using size_type = typename string_type::size_type; + + // Canonical directory and path seperators. + // +#ifdef _WIN32 + static const C directory_separator = '\\'; + static const C path_separator = ';'; +#else + static C const directory_separator = '/'; + static C const path_separator = ':'; +#endif + + // Canonical and alternative directory separators. Canonical should be + // first. + // +#ifdef _WIN32 + static constexpr const char* const directory_separators = "\\/"; +#else + static constexpr const char* const directory_separators = "/"; +#endif + + // Directory separator tests. On some platforms there could be multiple + // seperators. For example, on Windows we check for both '/' and '\'. + // + static bool + is_separator (C c) + { +#ifdef _WIN32 + return c == '\\' || c == '/'; +#else + return c == '/'; +#endif + } + + // Return 1-based index in directory_separators string or 0 if not a + // separator. + // + static size_type + separator_index (C c) + { +#ifdef _WIN32 + return c == '\\' ? 1 : c == '/' ? 2 : 0; +#else + return c == '/' ? 1 : 0; +#endif + } + + static bool + absolute (const string_type& s) + { + return absolute (s.c_str (), s.size ()); + } + + static bool + absolute (const C* s, size_type n) + { +#ifdef _WIN32 + return n > 1 && s[1] == ':'; +#else + return n != 0 && is_separator (s[0]); +#endif + } + + static bool + current (const string_type& s) + { + return current (s.c_str (), s.size ()); + } + + static bool + current (const C* s, size_type n) + { + return n == 1 && s[0] == '.'; + } + + static bool + parent (const string_type& s) + { + return parent (s.c_str (), s.size ()); + } + + static bool + parent (const C* s, size_type n) + { + return n == 2 && s[0] == '.' && s[1] == '.'; + } + + static bool + normalized (const string_type& s, bool sep) + { + return normalized (s.c_str (), s.size (), sep); + } + + static bool + normalized (const C* s, size_type n, bool sep) + { + size_t j (0); // Beginning of path component. + + for (size_t i (0); i != n; ++i) + { + char c (s[i]); + + if (is_separator (c)) + { + if (sep && c != directory_separator) + return false; + + const char* p (s + j); + size_t m (i - j); + j = i + 1; + + if (j != n && is_separator (s[j])) + return false; + + if (current (p, m) || parent (p, m)) + return false; + } + } + + // Last component. + // + const char* p (s + j); + size_t m (n - j); + + return !(current (p, m) || parent (p, m)); + } + + static bool + root (const string_type& s) + { + return root (s.c_str (), s.size ()); + } + + static bool + root (const C* s, size_type n) + { +#ifdef _WIN32 + return n == 2 && s[1] == ':'; +#else + return n == 1 && is_separator (s[0]); +#endif + } + + static size_type + find_separator (string_type const& s, + size_type pos = 0, + size_type n = string_type::npos) + { + if (n == string_type::npos) + n = s.size (); + + const C* r (find_separator (s.c_str () + pos, n - pos)); + return r != nullptr ? r - s.c_str () : string_type::npos; + } + + static const C* + find_separator (const C* s, size_type n) + { + for (const C* e (s + n); s != e; ++s) + { + if (is_separator (*s)) + return s; + } + + return nullptr; + } + + static size_type + rfind_separator (string_type const& s, size_type pos = string_type::npos) + { + if (pos == string_type::npos) + pos = s.size (); + else + pos++; + + const C* r (rfind_separator (s.c_str (), pos)); + return r != nullptr ? r - s.c_str () : string_type::npos; + } + + static const C* + rfind_separator (const C* s, size_type n) + { + for (; n != 0; --n) + { + if (is_separator (s[n - 1])) + return s + n - 1; + } + + return nullptr; + } + + // Return the position of '.' or npos if there is no extension. + // + static size_type + find_extension (string_type const& s) + { + const C* r (find_extension (s.c_str (), s.size ())); + return r != nullptr ? r - s.c_str () : string_type::npos; + } + + static const C* + find_extension (const C* s, size_type n) + { + size_type i (n); + + for (; i > 0; --i) + { + C c (s[i - 1]); + + if (c == '.') + break; + + if (is_separator (c)) + { + i = 0; + break; + } + } + + // Weed out paths like ".txt" (and "/.txt") and "txt.". + // + if (i > 1 && !is_separator (s[i - 2]) && i != n) + return s + i - 1; + else + return nullptr; + } + + // Return the start of the leaf (last path component) in the path. Note + // that the leaf will include the trailing separator, if any (i.e., the + // leaf of /tmp/bar/ is bar/). + // + static size_type + find_leaf (string_type const& s) + { + const C* r (find_leaf (s.c_str (), s.size ())); + return r != nullptr ? r - s.c_str () : string_type::npos; + } + + static const C* + find_leaf (const C* s, size_type n) + { + const C* p; + return n == 0 + ? nullptr + : (p = rfind_separator (s, n - 1)) == nullptr ? s : ++p; + } + + static int + compare (string_type const& l, string_type const& r) + { + return compare (l.c_str (), l.size (), r.c_str (), r.size ()); + } + + // @@ Currently for case-insensitive filesystems (Windows) compare() + // works properly only for ASCII. + // + static int + compare (const C* l, size_type ln, const C* r, size_type rn) + { + for (size_type i (0), n (ln < rn ? ln : rn); i != n; ++i) + { +#ifdef _WIN32 + C lc (lcase (l[i])), rc (lcase (r[i])); +#else + C lc (l[i]), rc (r[i]); +#endif + if (is_separator (lc) && is_separator (rc)) + continue; + + if (lc < rc) return -1; + if (lc > rc) return 1; + } + + return ln < rn ? -1 : (ln > rn ? 1 : 0); + } + + static void + canonicalize (string_type& s) + { + //canonicalize (s.data (), s.size ()); // C++17 + + for (size_t i (0), n (s.size ()); i != n; ++i) + if (is_separator (s[i]) && s[i] != directory_separator) + s[i] = directory_separator; + } + + static void + canonicalize (C* s, size_type n) + { + for (const C* e (s + n); s != e; ++s) + if (is_separator (*s) && *s != directory_separator) + *s = directory_separator; + } + + // Get/set current working directory. Throw std::system_error to report + // the underlying OS errors. + // + static string_type + current_directory (); + + static void + current_directory (string_type const&); + + // Return the user home directory. Throw std::system_error to report the + // underlying OS errors. + // + static string_type + home_directory (); + + // Return the temporary directory. Throw std::system_error to report the + // underlying OS errors. + // + static string_type + temp_directory (); + + // Return a temporary name. The name is constructed by starting with the + // prefix followed by the process id following by a unique counter value + // inside the process (MT-safe). Throw std::system_error to report the + // underlying OS errors. + // + static string_type + temp_name (string_type const& prefix); + + // Make the path real (by calling realpath(3)). Throw invalid_basic_path + // if the path is invalid (e.g., some components do not exist) and + // std::system_error to report other underlying OS errors. + // +#ifndef _WIN32 + static void + realize (string_type&); +#endif + + // Utilities. + // +#ifdef _WIN32 + static C + tolower (C); + + static C + toupper (C); +#endif + }; + + // This implementation of a filesystem path has two types: path, which can + // represent any path (file, directory, etc.) and dir_path, which is derived + // from path. The internal representation of directories maintains a + // trailing slash. However, it is ignored in path comparison, size, and + // string spelling. For example: + // + // path p1 ("foo"); // File path. + // path p2 ("bar/"); // Directory path. + // + // path p3 (p1 / p2); // Throw: p1 is not a directory. + // path p4 (p2 / p1); // Ok, file "bar/foo". + // path p5 (p2 / p2); // Ok, directory "bar/bar/". + // + // dir_path d1 ("foo"); // Directory path "foo/". + // dir_path d2 ("bar\\"); // Directory path "bar\". + // + // dir_path d3 (d2 / d1); // "bar\\foo/" + // + // (p4 == d3); // true + // d3.string (); // "bar\\foo" + // d3.representation (); // "bar\\foo/" + // + template + class basic_path; + + template struct any_path_kind; + template struct dir_path_kind; + + using path = basic_path>; + using dir_path = basic_path>; + using invalid_path = invalid_basic_path; + + // Cast from one path kind to another. Note that no checking is performed + // (e.g., that there is a trailing slash if casting to dir_path) but the + // representation is adjusted if necessary (e.g., the trailing slash is + // added to dir_path if missing). + // + template P path_cast (const basic_path&); + template P path_cast (basic_path&&); + + // Low-level path data storage. It is also by the implementation to pass + // around initialized/valid paths. + // + template + struct path_data + { + using string_type = std::basic_string; + using size_type = typename string_type::size_type; + using difference_type = typename string_type::difference_type; + + // The idea is as follows: path_ is always the "traditional" form; that + // is, "/" for the root directory and "/tmp" (no trailing slash) for the + // rest. This means we can return/store references to path_. + // + // Then we have tsep_ ("trailing separator") which is the size difference + // between path_ and its "pure" part, that is, without any trailing + // slashes, even for "/". So: + // + // tsep_ == -1 -- trailing slash in path_ (the "/" case) + // tsep_ == 0 -- no trailing slash + // + // Finally, to represent non-root ("/") trailing slashes we use positive + // tsep_ values. In this case tsep_ is interpreted as a 1-based index in + // the path_traits::directory_separators string. + // + // Notes: + // - If path_ is empty, then tsep_ can only be 0. + // - We could have used a much narrower integer for tsep_. + // + string_type path_; + difference_type tsep_; + + size_type + _size () const {return path_.size () + (tsep_ < 0 ? -1 : 0);} + + void + _swap (path_data& d) {path_.swap (d.path_); std::swap (tsep_, d.tsep_);} + + void + _clear () {path_.clear (); tsep_ = 0;} + + // Constructors. + // + path_data (): tsep_ (0) {} + + path_data (string_type&& p, difference_type ts) + : path_ (std::move (p)), tsep_ (path_.empty () ? 0 : ts) {} + + explicit + path_data (string_type&& p) + : path_ (std::move (p)), tsep_ (0) + { + size_type n (path_.size ()), i; + + if (n != 0 && (i = path_traits::separator_index (path_[n - 1])) != 0) + { + if (n == 1) // The "/" case. + tsep_ = -1; + else + { + tsep_ = i; + path_.pop_back (); + } + } + } + }; + + + template + struct any_path_kind + { + class base_type: public path_data // In essence protected path_data. + { + protected: + using path_data::path_data; + + base_type () = default; + base_type (path_data&& d): path_data (std::move (d)) {} + }; + + //using base_type = path_data; // @@ MOD VC-ICE + + using dir_type = basic_path>; + + // Init and cast. + // + // If exact is true, return the path if the initialization was successful, + // that is, the passed string is a valid path and no modifications were + // necessary. Otherwise, return the empty object and leave the passed + // string untouched. + // + // If extact is false, throw invalid_path if the string is not a valid + // path (e.g., uses an unsupported path notation on Windows). + // + using data_type = path_data; + using string_type = std::basic_string; + + static data_type + init (string_type&&, bool exact = false); + + static void + cast (data_type&) {} + }; + + template + struct dir_path_kind + { + using base_type = basic_path>; + using dir_type = basic_path>; + + // Init and cast. + // + using data_type = path_data; + using string_type = std::basic_string; + + static data_type + init (string_type&&, bool exact = false); + + static void + cast (data_type&); + }; + + struct exact_path_type {}; //@@ MOD TMP + + template + class basic_path: public K::base_type + { + public: + using string_type = std::basic_string; + using size_type = typename string_type::size_type; + using difference_type = typename string_type::difference_type; + using traits = path_traits; + + struct iterator; + using reverse_iterator = std::reverse_iterator; + + using base_type = typename K::base_type; + using dir_type = typename K::dir_type; + + // Create a special empty path. Note that we have to provide our own + // implementation rather than using '=default' to make clang allow + // default-initialized const instances of this type. + // + basic_path () {} + + // Constructors that initialize a path from a string argument throw the + // invalid_path exception if the string is not a valid path (e.g., uses + // unsupported path notations on Windows). + // + explicit + basic_path (C const* s): base_type (K::init (s)) {} + + basic_path (C const* s, size_type n) + : base_type (K::init (string_type (s, n))) {} + + explicit + basic_path (string_type s): base_type (K::init (std::move (s))) {} + + basic_path (const string_type& s, size_type n) + : base_type (K::init (string_type (s, 0, n))) {} + + basic_path (const string_type& s, size_type p, size_type n) + : base_type (K::init (string_type (s, p, n))) {} + + // Create a path using the exact string representation. If the string is + // not a valid path or if it would require a modification, then empty path + // is created instead and the passed string rvalue-reference is left + // untouched. Note that no exception is thrown if the path is invalid. See + // also representation()&& below. + // + //@@ MOD VC-ICE enum exact_type {exact}; + static const exact_path_type exact; + basic_path (string_type&& s, exact_path_type) + : base_type (K::init (std::move (s), true)) {} + + // Create a path as a sub-path identified by the [begin, end) range of + // components. + // + basic_path (const iterator& begin, const iterator& end); + + basic_path (const reverse_iterator& rbegin, const reverse_iterator& rend) + : basic_path (rend.base (), rbegin.base ()) {} + + void + swap (basic_path& p) {this->_swap (p);} + + void + clear () {this->_clear ();} + + // Get/set current working directory. Throw std::system_error to report + // the underlying OS errors. + // + static dir_type + current_directory () {return dir_type (traits::current_directory ());} + + static void + current_directory (basic_path const&); + + // Return the user home directory. Throw std::system_error to report the + // underlying OS errors. + // + static dir_type + home_directory () {return dir_type (traits::home_directory ());} + + // Return the temporary directory. Throw std::system_error to report the + // underlying OS errors. + // + static dir_type + temp_directory () {return dir_type (traits::temp_directory ());} + + // Return a temporary path. The path is constructed by starting with the + // temporary directory and then appending a path component consisting of + // the prefix followed by the process id following by a unique counter + // value inside the process. Throw std::system_error to report the + // underlying OS errors. + // + static basic_path + temp_path (const string_type& prefix) + { + return temp_directory () / traits::temp_name (prefix); + } + + public: + bool + empty () const {return this->path_.empty ();} + + // Note that size does not include the trailing separator except for + // the root case. + // + size_type + size () const {return this->path_.size ();} + + // Return true if this path doesn't have any directories. Note that "/foo" + // is not a simple path (it is "foo" in root directory) while "/" is (it + // is the root directory). + // + bool + simple () const; + + bool + absolute () const; + + bool + relative () const {return !absolute ();} + + bool + root () const; + + // The following predicates return true for the "." and ".." paths, + // respectively. Note that the result doesn't depend on the presence or + // spelling of the trailing directory separator. + // + // Also note that the path must literally match the specified values rather + // than be semantically current or parent. For example for paths "foo/.." + // or "bar/../.." the predicates return false. + // + bool + current () const; + + bool + parent () const; + + // Return true if the path is normalized, that is, does not contain any + // current or parent directory components or multiple consecutive and, + // unless sep is false, non-canonical directory separators. Empty path + // is considered normalized. + // + bool + normalized (bool sep = true) const; + + // Test, based on the presence/absence of the trailing separator, if the + // path is to a directory. + // + bool + to_directory () const {return this->tsep_ != 0;} + + // Return true if *this is a sub-path of the specified path (i.e., + // the specified path 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. + // + bool + sub (const basic_path&) const; + + // Return true if *this is a super-path of the specified path (i.e., + // the specified path 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 suffix of any path. + // + bool + sup (const basic_path&) const; + + public: + // Return the path without the directory part. Leaf of a directory is + // itself a directory (contains trailing slash). Leaf of a root is the + // path itself. + // + basic_path + leaf () const; + + // Return the path without the specified directory part. Throws + // invalid_path if the directory is not a prefix of *this. Expects both + // paths to be normalized. + // + basic_path + leaf (basic_path const&) const; + + // Return the directory part of the path or empty path if there is no + // directory. Directory of a root is an empty path. + // + dir_type + directory () const; + + // Return the directory part of the path without the specified leaf part. + // Throws invalid_path if the leaf is not a suffix of *this. Expects both + // paths to be normalized. + // + dir_type + directory (basic_path const&) const; + + // Return the root directory of the path or empty path if the directory is + // not absolute. + // + dir_type + root_directory () const; + + // Return the path without the extension, if any. + // + basic_path + base () const; + + // Return the extension or NULL if not present. If not empty, then the + // result starts with the character past the dot. + // + string_type + extension () const; + + // Return the in-place pointer to extension or NULL if not present. If not + // NULL, then the result points to the character past the dot but it is + // legal to decrement it once to obtain the value with the dot. + // + const C* + extension_cstring () const; + + // Return a path relative to the specified path that is equivalent + // to *this. Throws invalid_path if a relative path cannot be derived + // (e.g., paths are on different drives on Windows). + // + basic_path + relative (basic_path) const; + + // Iteration over path components. + // + public: + struct iterator + { + using value_type = string_type ; + using pointer = string_type*; + using reference = string_type ; + using size_type = typename string_type::size_type; + using difference_type = std::ptrdiff_t ; + using iterator_category = std::bidirectional_iterator_tag ; + + using data_type = path_data; + + iterator (): p_ (nullptr) {} + iterator (const data_type* p, size_type b, size_type e) + : p_ (p), b_ (b), e_ (e) {} + + // Create an iterator by "rebasing" an old iterator onto a new path + // object. Can, for example, be used to "move" an iterator when moving + // the path object. Note: potentially dangerous if the old iterator used + // to point to a different path. + // + iterator (const basic_path& p, const iterator& i) + : p_ (&p), b_ (i.b_), e_ (i.e_) {} + + iterator& + operator++ () + { + const string_type& s (p_->path_); + + // Position past trailing separator, if any. + // + b_ = e_ != string_type::npos && ++e_ != s.size () + ? e_ + : string_type::npos; + + // Find next trailing separator. + // + e_ = b_ != string_type::npos ? traits::find_separator (s, b_) : b_; + + return *this; + } + + iterator& + operator-- () + { + const string_type& s (p_->path_); + + // Find the new end. + // + e_ = b_ == string_type::npos // Past end? + ? (traits::is_separator (s.back ()) // Have trailing slash? + ? s.size () - 1 + : string_type::npos) + : b_ - 1; + + // Find the new begin. + // + b_ = e_ == 0 // Empty component? + ? string_type::npos + : traits::rfind_separator (s, e_ != string_type::npos ? e_ - 1 : e_); + + b_ = b_ == string_type::npos // First component? + ? 0 + : b_ + 1; + + return *this; + } + + iterator + operator++ (int) {iterator r (*this); operator++ (); return r;} + + iterator + operator-- (int) {iterator r (*this); operator-- (); return r;} + + string_type + operator* () const + { + return string_type (p_->path_, + b_, + e_ != string_type::npos ? e_ - b_ : e_); + } + + // Return the directory separator after this component or '\0' if there + // is none. This, for example, can be used to determine if the last + // component is a directory. + // + C + separator () const + { + return e_ != string_type::npos + ? p_->path_[e_] + : (p_->tsep_ > 0 + ? path_traits::directory_separators[p_->tsep_ - 1] + : 0); + } + + pointer operator-> () const = delete; + + friend bool + operator== (const iterator& x, const iterator& y) + { + return x.p_ == y.p_ && x.b_ == y.b_ && x.e_ == y.e_; + } + + friend bool + operator!= (const iterator& x, const iterator& y) {return !(x == y);} + + private: + friend class basic_path; + + // b - first character of component + // e - separator after component (or npos if none) + // b == npos && e == npos - one past last component (end) + // + const data_type* p_; + size_type b_; + size_type e_; + }; + + iterator begin () const; + iterator end () const; + + reverse_iterator rbegin () const {return reverse_iterator (end ());} + reverse_iterator rend () const {return reverse_iterator (begin ());} + + public: + // Canonicalize the path and return *this. Canonicalization involves + // converting all directory separators to the canonical form. Note that + // multiple directory separators are not collapsed. + // + basic_path& + canonicalize (); + + // Normalize the path and return *this. Normalization involves collapsing + // the '.' and '..' directories if possible, collapsing multiple + // directory separators, and converting all directory separators to the + // canonical form. If cur_empty is true then collapse relative paths + // representing the current directory (for example, '.', './', 'foo/..') + // to an empty path. Otherwise convert it to the canonical form (./ on + // POSIX systems). Note that a non-empty path cannot become an empty one + // in the latter case. + // + // If actual is true, then for case-insensitive filesystems obtain the + // actual spelling of the path. Only an absolute path can be actualized. + // If a path component does not exist, then its (and all subsequent) + // spelling is unchanged. This is a potentially expensive operation. + // Normally one can assume that "well-known" directories (current, home, + // etc.) are returned in their actual spelling. + // + basic_path& + normalize (bool actual = false, bool cur_empty = false); + + // Make the path absolute using the current directory unless it is already + // absolute. Return *this. + // + basic_path& + complete (); + + // Make the path real, that is, absolute, normalized, and with resolved + // symlinks. On POSIX systems this is accomplished with the call to + // realpath(3). On Windows -- complete() and normalize(). Return *this. + // + basic_path& + realize (); + + public: + basic_path& + operator/= (basic_path const&); + + // Combine a single path component (must not contain directory separators) + // as a string, without first constructing the path object. + // + basic_path& + operator/= (string_type const&); + + basic_path& + operator/= (const C*); + + // Append to the end of the path (normally an extension, etc). + // + basic_path& + operator+= (string_type const&); + + basic_path& + operator+= (const C*); + + basic_path& + operator+= (C); + + void + append (const C*, size_type); + + // Note that comparison is case-insensitive if the filesystem is not + // case-sensitive (e.g., Windows). And it ignored trailing slashes + // except for the root case. + // + template + int + compare (const basic_path& x) const { + return traits::compare (this->path_, x.path_);} + + public: + // Path string and representation. The string does not contain the + // trailing slash except for the root case. In other words, it is the + // "traditional" spelling of the path that can be passed to system calls, + // etc. Representation, on the other hand is the "precise" spelling that + // includes the trailing slash, if any. One cannot always round-trip a + // path using string() but can using representation(). Note also that + // representation() returns a copy while string() returns a (tracking) + // reference. + // + const string_type& + string () const& {return this->path_;} + + string_type + representation () const&; + + // Moves the underlying path string out of the path object. The path + // object becomes empty. Usage: std::move (p).string (). + // + string_type + string () && {string_type r; r.swap (this->path_); return r;} + + string_type + representation () &&; + + // Trailing directory separator or '\0' if there is none. + // + C + separator () const; + + // As above but return it as a (potentially empty) string. + // + string_type + separator_string () const; + + // If possible, return a POSIX version of the path. For example, for a + // Windows path in the form foo\bar this function will return foo/bar. If + // it is not possible to create a POSIX version for this path (e.g., + // c:\foo), this function will throw the invalid_path exception. + // + string_type + posix_string () const&; + + string_type + posix_representation () const&; + + string_type + posix_string () &&; + + string_type + posix_representation () &&; + + // Implementation details. + // + protected: + using data_type = path_data; + + // Direct initialization without init()/cast(). + // + explicit + basic_path (data_type&& d): base_type (std::move (d)) {} + + #ifndef _MSC_VER //@@ MOD VC ICE + using base_type::_size; + #endif + + // Common implementation for operator/= and operator+=. + // + void + combine (const C*, size_type, difference_type); + + void + combine (const C*, size_type); + + // Friends. + // + template + friend class basic_path; + + template + friend basic_path + path_cast_impl (const basic_path&, basic_path*); + + template + friend basic_path + path_cast_impl (basic_path&&, basic_path*); + }; + + template + inline basic_path + operator/ (const basic_path& x, const basic_path& y) + { + basic_path r (x); + r /= y; + return r; + } + + template + inline basic_path + operator+ (const basic_path& x, const std::basic_string& y) + { + basic_path r (x); + r += y; + return r; + } + + template + inline basic_path + operator+ (const basic_path& x, const C* y) + { + basic_path r (x); + r += y; + return r; + } + + template + inline basic_path + operator+ (const basic_path& x, C y) + { + basic_path r (x); + r += y; + return r; + } + + template + inline bool + operator== (const basic_path& x, const basic_path& y) + { + return x.compare (y) == 0; + } + + template + inline bool + operator!= (const basic_path& x, const basic_path& y) + { + return !(x == y); + } + + template + inline bool + operator< (const basic_path& x, const basic_path& y) + { + return x.compare (y) < 0; + } + + // Additional operators for certain path kind combinations. + // + template + inline basic_path> + operator/ (const basic_path>& x, + const basic_path>& y) + { + basic_path> r (x); + r /= y; + return r; + } + + + // Note that the result of (foo / "bar") is always a path, even if foo + // is dir_path. An idiom to force it dir_path is this: + // + // dir_path foo_bar (dir_path (foo) /= "bar"); + // + template + inline basic_path> + operator/ (const basic_path& x, const std::basic_string& y) + { + basic_path> r (x); + r /= y; + return r; + } + + template + inline basic_path> + operator/ (const basic_path& x, const C* y) + { + basic_path> r (x); + r /= y; + return r; + } + + // For operator<< (ostream) see the path-io header. +} + +LIBBUTL_MODEXPORT namespace std +{ + template + struct hash>: hash> + { + using argument_type = butl::basic_path; + + size_t + operator() (const butl::basic_path& p) const noexcept + { +#ifndef _WIN32 + return hash>::operator() (p.string ()); +#else + // Case-insensitive FNV hash. + // + const auto& s (p.string ()); + + size_t hash (static_cast (2166136261UL)); + for (size_t i (0), n (s.size ()); i != n; ++i) + { + hash ^= static_cast (butl::lcase (s[i])); + + // We are using C-style cast to suppress VC warning for 32-bit target + // (the value is compiled but not used). + // + hash *= sizeof (size_t) == 4 + ? static_cast (16777619UL) + : (size_t) 1099511628211ULL; + } + return hash; +#endif + } + }; +} + +#include +#include diff --git a/libbutl/path.txx b/libbutl/path.txx index 17673f8..512e849 100644 --- a/libbutl/path.txx +++ b/libbutl/path.txx @@ -2,14 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include - -#ifdef _WIN32 -# include // replace() -#endif - -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { template basic_path basic_path:: @@ -138,6 +131,7 @@ namespace butl // Throw system_error in case of other failures. Result and dir can be the // same instance. // + LIBBUTL_MODEXPORT //@@ MOD VC doesn't "see" it in impl unit unless exported. template bool basic_path_append_actual_name (std::basic_string& result, diff --git a/libbutl/prefix-map.hxx b/libbutl/prefix-map.hxx deleted file mode 100644 index 0404f72..0000000 --- a/libbutl/prefix-map.hxx +++ /dev/null @@ -1,138 +0,0 @@ -// file : libbutl/prefix-map.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_PREFIX_MAP_HXX -#define LIBBUTL_PREFIX_MAP_HXX - -#include -#include -#include // move() -#include // min() - -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. The '.' and '/' above are the delimiter characters. - // - // Note that as a special rule, the default implementation of - // compare_prefix treats empty key as everyone's prefix even if - // the paths don't start with the delimiter (useful to represent - // a "root path"). - // - // Implementation-wise, the idea is to pretend that each key ends - // with the delimiter. This way we automatically avoid matching - // 'foobar' as having a prefix 'foo'. - // - template - struct compare_prefix; - - template - struct compare_prefix> - { - typedef std::basic_string K; - - typedef C delimiter_type; - typedef typename K::size_type size_type; - typedef typename K::traits_type traits_type; - - explicit - compare_prefix (delimiter_type d): d_ (d) {} - - bool - operator() (const K& x, const K& y) const - { - return compare (x.c_str (), x.size (), y.c_str (), y.size ()) < 0; - } - - bool - prefix (const K& p, const K& k) const - { - size_type pn (p.size ()), kn (k.size ()); - return pn == 0 || // Empty key is always a prefix. - (pn <= kn && - compare (p.c_str (), pn, k.c_str (), pn == kn ? pn : pn + 1) == 0); - } - - protected: - int - compare (const C* x, size_type xn, - const C* y, size_type yn) const - { - size_type n (std::min (xn, yn)); - int r (traits_type::compare (x, y, n)); - - if (r == 0) - { - // Pretend there is the delimiter characters at the end of the - // shorter string. - // - char xc (xn > n ? x[n] : (xn++, d_)); - char yc (yn > n ? y[n] : (yn++, d_)); - r = traits_type::compare (&xc, &yc, 1); - - // If we are still equal, then compare the lengths. - // - if (r == 0) - r = (xn == yn ? 0 : (xn < yn ? -1 : 1)); - } - - return r; - } - - private: - delimiter_type d_; - }; - - template - struct prefix_map_common: M - { - typedef M map_type; - typedef typename map_type::key_type key_type; - typedef typename map_type::value_type value_type; - typedef typename map_type::key_compare compare_type; - typedef typename compare_type::delimiter_type delimiter_type; - - typedef typename map_type::iterator iterator; - typedef typename map_type::const_iterator const_iterator; - - explicit - prefix_map_common (delimiter_type d) - : map_type (compare_type (d)) {} - - prefix_map_common (std::initializer_list i, delimiter_type d) - : map_type (std::move (i), compare_type (d)) {} - - std::pair - find_prefix (const key_type&); - - std::pair - find_prefix (const key_type&) const; - }; - - template ::delimiter_type D> - struct prefix_map_impl: prefix_map_common - { - typedef typename prefix_map_common::value_type value_type; - - prefix_map_impl (): prefix_map_common (D) {} - prefix_map_impl (std::initializer_list i) - : prefix_map_common (std::move (i), D) {} - }; - - template ::delimiter_type D> - using prefix_map = prefix_map_impl>, D>; - - template ::delimiter_type D> - using prefix_multimap = - prefix_map_impl>, D>; -} - -#include - -#endif // LIBBUTL_PREFIX_MAP_HXX diff --git a/libbutl/prefix-map.mxx b/libbutl/prefix-map.mxx new file mode 100644 index 0000000..95a4f3b --- /dev/null +++ b/libbutl/prefix-map.mxx @@ -0,0 +1,152 @@ +// file : libbutl/prefix-map.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#include // move() +#include // min() +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.prefix_map; +#ifdef __cpp_lib_modules +import std.core; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT 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. The '.' and '/' above are the delimiter characters. + // + // Note that as a special rule, the default implementation of + // compare_prefix treats empty key as everyone's prefix even if + // the paths don't start with the delimiter (useful to represent + // a "root path"). + // + // Implementation-wise, the idea is to pretend that each key ends + // with the delimiter. This way we automatically avoid matching + // 'foobar' as having a prefix 'foo'. + // + template + struct compare_prefix; + + template + struct compare_prefix> + { + typedef std::basic_string K; + + typedef C delimiter_type; + typedef typename K::size_type size_type; + typedef typename K::traits_type traits_type; + + explicit + compare_prefix (delimiter_type d): d_ (d) {} + + bool + operator() (const K& x, const K& y) const + { + return compare (x.c_str (), x.size (), y.c_str (), y.size ()) < 0; + } + + bool + prefix (const K& p, const K& k) const + { + size_type pn (p.size ()), kn (k.size ()); + return pn == 0 || // Empty key is always a prefix. + (pn <= kn && + compare (p.c_str (), pn, k.c_str (), pn == kn ? pn : pn + 1) == 0); + } + + protected: + int + compare (const C* x, size_type xn, + const C* y, size_type yn) const + { + size_type n (std::min (xn, yn)); + int r (traits_type::compare (x, y, n)); + + if (r == 0) + { + // Pretend there is the delimiter characters at the end of the + // shorter string. + // + char xc (xn > n ? x[n] : (xn++, d_)); + char yc (yn > n ? y[n] : (yn++, d_)); + r = traits_type::compare (&xc, &yc, 1); + + // If we are still equal, then compare the lengths. + // + if (r == 0) + r = (xn == yn ? 0 : (xn < yn ? -1 : 1)); + } + + return r; + } + + private: + delimiter_type d_; + }; + + template + struct prefix_map_common: M + { + typedef M map_type; + typedef typename map_type::key_type key_type; + typedef typename map_type::value_type value_type; + typedef typename map_type::key_compare compare_type; + typedef typename compare_type::delimiter_type delimiter_type; + + typedef typename map_type::iterator iterator; + typedef typename map_type::const_iterator const_iterator; + + explicit + prefix_map_common (delimiter_type d) + : map_type (compare_type (d)) {} + + prefix_map_common (std::initializer_list i, delimiter_type d) + : map_type (std::move (i), compare_type (d)) {} + + std::pair + find_prefix (const key_type&); + + std::pair + find_prefix (const key_type&) const; + }; + + template ::delimiter_type D> + struct prefix_map_impl: prefix_map_common + { + typedef typename prefix_map_common::value_type value_type; + + prefix_map_impl (): prefix_map_common (D) {} + prefix_map_impl (std::initializer_list i) + : prefix_map_common (std::move (i), D) {} + }; + + template ::delimiter_type D> + using prefix_map = prefix_map_impl>, D>; + + template ::delimiter_type D> + using prefix_multimap = + prefix_map_impl>, D>; +} + +#include diff --git a/libbutl/prefix-map.txx b/libbutl/prefix-map.txx index f3cd29f..efcee88 100644 --- a/libbutl/prefix-map.txx +++ b/libbutl/prefix-map.txx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { template auto prefix_map_common:: diff --git a/libbutl/process-details.hxx b/libbutl/process-details.hxx index b078cbb..adb33fd 100644 --- a/libbutl/process-details.hxx +++ b/libbutl/process-details.hxx @@ -2,15 +2,18 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#ifndef LIBBUTL_PROCESS_DETAILS_HXX -#define LIBBUTL_PROCESS_DETAILS_HXX - -#include +#pragma once #include + +#ifdef __cpp_lib_modules +import std.core; //@@ MOD std.threading +#else +#include #if defined(__cpp_lib_shared_mutex) || defined(__cpp_lib_shared_timed_mutex) # include #endif +#endif namespace butl { @@ -45,5 +48,3 @@ namespace butl // extern shared_mutex process_spawn_mutex; } - -#endif // LIBBUTL_PROCESS_DETAILS_HXX diff --git a/libbutl/process-io.hxx b/libbutl/process-io.hxx deleted file mode 100644 index b70080c..0000000 --- a/libbutl/process-io.hxx +++ /dev/null @@ -1,28 +0,0 @@ -// file : libbutl/process-io.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_PROCESS_IO_HXX -#define LIBBUTL_PROCESS_IO_HXX - -#include - -#include - -namespace butl -{ - inline std::ostream& - operator<< (std::ostream& o, const process_path& p) - { - return o << p.recall_string (); - } - - inline std::ostream& - operator<< (std::ostream& o, const process_args& a) - { - process::print (o, a.argv, a.argc); - return o; - } -} - -#endif // LIBBUTL_PROCESS_IO_HXX diff --git a/libbutl/process-io.mxx b/libbutl/process-io.mxx new file mode 100644 index 0000000..dfd2221 --- /dev/null +++ b/libbutl/process-io.mxx @@ -0,0 +1,44 @@ +// file : libbutl/process-io.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.process_io; +#ifdef __cpp_lib_modules +import std.core; //@@ MOD TMP (should not be needed). +import std.io; +#endif +import butl.process; +#else +#include +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + inline std::ostream& + operator<< (std::ostream& o, const process_path& p) + { + return o << p.recall_string (); + } + + inline std::ostream& + operator<< (std::ostream& o, const process_args& a) + { + process::print (o, a.argv, a.argc); + return o; + } +} diff --git a/libbutl/process-run.cxx b/libbutl/process-run.cxx index 9c857d0..fd8abe3 100644 --- a/libbutl/process-run.cxx +++ b/libbutl/process-run.cxx @@ -2,10 +2,35 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif +// C includes. + +#ifndef __cpp_lib_modules #include // exit() #include // cerr +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.process; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.path; +#endif + +import butl.utility; // operator<<(ostream,exception) +#else +#include +#endif using namespace std; diff --git a/libbutl/process-run.txx b/libbutl/process-run.txx index a9c7e8b..d8d4cb7 100644 --- a/libbutl/process-run.txx +++ b/libbutl/process-run.txx @@ -2,10 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include // forward(), index_sequence - -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { template process_env:: diff --git a/libbutl/process.cxx b/libbutl/process.cxx index dce8d6b..e65018a 100644 --- a/libbutl/process.cxx +++ b/libbutl/process.cxx @@ -2,7 +2,11 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif + +#include #ifndef _WIN32 # include // setenv(), unsetenv() @@ -27,8 +31,7 @@ # include // _MAX_PATH # include // stat # include // stat(), S_IS* -# include // GetEnvironmentStringsA(), - // FreeEnvironmentStringsA() +# include // {Get,Free}EnvironmentStringsA() # ifdef _MSC_VER // Unlikely to be fixed in newer versions. # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) @@ -37,29 +40,66 @@ # define STDOUT_FILENO 1 # define STDERR_FILENO 2 # endif // _MSC_VER - -# include -# include -# include // getenv(), __argv[] - -# include -# include #endif -#include +#include + +#ifndef __cpp_lib_modules +#include +#include +#include +#include +#include #include // ios_base::failure -#include -#include // size_t #include // strlen(), strchr() #include // move() #include -#include // casecmp() -#include // fdnull() +#ifdef _WIN32 +#include +#include +#include // getenv(), __argv[] +#endif +#endif + #include -#include +namespace butl +{ + shared_mutex process_spawn_mutex; // Out of module purview. +} + +#ifdef __cpp_modules +module butl.process; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.path; +import butl.optional; +import butl.fdstream; +import butl.vector_view; +import butl.small_vector; +#endif + +import butl.utility; // casecmp() +import butl.fdstream; // fdnull() +#ifdef _WIN32 +import butl.timestamp; +#endif + +#else +#include +#include + +#ifdef _WIN32 +#include +#endif +#endif using namespace std; @@ -69,8 +109,6 @@ using namespace butl::win32; namespace butl { - shared_mutex process_spawn_mutex; - // process // static process_path diff --git a/libbutl/process.hxx b/libbutl/process.hxx deleted file mode 100644 index 3e1a990..0000000 --- a/libbutl/process.hxx +++ /dev/null @@ -1,649 +0,0 @@ -// file : libbutl/process.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_PROCESS_HXX -#define LIBBUTL_PROCESS_HXX - -#ifndef _WIN32 -# include // pid_t -#endif - -#include -#include -#include -#include -#include // uint32_t -#include - -#include -#include -#include -#include // auto_fd, fdpipe -#include -#include - -namespace butl -{ - struct process_error: std::system_error - { - const bool child; - - process_error (int e, bool child = false) - : system_error (e, std::generic_category ()), child (child) {} - -#ifdef _WIN32 - process_error (const std::string& d, int fallback_errno_code = 0) - : system_error (fallback_errno_code, std::system_category (), d), - child (false) {} -#endif - }; - - struct process_child_error: process_error - { - explicit - process_child_error (int e): process_error (e, true) {} - }; - - // Process arguments (i.e., the command line). The first must be an - // executable name and the last element should be NULL. Can also be the - // multi-process piped command line (see process::print() for details). - // - struct process_args - { - const char* const* argv; - std::size_t argc; - }; - - // A process executable has three paths: initial, recall, and effective. - // Initial is the original "command" that you specify in argv[0] and on - // POSIX that's what ends up in the child's argv[0]. But not on Windows. On - // Windows the command is first searched for in the parent executable's - // directory and if found then that's what should end up in child's argv[0]. - // So this is the recall path. It is called recall because this is what the - // caller of the parent process will be able to execute if you printed the - // command line (provided you haven't changed the CWD). Finally, effective - // is the absolute path to the executable that will include the directory - // part if found in PATH, the .exe extension if one is missing, etc. - // - // As an example, let's say we run foo\foo.exe that itself spawns bar which - // is found as foo\bar.exe. The paths will then be: - // - // initial: bar - // recall: foo\bar - // effective: c:\...\foo\bar.exe - // - // In most cases, at least on POSIX, the first two paths will be the same. - // As an optimization, if the recall path is empty, then it means it is the - // same as initial. Similarly, if the effective path is empty then, it is - // the same as recall (and if that is empty, as initial). - // - // Note that the call to path_search() below adjust args[0] to point to the - // recall path which brings up lifetime issues. To address this this class - // also implements an RAII-based auto-restore of args[0] to its initial - // value. - // - class process_path - { - public: - const char* initial = nullptr; - path recall; - path effect; - - // Handle empty recall/effect. - // - const char* recall_string () const; - const char* effect_string () const; - - bool empty () const - { - return initial == nullptr && recall.empty () && effect.empty (); - } - - // Moveable-only type. - // - process_path (process_path&&); - process_path& operator= (process_path&&); - - process_path (const process_path&) = delete; - process_path& operator= (const process_path&) = delete; - - process_path () = default; - process_path (const char* i, path&& r, path&& e); - ~process_path (); - - private: - friend class process; - const char** args0_ = nullptr; - }; - - // Process exit information. - // - struct LIBBUTL_SYMEXPORT process_exit - { - // Status type is the raw exit value as returned by GetExitCodeProcess() - // (NTSTATUS value that represents exit or error codes; MSDN refers to the - // error code as "value of the exception that caused the termination") or - // waitpid(1). Code type is the return value if the process exited - // normally. - // -#ifndef _WIN32 - using status_type = int; - using code_type = std::uint8_t; -#else - using status_type = std::uint32_t; // Win32 DWORD - using code_type = std::uint16_t; // Win32 WORD -#endif - - status_type status; - - process_exit () = default; - - explicit - process_exit (code_type); - - enum as_status_type {as_status}; - process_exit (status_type s, as_status_type): status (s) {} - - // Return false if the process exited abnormally. - // - bool - normal () const; - - code_type - code () const; - - explicit operator bool () const {return normal () && code () == 0;} - - // Abnormal termination information. - // - - // Return the signal number that caused the termination or 0 if no such - // information is available. - // - int - signal () const; - - // Return true if the core file was generated. - // - bool - core () const; - - // Return a description of the reason that caused the process to terminate - // abnormally. On POSIX this is the signal name, on Windows -- the summary - // produced from the corresponding error identifier defined in ntstatus.h. - // - std::string - description () const; - }; - - class LIBBUTL_SYMEXPORT process - { - public: -#ifndef _WIN32 - using handle_type = pid_t; - using id_type = pid_t; -#else - using handle_type = void*; // Win32 HANDLE - using id_type = std::uint32_t; // Win32 DWORD -#endif - - // Start another process using the specified command line. The default - // values to the in, out and err arguments indicate that the child process - // should inherit the parent process stdin, stdout, and stderr, - // respectively. If -1 is passed instead, then the corresponding child - // process descriptor is connected (via a pipe) to out_fd for stdin, - // in_ofd for stdout, and in_efd for stderr (see data members below). If - // -2 is passed, then the corresponding child process descriptor is - // replaced with the null device descriptor (e.g., /dev/null). This - // results in the child process not being able to read anything from stdin - // (gets immediate EOF) and all data written to stdout/stderr being - // discarded. - // - // On Windows parent process pipe descriptors are set to text mode to be - // consistent with the default (text) mode of standard file descriptors of - // the child process. When reading in the text mode the sequence of 0xD, - // 0xA characters is translated into the single OxA character and 0x1A is - // interpreted as EOF. When writing in the text mode the OxA character is - // translated into the 0xD, 0xA sequence. Use the fdmode() function to - // change the mode, if required. - // - // Instead of passing -1, -2 or the default value, you can also pass your - // own descriptors. Note, however, that in this case they are not closed by - // 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); - // - // The cwd argument allows to change the current working directory of the - // child process. NULL and empty arguments are ignored. - // - // The envvars argument allows to set and unset environment variables in - // the child process. If not NULL, it must contain strings in the - // "name=value" (set) or "name" (unset) forms and be terminated with - // NULL. Note that all other variables are inherited from the parent - // process. - // - // Throw process_error if anything goes wrong. Note that some of the - // exceptions (e.g., if exec() failed) can be thrown in the child - // version of us (as process_child_error). - // - // Note that the versions without the the process_path argument may - // temporarily change args[0] (see path_search() for details). - // - process (const char* [], - int = 0, int = 1, int = 2, - const char* cwd = nullptr, - const char* const* envvars = nullptr); - - process (const process_path&, const char* [], - int = 0, int = 1, int = 2, - const char* cwd = nullptr, - const char* const* envvars = nullptr); - - // The "piping" constructor, for example: - // - // process lhs (..., 0, -1); // Redirect stdout to a pipe. - // process rhs (..., lhs); // Redirect stdin to lhs's pipe. - // - // rhs.wait (); // Wait for last first. - // lhs.wait (); - // - process (const char* [], - process&, int = 1, int = 2, - const char* cwd = nullptr, - const char* const* envvars = nullptr); - - process (const process_path&, const char* [], - process&, int = 1, int = 2, - 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 - // be called multiple times with subsequent calls simply returning the - // status. - // - bool - wait (bool ignore_errors = false); - - // Return true if the process has already terminated in which case - // optionally set the argument to the result of wait(). - // - bool - try_wait (); - - bool - try_wait (bool&); - - // Note that the destructor will wait for the process but will ignore - // any errors and the exit status. - // - ~process () {if (handle != 0) wait (true);} - - // Moveable-only type. - // - process (process&&); - process& operator= (process&&); - - process (const process&) = delete; - process& operator= (const process&) = delete; - - // Create an empty or "already terminated" process. By default the - // termination status is unknown but you can change that. - // - explicit - process (optional = nullopt); - - // Resolve process' paths based on the initial path in args0. If recall - // differs from initial, adjust args0 to point to the recall path. If - // resolution fails, throw process_error. Normally, you will use this - // function like this: - // - // const char* args[] = {"foo", ..., nullptr}; - // - // process_path pp (process::path_search (args[0])) - // - // ... // E.g., print args[0]. - // - // process p (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 - // parent executable's directory" semantics across platforms. - // - // If path_only is true then only search in the PATH environment variable - // (or in CWD if there is a directory component) ignorting other places - // (like calling process' directory and, gasp, CWD on Windows). - // - static process_path - path_search (const char*& args0, - const dir_path& fallback = dir_path (), - bool path_only = false); - - // This version is primarily useful when you want to pre-search the - // executable before creating the args[] array. In this case you will - // use the recall path for args[0]. - // - // The init argument determines whether to initialize the initial path to - // the shallow copy of file. If it is true, then initial is the same as - // file and recall is either empty or contain a different path. If it is - // false then initial contains a shallow copy of recall, and recall is - // either a different path or a deep copy of file. Normally you don't care - // about initial once you got recall and the main reason to pass true to - // this argument is to save a copy (since initial and recall are usually - // the same). - // - static process_path - path_search (const char* file, bool init, - const dir_path& = dir_path (), bool = false); - - static process_path - path_search (const std::string&, bool, - const dir_path& = dir_path (), bool = false); - - static process_path - path_search (const path&, bool, - const dir_path& = dir_path (), bool = false); - - // As above but if not found return empty process_path instead of - // throwing. - // - static process_path - try_path_search (const char*, bool, - const dir_path& = dir_path (), bool = false); - - static process_path - try_path_search (const std::string&, bool, - const dir_path& = dir_path (), bool = false); - - static process_path - try_path_search (const path&, bool, - const dir_path& = dir_path (), bool = false); - - // Print process commmand line. If the number of elements is specified, - // then it will print the piped multi-process command line, if present. - // In this case, the expected format is as follows: - // - // name1 arg arg ... nullptr - // name2 arg arg ... nullptr - // ... - // nameN arg arg ... nullptr nullptr - // - static void - print (std::ostream&, const char* const args[], size_t n = 0); - - public: - id_type - id () const; - - static id_type - current_id (); - - public: - handle_type 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 exit; - - // Use the following file descriptors to communicate with the new process's - // standard streams. - // - auto_fd out_fd; // Write to it to send to stdin. - auto_fd in_ofd; // Read from it to receive from stdout. - auto_fd in_efd; // Read from it to receive from stderr. - }; - - // Higher-level process running interface that aims to make executing a - // process for the common cases as simple as calling a functions. Normally - // it is further simplified by project-specific wrapper functions that - // handle the process_error exception as well as abnormal and/or non-zero - // exit status. - // - // The I/O/E arguments determine the child's stdin/stdout/stderr. They can - // be of type int, auto_fd (and, in the future, perhaps also fd_pipe, - // string, buffer, etc). For example, the following call will make stdin - // read from /dev/null, stdout redirect to stderr, and inherit the parent's - // stderr. - // - // process_run (fdnull (), 2, 2, ...) - // - // The P argument is the program path. It can be anything that can be passed - // to process::path_search() (const char*, std::string, path) or the - // process_path itself. - // - // The A arguments can be anything convertible to const char* via the - // overloaded process_arg_as() (see below). Out of the box you can use const - // char*, std::string, path/dir_path, (as well as [small_]vector[_view] of - // these), and numeric types. - // - struct process_env - { - const process_path* path; - const dir_path* cwd = nullptr; - const char* const* vars = nullptr; - - process_env (const process_path& p, - const dir_path& c = dir_path (), - const char* const* v = nullptr) - : path (&p), - - // Note that this is not just an optimization. It is required when - // the ctor is called with the default arguments (not to keep the - // temporary object pointer). - // - cwd (!c.empty () ? &c : nullptr), - - vars (v) {} - - process_env (const process_path& p, const char* const* v) - : path (&p), cwd (nullptr), vars (v) {} - - template - process_env (const process_path& p, const dir_path& c, const V& v) - : process_env (p, v) {cwd = &c;} - - template - process_env (const process_path& p, const V& v); - - process_env (const char* p, - const dir_path& c = dir_path (), - const char* const* v = nullptr) - : process_env (path_, c, v) {path_ = process::path_search (p, true);} - - process_env (const std::string& p, - const dir_path& c = dir_path (), - const char* const* v = nullptr) - : process_env (p.c_str (), c, v) {} - - process_env (const butl::path& p, - const dir_path& c = dir_path (), - const char* const* v = nullptr) - : process_env (p.string (), c, v) {} - - template - process_env (const char* p, const dir_path& c, const V& v) - : process_env (path_, c, v) {path_ = process::path_search (p, true);} - - template - process_env (const std::string& p, const dir_path& c, const V& v) - : process_env (p.c_str (), c, v) {} - - template - process_env (const butl::path& p, const dir_path& c, const V& v) - : process_env (p.string (), c, v) {} - - template - process_env (const char* p, const V& v) - : process_env (path_, v) {path_ = process::path_search (p, true);} - - template - process_env (const std::string& p, const V& v) - : process_env (p.c_str (), v) {} - - template - process_env (const butl::path& p, const V& v) - : process_env (p.string (), v) {} - - private: - process_path path_; - small_vector vars_; - }; - - template - process_exit - process_run (I&& in, - O&& out, - E&& err, - const process_env&, - A&&... args); - - // The version with the command callback that can be used for printing the - // command line or similar. It should be callable with the following - // signature: - // - // void (const char*[], std::size_t) - // - template - process_exit - process_run_callback (const C&, - I&& in, - O&& out, - E&& err, - const process_env&, - A&&... args); - - // Versions that start the process without waiting. - // - template - process - process_start (I&& in, - O&& out, - E&& err, - const process_env&, - A&&... args); - - template - process - process_start_callback (const C&, - I&& in, - O&& out, - E&& err, - 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 - // also any type that defines to_string() (via ADL). - // - template - inline const char* - process_arg_as (const T& x, std::string& storage) - { - return (storage = std::to_string (x)).c_str (); - } - - inline const char* - process_arg_as (const std::string& s, std::string&) {return s.c_str ();} - - template - inline const char* - process_arg_as (const basic_path& p, std::string&) - { - return p.string ().c_str (); - } - - // char[N] - // - inline const char* - process_arg_as (const char* s, std::string&) {return s;} - - template - inline const char* - process_arg_as (char (&s)[N], std::string&) {return s;} - - template - inline const char* - process_arg_as (const char (&s)[N], std::string&) {return s;} - - template - inline void - process_args_as (V& v, const T& x, std::string& storage) - { - v.push_back (process_arg_as (x, storage)); - } - - // [small_]vector[_view]<> - // - template - inline void - process_args_as (V& v, const std::vector& vs, std::string&) - { - for (const std::string& s: vs) - v.push_back (s.c_str ()); - } - - template - inline void - process_args_as (V& v, const small_vector& vs, std::string&) - { - for (const std::string& s: vs) - v.push_back (s.c_str ()); - } - - template - inline void - process_args_as (V& v, const vector_view& vs, std::string&) - { - for (const std::string& s: vs) - v.push_back (s.c_str ()); - } - - template - inline void - process_args_as (V& v, const std::vector& vs, std::string&) - { - for (const char* s: vs) - v.push_back (s); - } - - template - inline void - process_args_as (V& v, const small_vector& vs, std::string&) - { - for (const char* s: vs) - v.push_back (s); - } - - template - inline void - process_args_as (V& v, const vector_view& vs, std::string&) - { - for (const char* s: vs) - v.push_back (s); - } -} - -#include - -#include - -#endif // LIBBUTL_PROCESS_HXX diff --git a/libbutl/process.ixx b/libbutl/process.ixx index 1f1a2f4..c355855 100644 --- a/libbutl/process.ixx +++ b/libbutl/process.ixx @@ -2,8 +2,6 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // move() - namespace butl { // process_path @@ -35,14 +33,21 @@ namespace butl inline process_path& process_path:: operator= (process_path&& p) { + if (this != &p) { if (args0_ != nullptr) *args0_ = initial; initial = p.initial; + +#if defined(__cpp_modules) && defined(__clang__) //@@ MOD Clang ICE + recall = p.recall; + effect = p.effect; +#else recall = std::move (p.recall); effect = std::move (p.effect); +#endif args0_ = p.args0_; p.args0_ = nullptr; diff --git a/libbutl/process.mxx b/libbutl/process.mxx new file mode 100644 index 0000000..abf7d35 --- /dev/null +++ b/libbutl/process.mxx @@ -0,0 +1,669 @@ +// file : libbutl/process.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +#ifndef _WIN32 +# include // pid_t +#endif + +#include + +#ifndef __cpp_lib_modules +#include +#include +#include +#include // size_t +#include // uint32_t +#include + +#include // move(), forward(), index_sequence +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.process; +#ifdef __cpp_lib_modules +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 +#include +#include +#include +#include +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + struct process_error: std::system_error + { + const bool child; + + process_error (int e, bool child = false) + : system_error (e, std::generic_category ()), child (child) {} + +#ifdef _WIN32 + process_error (const std::string& d, int fallback_errno_code = 0) + : system_error (fallback_errno_code, std::system_category (), d), + child (false) {} +#endif + }; + + struct process_child_error: process_error + { + explicit + process_child_error (int e): process_error (e, true) {} + }; + + // Process arguments (i.e., the command line). The first must be an + // executable name and the last element should be NULL. Can also be the + // multi-process piped command line (see process::print() for details). + // + struct process_args + { + const char* const* argv; + std::size_t argc; + }; + + // A process executable has three paths: initial, recall, and effective. + // Initial is the original "command" that you specify in argv[0] and on + // POSIX that's what ends up in the child's argv[0]. But not on Windows. On + // Windows the command is first searched for in the parent executable's + // directory and if found then that's what should end up in child's argv[0]. + // So this is the recall path. It is called recall because this is what the + // caller of the parent process will be able to execute if you printed the + // command line (provided you haven't changed the CWD). Finally, effective + // is the absolute path to the executable that will include the directory + // part if found in PATH, the .exe extension if one is missing, etc. + // + // As an example, let's say we run foo\foo.exe that itself spawns bar which + // is found as foo\bar.exe. The paths will then be: + // + // initial: bar + // recall: foo\bar + // effective: c:\...\foo\bar.exe + // + // In most cases, at least on POSIX, the first two paths will be the same. + // As an optimization, if the recall path is empty, then it means it is the + // same as initial. Similarly, if the effective path is empty then, it is + // the same as recall (and if that is empty, as initial). + // + // Note that the call to path_search() below adjust args[0] to point to the + // recall path which brings up lifetime issues. To address this this class + // also implements an RAII-based auto-restore of args[0] to its initial + // value. + // + class process_path + { + public: + const char* initial = nullptr; + path recall; + path effect; + + // Handle empty recall/effect. + // + const char* recall_string () const; + const char* effect_string () const; + + bool empty () const + { + return initial == nullptr && recall.empty () && effect.empty (); + } + + // Moveable-only type. + // + process_path (process_path&&); + process_path& operator= (process_path&&); + + process_path (const process_path&) = delete; + process_path& operator= (const process_path&) = delete; + + process_path () = default; + process_path (const char* i, path&& r, path&& e); + ~process_path (); + + private: + friend class process; + const char** args0_ = nullptr; + }; + + // Process exit information. + // + struct LIBBUTL_SYMEXPORT process_exit + { + // Status type is the raw exit value as returned by GetExitCodeProcess() + // (NTSTATUS value that represents exit or error codes; MSDN refers to the + // error code as "value of the exception that caused the termination") or + // waitpid(1). Code type is the return value if the process exited + // normally. + // +#ifndef _WIN32 + using status_type = int; + using code_type = std::uint8_t; +#else + using status_type = std::uint32_t; // Win32 DWORD + using code_type = std::uint16_t; // Win32 WORD +#endif + + status_type status; + + process_exit () = default; + + explicit + process_exit (code_type); + + enum as_status_type {as_status}; + process_exit (status_type s, as_status_type): status (s) {} + + // Return false if the process exited abnormally. + // + bool + normal () const; + + code_type + code () const; + + explicit operator bool () const {return normal () && code () == 0;} + + // Abnormal termination information. + // + + // Return the signal number that caused the termination or 0 if no such + // information is available. + // + int + signal () const; + + // Return true if the core file was generated. + // + bool + core () const; + + // Return a description of the reason that caused the process to terminate + // abnormally. On POSIX this is the signal name, on Windows -- the summary + // produced from the corresponding error identifier defined in ntstatus.h. + // + std::string + description () const; + }; + + class LIBBUTL_SYMEXPORT process + { + public: +#ifndef _WIN32 + using handle_type = pid_t; + using id_type = pid_t; +#else + using handle_type = void*; // Win32 HANDLE + using id_type = std::uint32_t; // Win32 DWORD +#endif + + // Start another process using the specified command line. The default + // values to the in, out and err arguments indicate that the child process + // should inherit the parent process stdin, stdout, and stderr, + // respectively. If -1 is passed instead, then the corresponding child + // process descriptor is connected (via a pipe) to out_fd for stdin, + // in_ofd for stdout, and in_efd for stderr (see data members below). If + // -2 is passed, then the corresponding child process descriptor is + // replaced with the null device descriptor (e.g., /dev/null). This + // results in the child process not being able to read anything from stdin + // (gets immediate EOF) and all data written to stdout/stderr being + // discarded. + // + // On Windows parent process pipe descriptors are set to text mode to be + // consistent with the default (text) mode of standard file descriptors of + // the child process. When reading in the text mode the sequence of 0xD, + // 0xA characters is translated into the single OxA character and 0x1A is + // interpreted as EOF. When writing in the text mode the OxA character is + // translated into the 0xD, 0xA sequence. Use the fdmode() function to + // change the mode, if required. + // + // Instead of passing -1, -2 or the default value, you can also pass your + // own descriptors. Note, however, that in this case they are not closed by + // 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); + // + // The cwd argument allows to change the current working directory of the + // child process. NULL and empty arguments are ignored. + // + // The envvars argument allows to set and unset environment variables in + // the child process. If not NULL, it must contain strings in the + // "name=value" (set) or "name" (unset) forms and be terminated with + // NULL. Note that all other variables are inherited from the parent + // process. + // + // Throw process_error if anything goes wrong. Note that some of the + // exceptions (e.g., if exec() failed) can be thrown in the child + // version of us (as process_child_error). + // + // Note that the versions without the the process_path argument may + // temporarily change args[0] (see path_search() for details). + // + process (const char* [], + int = 0, int = 1, int = 2, + const char* cwd = nullptr, + const char* const* envvars = nullptr); + + process (const process_path&, const char* [], + int = 0, int = 1, int = 2, + const char* cwd = nullptr, + const char* const* envvars = nullptr); + + // The "piping" constructor, for example: + // + // process lhs (..., 0, -1); // Redirect stdout to a pipe. + // process rhs (..., lhs); // Redirect stdin to lhs's pipe. + // + // rhs.wait (); // Wait for last first. + // lhs.wait (); + // + process (const char* [], + process&, int = 1, int = 2, + const char* cwd = nullptr, + const char* const* envvars = nullptr); + + process (const process_path&, const char* [], + process&, int = 1, int = 2, + 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 + // be called multiple times with subsequent calls simply returning the + // status. + // + bool + wait (bool ignore_errors = false); + + // Return true if the process has already terminated in which case + // optionally set the argument to the result of wait(). + // + bool + try_wait (); + + bool + try_wait (bool&); + + // Note that the destructor will wait for the process but will ignore + // any errors and the exit status. + // + ~process () {if (handle != 0) wait (true);} + + // Moveable-only type. + // + process (process&&); + process& operator= (process&&); + + process (const process&) = delete; + process& operator= (const process&) = delete; + + // Create an empty or "already terminated" process. By default the + // termination status is unknown but you can change that. + // + explicit + process (optional = nullopt); + + // Resolve process' paths based on the initial path in args0. If recall + // differs from initial, adjust args0 to point to the recall path. If + // resolution fails, throw process_error. Normally, you will use this + // function like this: + // + // const char* args[] = {"foo", ..., nullptr}; + // + // process_path pp (process::path_search (args[0])) + // + // ... // E.g., print args[0]. + // + // process p (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 + // parent executable's directory" semantics across platforms. + // + // If path_only is true then only search in the PATH environment variable + // (or in CWD if there is a directory component) ignorting other places + // (like calling process' directory and, gasp, CWD on Windows). + // + static process_path + path_search (const char*& args0, + const dir_path& fallback = dir_path (), + bool path_only = false); + + // This version is primarily useful when you want to pre-search the + // executable before creating the args[] array. In this case you will + // use the recall path for args[0]. + // + // The init argument determines whether to initialize the initial path to + // the shallow copy of file. If it is true, then initial is the same as + // file and recall is either empty or contain a different path. If it is + // false then initial contains a shallow copy of recall, and recall is + // either a different path or a deep copy of file. Normally you don't care + // about initial once you got recall and the main reason to pass true to + // this argument is to save a copy (since initial and recall are usually + // the same). + // + static process_path + path_search (const char* file, bool init, + const dir_path& = dir_path (), bool = false); + + static process_path + path_search (const std::string&, bool, + const dir_path& = dir_path (), bool = false); + + static process_path + path_search (const path&, bool, + const dir_path& = dir_path (), bool = false); + + // As above but if not found return empty process_path instead of + // throwing. + // + static process_path + try_path_search (const char*, bool, + const dir_path& = dir_path (), bool = false); + + static process_path + try_path_search (const std::string&, bool, + const dir_path& = dir_path (), bool = false); + + static process_path + try_path_search (const path&, bool, + const dir_path& = dir_path (), bool = false); + + // Print process commmand line. If the number of elements is specified, + // then it will print the piped multi-process command line, if present. + // In this case, the expected format is as follows: + // + // name1 arg arg ... nullptr + // name2 arg arg ... nullptr + // ... + // nameN arg arg ... nullptr nullptr + // + static void + print (std::ostream&, const char* const args[], size_t n = 0); + + public: + id_type + id () const; + + static id_type + current_id (); + + public: + handle_type 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 exit; + + // Use the following file descriptors to communicate with the new process's + // standard streams. + // + auto_fd out_fd; // Write to it to send to stdin. + auto_fd in_ofd; // Read from it to receive from stdout. + auto_fd in_efd; // Read from it to receive from stderr. + }; + + // Higher-level process running interface that aims to make executing a + // process for the common cases as simple as calling a functions. Normally + // it is further simplified by project-specific wrapper functions that + // handle the process_error exception as well as abnormal and/or non-zero + // exit status. + // + // The I/O/E arguments determine the child's stdin/stdout/stderr. They can + // be of type int, auto_fd (and, in the future, perhaps also fd_pipe, + // string, buffer, etc). For example, the following call will make stdin + // read from /dev/null, stdout redirect to stderr, and inherit the parent's + // stderr. + // + // process_run (fdnull (), 2, 2, ...) + // + // The P argument is the program path. It can be anything that can be passed + // to process::path_search() (const char*, std::string, path) or the + // process_path itself. + // + // The A arguments can be anything convertible to const char* via the + // overloaded process_arg_as() (see below). Out of the box you can use const + // char*, std::string, path/dir_path, (as well as [small_]vector[_view] of + // these), and numeric types. + // + struct process_env + { + const process_path* path; + const dir_path* cwd = nullptr; + const char* const* vars = nullptr; + + process_env (const process_path& p, + const dir_path& c = dir_path (), + const char* const* v = nullptr) + : path (&p), + + // Note that this is not just an optimization. It is required when + // the ctor is called with the default arguments (not to keep the + // temporary object pointer). + // + cwd (!c.empty () ? &c : nullptr), + + vars (v) {} + + process_env (const process_path& p, const char* const* v) + : path (&p), cwd (nullptr), vars (v) {} + + template + process_env (const process_path& p, const dir_path& c, const V& v) + : process_env (p, v) {cwd = &c;} + + template + process_env (const process_path& p, const V& v); + + process_env (const char* p, + const dir_path& c = dir_path (), + const char* const* v = nullptr) + : process_env (path_, c, v) {path_ = process::path_search (p, true);} + + process_env (const std::string& p, + const dir_path& c = dir_path (), + const char* const* v = nullptr) + : process_env (p.c_str (), c, v) {} + + process_env (const butl::path& p, + const dir_path& c = dir_path (), + const char* const* v = nullptr) + : process_env (p.string (), c, v) {} + + template + process_env (const char* p, const dir_path& c, const V& v) + : process_env (path_, c, v) {path_ = process::path_search (p, true);} + + template + process_env (const std::string& p, const dir_path& c, const V& v) + : process_env (p.c_str (), c, v) {} + + template + process_env (const butl::path& p, const dir_path& c, const V& v) + : process_env (p.string (), c, v) {} + + template + process_env (const char* p, const V& v) + : process_env (path_, v) {path_ = process::path_search (p, true);} + + template + process_env (const std::string& p, const V& v) + : process_env (p.c_str (), v) {} + + template + process_env (const butl::path& p, const V& v) + : process_env (p.string (), v) {} + + private: + process_path path_; + small_vector vars_; + }; + + template + process_exit + process_run (I&& in, + O&& out, + E&& err, + const process_env&, + A&&... args); + + // The version with the command callback that can be used for printing the + // command line or similar. It should be callable with the following + // signature: + // + // void (const char*[], std::size_t) + // + template + process_exit + process_run_callback (const C&, + I&& in, + O&& out, + E&& err, + const process_env&, + A&&... args); + + // Versions that start the process without waiting. + // + template + process + process_start (I&& in, + O&& out, + E&& err, + const process_env&, + A&&... args); + + template + process + process_start_callback (const C&, + I&& in, + O&& out, + E&& err, + 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 + // also any type that defines to_string() (via ADL). + // + template + inline const char* + process_arg_as (const T& x, std::string& storage) + { + return (storage = std::to_string (x)).c_str (); + } + + inline const char* + process_arg_as (const std::string& s, std::string&) {return s.c_str ();} + + template + inline const char* + process_arg_as (const basic_path& p, std::string&) + { + return p.string ().c_str (); + } + + // char[N] + // + inline const char* + process_arg_as (const char* s, std::string&) {return s;} + + template + inline const char* + process_arg_as (char (&s)[N], std::string&) {return s;} + + template + inline const char* + process_arg_as (const char (&s)[N], std::string&) {return s;} + + template + inline void + process_args_as (V& v, const T& x, std::string& storage) + { + v.push_back (process_arg_as (x, storage)); + } + + // [small_]vector[_view]<> + // + template + inline void + process_args_as (V& v, const std::vector& vs, std::string&) + { + for (const std::string& s: vs) + v.push_back (s.c_str ()); + } + + template + inline void + process_args_as (V& v, const small_vector& vs, std::string&) + { + for (const std::string& s: vs) + v.push_back (s.c_str ()); + } + + template + inline void + process_args_as (V& v, const vector_view& vs, std::string&) + { + for (const std::string& s: vs) + v.push_back (s.c_str ()); + } + + template + inline void + process_args_as (V& v, const std::vector& vs, std::string&) + { + for (const char* s: vs) + v.push_back (s); + } + + template + inline void + process_args_as (V& v, const small_vector& vs, std::string&) + { + for (const char* s: vs) + v.push_back (s); + } + + template + inline void + process_args_as (V& v, const vector_view& vs, std::string&) + { + for (const char* s: vs) + v.push_back (s); + } +} + +#include +#include diff --git a/libbutl/regex.cxx b/libbutl/regex.cxx index a177a74..1da325b 100644 --- a/libbutl/regex.cxx +++ b/libbutl/regex.cxx @@ -2,17 +2,42 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include - -#if defined(_MSC_VER) && _MSC_VER <= 1911 -# include // strstr() +#ifndef __cpp_modules +#include #endif +// C includes. + +#ifndef __cpp_lib_modules +#include +#include + #include #include #include // runtime_error +#if defined(_MSC_VER) && _MSC_VER <= 1911 +# include // strstr() +#endif +#endif + +// Other includes. -#include // operator<<(ostream, exception) +#ifdef __cpp_modules +module butl.regex; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +//@@ MOD TODO import std.regex; +#endif +#endif + +import butl.utility; // operator<<(ostream, exception) +#else +#include +#endif namespace std { diff --git a/libbutl/regex.hxx b/libbutl/regex.hxx deleted file mode 100644 index 2105f05..0000000 --- a/libbutl/regex.hxx +++ /dev/null @@ -1,76 +0,0 @@ -// file : libbutl/regex.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_REGEX_HXX -#define LIBBUTL_REGEX_HXX - -#include -#include -#include // basic_string -#include // pair - -#include - -namespace butl -{ - // Call specified append() function for non-matched substrings and matched - // substring replacements returning true if search succeeded. The function - // must be callable with the following signature: - // - // void - // append(basic_string::iterator begin, basic_string::iterator end); - // - // The regex semantics is like that of std::regex_replace() extended the - // standard ECMA-262 substitution escape sequences with a subset of Perl - // sequences: - // - // \\, \u, \l, \U, \L, \E, \1, ..., \9 - // - // Notes and limitations: - // - // - The only valid regex_constants flags are match_default, - // format_first_only and format_no_copy. - // - // - If backslash doesn't start any of the listed sequences then it is - // silently dropped and the following character is copied as is. - // - // - The character case conversion is performed according to the global - // C++ locale (which is, unless changed, is the same as C locale and - // both default to the POSIX locale aka "C"). - // - template - bool - regex_replace_ex (const std::basic_string&, - const std::basic_regex&, - const std::basic_string& fmt, - F&& append, - std::regex_constants::match_flag_type = - std::regex_constants::match_default); - - // As above but concatenate non-matched substrings and matched substring - // replacements into a string returning it as well as whether the search - // succeeded. - // - template - std::pair, bool> - regex_replace_ex (const std::basic_string&, - const std::basic_regex&, - const std::basic_string& fmt, - std::regex_constants::match_flag_type = - std::regex_constants::match_default); -} - -namespace std -{ - // Print regex error description but only if it is meaningful (this is also - // why we have to print leading colon). - // - LIBBUTL_SYMEXPORT ostream& - operator<< (ostream&, const regex_error&); -} - -#include -#include - -#endif // LIBBUTL_REGEX_HXX diff --git a/libbutl/regex.ixx b/libbutl/regex.ixx index dd3ad1d..f0dd687 100644 --- a/libbutl/regex.ixx +++ b/libbutl/regex.ixx @@ -2,9 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // move(), make_pair() - -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { template inline std::pair, bool> diff --git a/libbutl/regex.mxx b/libbutl/regex.mxx new file mode 100644 index 0000000..ca677a5 --- /dev/null +++ b/libbutl/regex.mxx @@ -0,0 +1,98 @@ +// file : libbutl/regex.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#include +#include // pair + +#include +#include // size_t +#include // move(), make_pair() +#endif + +#if defined(__clang__) && __has_include(<__config>) +# include <__config> // _LIBCPP_VERSION +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.regex; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +//@@ MOD TODO import std.regex; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // Call specified append() function for non-matched substrings and matched + // substring replacements returning true if search succeeded. The function + // must be callable with the following signature: + // + // void + // append(basic_string::iterator begin, basic_string::iterator end); + // + // The regex semantics is like that of std::regex_replace() extended the + // standard ECMA-262 substitution escape sequences with a subset of Perl + // sequences: + // + // \\, \u, \l, \U, \L, \E, \1, ..., \9 + // + // Notes and limitations: + // + // - The only valid regex_constants flags are match_default, + // format_first_only and format_no_copy. + // + // - If backslash doesn't start any of the listed sequences then it is + // silently dropped and the following character is copied as is. + // + // - The character case conversion is performed according to the global + // C++ locale (which is, unless changed, is the same as C locale and + // both default to the POSIX locale aka "C"). + // + template + bool + regex_replace_ex (const std::basic_string&, + const std::basic_regex&, + const std::basic_string& fmt, + F&& append, + std::regex_constants::match_flag_type = + std::regex_constants::match_default); + + // As above but concatenate non-matched substrings and matched substring + // replacements into a string returning it as well as whether the search + // succeeded. + // + template + std::pair, bool> + regex_replace_ex (const std::basic_string&, + const std::basic_regex&, + const std::basic_string& fmt, + std::regex_constants::match_flag_type = + std::regex_constants::match_default); +} + +LIBBUTL_MODEXPORT namespace std +{ + // Print regex error description but only if it is meaningful (this is also + // why we have to print leading colon). + // + LIBBUTL_SYMEXPORT ostream& + operator<< (ostream&, const regex_error&); +} + +#include +#include diff --git a/libbutl/regex.txx b/libbutl/regex.txx index dd1a539..d1daa44 100644 --- a/libbutl/regex.txx +++ b/libbutl/regex.txx @@ -2,10 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include // size_t, _LIBCPP_VERSION - -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { template bool diff --git a/libbutl/sendmail.cxx b/libbutl/sendmail.cxx index abdea32..cd6ddff 100644 --- a/libbutl/sendmail.cxx +++ b/libbutl/sendmail.cxx @@ -2,7 +2,32 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.sendmail; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +#endif +import butl.process; +import butl.fdstream; +import butl.small_vector; +#endif + +#endif using namespace std; diff --git a/libbutl/sendmail.hxx b/libbutl/sendmail.hxx deleted file mode 100644 index c631f18..0000000 --- a/libbutl/sendmail.hxx +++ /dev/null @@ -1,120 +0,0 @@ -// file : libbutl/sendmail.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_SENDMAIL_HXX -#define LIBBUTL_SENDMAIL_HXX - -#include - -#include - -#include -#include -#include - -namespace butl -{ - // Send email using the sendmail(1) program. - // - // Write the body of the email to out. Note that you must explicitly close - // it before calling wait(). Throw process_error and io_error (both derive - // from system_error) in case of errors. - // - // Typical usage: - // - // try - // { - // sendmail sm (2, // Diagnostics to stderr. - // "", // Default From: address. - // "Test subject", - // {"test@example.com"}); - // - // sm.out << "Test body" << endl; - // - // sm.out.close (); - // - // if (!sm.wait ()) - // ... // sendmail returned non-zero status. - // } - // catch (const std::system_error& e) - // { - // cerr << "sendmail error: " << e << endl; - // } - // - class LIBBUTL_SYMEXPORT sendmail: public process - { - public: - ofdstream out; - - // Notes: - // - // - If from is empty then the process user's address is used. - // - // - The to/cc/bcc addressed should already be quoted if required. - // - using recipients_type = small_vector; - - template - sendmail (E&& err, - const std::string& from, - const std::string& subject, - const recipients_type& to); - - template - sendmail (E&& err, - const std::string& from, - const std::string& subject, - const recipients_type& to, - const recipients_type& cc); - - template - sendmail (E&& err, - const std::string& from, - const std::string& subject, - const recipients_type& to, - const recipients_type& cc, - const recipients_type& bcc, - O&&... options); - - // Version with the command line callback (see process_run_callback() for - // details). - // - template - sendmail (const C&, - E&& err, - const std::string& from, - const std::string& subject, - const recipients_type& to); - - template - sendmail (const C&, - E&& err, - const std::string& from, - const std::string& subject, - const recipients_type& to, - const recipients_type& cc); - - template - sendmail (const C&, - E&& err, - const std::string& from, - const std::string& subject, - const recipients_type& to, - const recipients_type& cc, - const recipients_type& bcc, - O&&... options); - - private: - void - headers (const std::string& from, - const std::string& subj, - const recipients_type& to, - const recipients_type& cc, - const recipients_type& bcc); - }; -} - -#include - -#endif // LIBBUTL_SENDMAIL_HXX diff --git a/libbutl/sendmail.ixx b/libbutl/sendmail.ixx index 36c0530..942b9a8 100644 --- a/libbutl/sendmail.ixx +++ b/libbutl/sendmail.ixx @@ -2,10 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // size_t -#include // move(), forward() - -namespace butl +LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { template inline sendmail:: diff --git a/libbutl/sendmail.mxx b/libbutl/sendmail.mxx new file mode 100644 index 0000000..778ff05 --- /dev/null +++ b/libbutl/sendmail.mxx @@ -0,0 +1,138 @@ +// file : libbutl/sendmail.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include + +#include // size_t +#include // move(), forward() +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.sendmail; +#ifdef __cpp_lib_modules +import std.core; +#endif +import butl.process; +import butl.fdstream; +import butl.small_vector; +#else +#include +#include +#include +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // Send email using the sendmail(1) program. + // + // Write the body of the email to out. Note that you must explicitly close + // it before calling wait(). Throw process_error and io_error (both derive + // from system_error) in case of errors. + // + // Typical usage: + // + // try + // { + // sendmail sm (2, // Diagnostics to stderr. + // "", // Default From: address. + // "Test subject", + // {"test@example.com"}); + // + // sm.out << "Test body" << endl; + // + // sm.out.close (); + // + // if (!sm.wait ()) + // ... // sendmail returned non-zero status. + // } + // catch (const std::system_error& e) + // { + // cerr << "sendmail error: " << e << endl; + // } + // + class LIBBUTL_SYMEXPORT sendmail: public process + { + public: + ofdstream out; + + // Notes: + // + // - If from is empty then the process user's address is used. + // + // - The to/cc/bcc addressed should already be quoted if required. + // + using recipients_type = small_vector; + + template + sendmail (E&& err, + const std::string& from, + const std::string& subject, + const recipients_type& to); + + template + sendmail (E&& err, + const std::string& from, + const std::string& subject, + const recipients_type& to, + const recipients_type& cc); + + template + sendmail (E&& err, + const std::string& from, + const std::string& subject, + const recipients_type& to, + const recipients_type& cc, + const recipients_type& bcc, + O&&... options); + + // Version with the command line callback (see process_run_callback() for + // details). + // + template + sendmail (const C&, + E&& err, + const std::string& from, + const std::string& subject, + const recipients_type& to); + + template + sendmail (const C&, + E&& err, + const std::string& from, + const std::string& subject, + const recipients_type& to, + const recipients_type& cc); + + template + sendmail (const C&, + E&& err, + const std::string& from, + const std::string& subject, + const recipients_type& to, + const recipients_type& cc, + const recipients_type& bcc, + O&&... options); + + private: + void + headers (const std::string& from, + const std::string& subj, + const recipients_type& to, + const recipients_type& cc, + const recipients_type& bcc); + }; +} + +#include diff --git a/libbutl/sha256.cxx b/libbutl/sha256.cxx index 09f6844..b0b7301 100644 --- a/libbutl/sha256.cxx +++ b/libbutl/sha256.cxx @@ -2,19 +2,21 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif // C interface for sha256c. // #include #include // size_t -#include // isxdigit() -#include // invalid_argument - -#include // ucase(), lcase() - -using SHA256_CTX = butl::sha256::context; +struct SHA256_CTX +{ + uint32_t state[8]; + uint64_t count; + uint8_t buf[64]; +}; extern "C" { @@ -25,23 +27,48 @@ extern "C" #include "sha256c.c" } +#ifndef __cpp_lib_modules +#include +#include +#include + +#include // isxdigit(), toupper(), tolower() +#include // invalid_argument +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.sha256; + +// Only imports additional to interface. +#ifdef __cpp_lib_modules +import std.io; +#endif + +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +#endif +#endif + +#endif + using namespace std; namespace butl { - // sha256 - // sha256:: sha256 () : done_ (false) { - SHA256_Init (&ctx_); + SHA256_Init (reinterpret_cast (buf_)); } void sha256:: append (const void* b, size_t n) { - SHA256_Update (&ctx_, b, n); + SHA256_Update (reinterpret_cast (buf_), b, n); } const sha256::digest_type& sha256:: @@ -49,9 +76,9 @@ namespace butl { if (!done_) { - SHA256_Final (bin_, &ctx_); + SHA256_Final (bin_, reinterpret_cast (buf_)); done_ = true; - str_[0] = '\0'; // Indicate we haven't computed the string yet. + buf_[0] = '\0'; // Indicate we haven't computed the string yet. } return bin_; @@ -67,22 +94,20 @@ namespace butl if (!done_) binary (); - if (str_[0] == '\0') + if (buf_[0] == '\0') { for (size_t i (0); i != 32; ++i) { - str_[i * 2] = hex_map[bin_[i] >> 4]; - str_[i * 2 + 1] = hex_map[bin_[i] & 0x0f]; + buf_[i * 2] = hex_map[bin_[i] >> 4]; + buf_[i * 2 + 1] = hex_map[bin_[i] & 0x0f]; } - str_[64] = '\0'; + buf_[64] = '\0'; } - return str_; + return buf_; } - // Conversion functions - // string sha256_to_fingerprint (const string& s) { @@ -103,7 +128,7 @@ namespace butl if (i > 0 && i % 2 == 0) f += ":"; - f += ucase (c); + f += toupper (c); //@@ MOD revert to ucase() } return f; @@ -133,7 +158,7 @@ namespace butl if (!isxdigit (c)) bad (); - s += lcase (c); + s += tolower (c); //@@ MOD revert to lcase() } } diff --git a/libbutl/sha256.hxx b/libbutl/sha256.hxx deleted file mode 100644 index 459b46c..0000000 --- a/libbutl/sha256.hxx +++ /dev/null @@ -1,135 +0,0 @@ -// file : libbutl/sha256.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_SHA256_HXX -#define LIBBUTL_SHA256_HXX - -#include -#include // strlen(), memcpy() -#include -#include // size_t -#include // enable_if, is_integral - -#include - -namespace butl -{ - // SHA256 checksum calculator. - // - // For a single chunk of data a sum can be obtained in one line, for - // example: - // - // cerr << sha256 ("123").string () << endl; - // - class LIBBUTL_SYMEXPORT sha256 - { - public: - sha256 (); - - // Append binary data. - // - void - append (const void*, std::size_t); - - sha256 (const void* b, std::size_t n): sha256 () {append (b, n);} - - // Append string. - // - // Note that the hash includes the '\0' terminator. Failed that, a call - // with an empty string will be indistinguishable from no call at all. - // - void - append (const std::string& s) {append (s.c_str (), s.size () + 1);} - - void - append (const char* s) {append (s, std::strlen (s) + 1);} - - explicit - sha256 (const std::string& s): sha256 () {append (s);} - - explicit - sha256 (const char* s): sha256 () {append (s);} - - // Append an integral type with a fast path optimization (see - // SHA256_Update() for details). - // - void - append (char c) - { - std::uint32_t r ((ctx_.count >> 3) & 0x3f); - - if (1 < 64 - r) - { - ctx_.buf[r] = static_cast (c); - ctx_.count += 8; - } - else - append (&c, 1); - } - - template - typename std::enable_if::value>::type - append (T x) - { - const std::size_t len (sizeof (x)); - std::uint32_t r ((ctx_.count >> 3) & 0x3f); - - if (len < 64 - r) - { - std::memcpy (&ctx_.buf[r], &x, sizeof (x)); - ctx_.count += len << 3; - } - else - append (&x, len); - } - - // Extract result. - // - // It can be obtained as either a 32-byte binary digest or as a 64- - // character hex-encoded C-string. - // - using digest_type = std::uint8_t[32]; - - const digest_type& - binary () const; - - const char* - string () const; - - public: - struct context - { - std::uint32_t state[8]; - std::uint64_t count; - std::uint8_t buf[64]; - }; - - private: - union - { - mutable context ctx_; - mutable char str_[65]; - }; - - mutable digest_type bin_; - mutable bool done_; - }; - - // Convert a SHA256 string representation (64 hex digits) to the fingerprint - // canonical representation (32 colon-separated upper case hex digit pairs, - // like 01:AB:CD:...). Throw invalid_argument if the argument is not a valid - // SHA256 string. - // - LIBBUTL_SYMEXPORT std::string - sha256_to_fingerprint (const std::string&); - - // Convert a fingerprint (32 colon-separated hex digit pairs) to the SHA256 - // string representation (64 lower case hex digits). Throw invalid_argument - // if the argument is not a valid fingerprint. - // - LIBBUTL_SYMEXPORT std::string - fingerprint_to_sha256 (const std::string&); -} - -#endif // LIBBUTL_SHA256_HXX diff --git a/libbutl/sha256.mxx b/libbutl/sha256.mxx new file mode 100644 index 0000000..c5e5864 --- /dev/null +++ b/libbutl/sha256.mxx @@ -0,0 +1,146 @@ +// file : libbutl/sha256.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include // size_t +#include +#include // strlen(), memcpy() +#include // enable_if, is_integral +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.sha256; +#ifdef __cpp_lib_modules +import std.core; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + // SHA256 checksum calculator. + // + // For a single chunk of data a sum can be obtained in one line, for + // example: + // + // cerr << sha256 ("123").string () << endl; + // + class LIBBUTL_SYMEXPORT sha256 + { + public: + sha256 (); + + // Append binary data. + // + void + append (const void*, std::size_t); + + sha256 (const void* b, std::size_t n): sha256 () {append (b, n);} + + // Append string. + // + // Note that the hash includes the '\0' terminator. Failed that, a call + // with an empty string will be indistinguishable from no call at all. + // + void + append (const std::string& s) {append (s.c_str (), s.size () + 1);} + + void + append (const char* s) {append (s, std::strlen (s) + 1);} + + explicit + sha256 (const std::string& s): sha256 () {append (s);} + + explicit + sha256 (const char* s): sha256 () {append (s);} + + // Append an integral type with a fast path optimization (see + // SHA256_Update() for details). + // + void + append (char c) + { + std::uint32_t r ((ctx_.count >> 3) & 0x3f); + + if (1 < 64 - r) + { + ctx_.buf[r] = static_cast (c); + ctx_.count += 8; + } + else + append (&c, 1); + } + + template + typename std::enable_if::value>::type + append (T x) + { + const std::size_t len (sizeof (x)); + std::uint32_t r ((ctx_.count >> 3) & 0x3f); + + if (len < 64 - r) + { + std::memcpy (&ctx_.buf[r], &x, sizeof (x)); + ctx_.count += len << 3; + } + else + append (&x, len); + } + + // Extract result. + // + // It can be obtained as either a 32-byte binary digest or as a 64- + // character hex-encoded C-string. + // + using digest_type = std::uint8_t[32]; + + const digest_type& + binary () const; + + const char* + string () const; + + private: + struct context // Note: identical to SHA256_CTX. + { + uint32_t state[8]; + uint64_t count; + uint8_t buf[64]; + }; + + union + { + mutable context ctx_; + mutable char buf_[sizeof (context)]; // Also used to store string rep. + }; + + mutable digest_type bin_; + mutable bool done_; + }; + + // Convert a SHA256 string representation (64 hex digits) to the fingerprint + // canonical representation (32 colon-separated upper case hex digit pairs, + // like 01:AB:CD:...). Throw invalid_argument if the argument is not a valid + // SHA256 string. + // + LIBBUTL_SYMEXPORT std::string + sha256_to_fingerprint (const std::string&); + + // Convert a fingerprint (32 colon-separated hex digit pairs) to the SHA256 + // string representation (64 lower case hex digits). Throw invalid_argument + // if the argument is not a valid fingerprint. + // + LIBBUTL_SYMEXPORT std::string + fingerprint_to_sha256 (const std::string&); +} diff --git a/libbutl/small-vector.hxx b/libbutl/small-vector.hxx deleted file mode 100644 index 84c25e8..0000000 --- a/libbutl/small-vector.hxx +++ /dev/null @@ -1,297 +0,0 @@ -// file : libbutl/small-vector.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_SMALL_VECTOR_HXX -#define LIBBUTL_SMALL_VECTOR_HXX - -#include -#include -#include // size_t -#include // more(), forward() -#include // true_type - -namespace butl -{ - template - struct small_vector_buffer - { - // Size keeps track of the number of elements that are constructed in - // the buffer. Size equal N + 1 means the buffer is not allocated. - // - // Note that the names are decorated in order no to conflict with - // std::vector interface. - // - alignas (alignof (T)) char data_[sizeof (T) * N]; - bool free_ = true; - - // Note that the buffer should be constructed before std::vector and - // destroyed after (since std::vector's destructor will be destroying - // elements potentially residing in the buffer). This means that the - // buffer should be inherited from and before std::vector. - // - small_vector_buffer () = default; - - small_vector_buffer (small_vector_buffer&&) = delete; - small_vector_buffer (const small_vector_buffer&) = delete; - - small_vector_buffer& operator= (small_vector_buffer&&) = delete; - small_vector_buffer& operator= (const small_vector_buffer&) = delete; - }; - - template - class small_vector_allocator - { - public: - using buffer_type = small_vector_buffer; - - explicit - small_vector_allocator (buffer_type* b) noexcept: buf_ (b) {} - - // Required by VC15u3 when _ITERATOR_DEBUG_LEVEL is not 0. It allocates - // some extra stuff which cannot possibly come from the static buffer. - // -#if defined(_MSC_VER) && _MSC_VER >= 1911 - template - explicit - small_vector_allocator (const small_vector_allocator&) noexcept - : buf_ (nullptr) {} -#endif - - // Allocator interface. - // - public: - using value_type = T; - - T* - allocate(std::size_t n) - { - if (buf_ != nullptr) - { - assert (n >= N); // We should never be asked for less than N. - - if (n <= N) - { - buf_->free_ = false; - return reinterpret_cast (buf_->data_); - } - // Fall through. - } - - return static_cast (::operator new (sizeof (T) * n)); - } - - void - deallocate (void* p, std::size_t) noexcept - { - if (buf_ != nullptr && p == buf_->data_) - buf_->free_ = true; - else - ::operator delete (p); - } - - friend bool - operator== (small_vector_allocator x, small_vector_allocator y) noexcept - { - // We can use y to deallocate x's allocations if they use the same small - // buffer or neither uses its small buffer (which means all allocations, - // if any, have been from the shared heap). Of course this assumes no - // copy will be called to deallocate what has been allocated after said - // copy was made: - // - // A x; - // A y (x); - // p = x.allocate (); - // y.deallocate (p); // Ouch. - // - return (x.buf_ == y.buf_) || (x.buf_->free_ && y.buf_->free_); - } - - friend bool - operator!= (small_vector_allocator x, small_vector_allocator y) noexcept - { - return !(x == y); - } - - // It might get instantiated but should not be called. - // - small_vector_allocator - select_on_container_copy_construction () const noexcept - { - return small_vector_allocator (nullptr); - } - - // propagate_on_container_copy_assignment = false - // propagate_on_container_move_assignment = false - - // Swap is not supported (see explanation in small_vector::swap()). - // - using propagate_on_container_swap = std::true_type; - - void - swap (small_vector_allocator&) = delete; - - // Shouldn't be needed except to satisfy some static_assert's. - // - template - struct rebind {using other = small_vector_allocator;}; - - private: - buffer_type* buf_; - }; - - // Issues and limitations. - // - // - vector::reserve() may allocate more per the spec. But the three main - // C++ runtimes (libstdc++, libc++, and msvc) all seem to do the right - // thing. - // - // - What if in most cases the vector is empty? How can we avoid initial - // reserve? Provide no_reserve flag or some such? Is it really worth it? - // - // - swap() is deleted (see notes below). - // - template - class small_vector: private small_vector_buffer, - public std::vector> - { - public: - using allocator_type = small_vector_allocator; - using buffer_type = small_vector_buffer; - using base_type = std::vector>; - - small_vector () - : base_type (allocator_type (this)) - { - reserve (); - } - - small_vector (std::initializer_list v) - : base_type (allocator_type (this)) - { - if (v.size () <= N) - reserve (); - - static_cast (*this) = v; - } - - template - small_vector (I b, I e) - : base_type (allocator_type (this)) - { - // While we could optimize this for random access iterators, N will - // usually be pretty small. Let's hope the compiler sees this and does - // some magic for us. - // - std::size_t n (0); - for (I i (b); i != e && n <= N; ++i) ++n; - - if (n <= N) - reserve (); - - this->assign (b, e); - } - - explicit - small_vector (std::size_t n) - : base_type (allocator_type (this)) - { - if (n <= N) - reserve (); - - this->resize (n); - } - - small_vector (std::size_t n, const T& x) - : base_type (allocator_type (this)) - { - if (n <= N) - reserve (); - - this->assign (n, x); - } - - small_vector (const small_vector& v) - : buffer_type (), base_type (allocator_type (this)) - { - if (v.size () <= N) - reserve (); - - static_cast (*this) = v; - } - - small_vector& - operator= (const small_vector& v) - { - // Note: propagate_on_container_copy_assignment = false - // - static_cast (*this) = v; - return *this; - } - - small_vector (small_vector&& v) - : base_type (allocator_type (this)) - { - if (v.size () <= N) - reserve (); - - *this = std::move (v); // Delegate to operator=(&&). - } - - small_vector& - operator= (small_vector&& v) - { - // VC's implementation of operator=(&&) (both 14 and 15) frees the - // memory and then reallocated with capacity equal to v.size(). This is - // clearly sub-optimal (the existing buffer could be reused) so we hope - // this will be fixed eventually (VSO#367146; reportedly fixed for - // VC15U1). - // -#if defined(_MSC_VER) && _MSC_VER <= 1910 - if (v.size () < N) - { - clear (); - for (T& x: v) - push_back (std::move (x)); - v.clear (); - } - else -#endif - - // Note: propagate_on_container_move_assignment = false - // - static_cast (*this) = std::move (v); - - return *this; - } - - small_vector& - operator= (std::initializer_list v) - { - static_cast (*this) = v; - return *this; - } - - // Implementing swap() under small buffer optimization is not trivial, to - // say the least (think of swapping two such buffers of different sizes). - // One easy option would be to force both in to the heap. - // - void - swap (small_vector&) = delete; - - void - reserve (std::size_t n = N) - { - base_type::reserve (n < N ? N : n); - } - - void - shrink_to_fit () - { - if (this->capacity () > N) - base_type::shrink_to_fit (); - } - }; -} - -#endif // LIBBUTL_SMALL_VECTOR_HXX diff --git a/libbutl/small-vector.mxx b/libbutl/small-vector.mxx new file mode 100644 index 0000000..ca44ed7 --- /dev/null +++ b/libbutl/small-vector.mxx @@ -0,0 +1,310 @@ +// file : libbutl/small-vector.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +#include + +#ifndef __cpp_lib_modules +#include +#include // size_t +#include // move(), forward() +#include // true_type +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.small_vector; +#ifdef __cpp_lib_modules +import std.core; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + template + struct small_vector_buffer + { + // Size keeps track of the number of elements that are constructed in + // the buffer. Size equal N + 1 means the buffer is not allocated. + // + // Note that the names are decorated in order no to conflict with + // std::vector interface. + // + alignas (alignof (T)) char data_[sizeof (T) * N]; + bool free_ = true; + + // Note that the buffer should be constructed before std::vector and + // destroyed after (since std::vector's destructor will be destroying + // elements potentially residing in the buffer). This means that the + // buffer should be inherited from and before std::vector. + // + small_vector_buffer () = default; + + small_vector_buffer (small_vector_buffer&&) = delete; + small_vector_buffer (const small_vector_buffer&) = delete; + + small_vector_buffer& operator= (small_vector_buffer&&) = delete; + small_vector_buffer& operator= (const small_vector_buffer&) = delete; + }; + + template + class small_vector_allocator + { + public: + using buffer_type = small_vector_buffer; + + explicit + small_vector_allocator (buffer_type* b) noexcept: buf_ (b) {} + + // Required by VC15u3 when _ITERATOR_DEBUG_LEVEL is not 0. It allocates + // some extra stuff which cannot possibly come from the static buffer. + // +#if defined(_MSC_VER) && _MSC_VER >= 1911 + template + explicit + small_vector_allocator (const small_vector_allocator&) noexcept + : buf_ (nullptr) {} +#endif + + // Allocator interface. + // + public: + using value_type = T; + + T* + allocate(std::size_t n) + { + if (buf_ != nullptr) + { + assert (n >= N); // We should never be asked for less than N. + + if (n <= N) + { + buf_->free_ = false; + return reinterpret_cast (buf_->data_); + } + // Fall through. + } + + return static_cast (::operator new (sizeof (T) * n)); + } + + void + deallocate (void* p, std::size_t) noexcept + { + if (buf_ != nullptr && p == buf_->data_) + buf_->free_ = true; + else + ::operator delete (p); + } + + friend bool + operator== (small_vector_allocator x, small_vector_allocator y) noexcept + { + // We can use y to deallocate x's allocations if they use the same small + // buffer or neither uses its small buffer (which means all allocations, + // if any, have been from the shared heap). Of course this assumes no + // copy will be called to deallocate what has been allocated after said + // copy was made: + // + // A x; + // A y (x); + // p = x.allocate (); + // y.deallocate (p); // Ouch. + // + return (x.buf_ == y.buf_) || (x.buf_->free_ && y.buf_->free_); + } + + friend bool + operator!= (small_vector_allocator x, small_vector_allocator y) noexcept + { + return !(x == y); + } + + // It might get instantiated but should not be called. + // + small_vector_allocator + select_on_container_copy_construction () const noexcept + { + return small_vector_allocator (nullptr); + } + + // propagate_on_container_copy_assignment = false + // propagate_on_container_move_assignment = false + + // Swap is not supported (see explanation in small_vector::swap()). + // + using propagate_on_container_swap = std::true_type; + + void + swap (small_vector_allocator&) = delete; + + // Shouldn't be needed except to satisfy some static_assert's. + // + template + struct rebind {using other = small_vector_allocator;}; + + private: + buffer_type* buf_; + }; + + // Issues and limitations. + // + // - vector::reserve() may allocate more per the spec. But the three main + // C++ runtimes (libstdc++, libc++, and msvc) all seem to do the right + // thing. + // + // - What if in most cases the vector is empty? How can we avoid initial + // reserve? Provide no_reserve flag or some such? Is it really worth it? + // + // - swap() is deleted (see notes below). + // + template + class small_vector: private small_vector_buffer, + public std::vector> + { + public: + using allocator_type = small_vector_allocator; + using buffer_type = small_vector_buffer; + using base_type = std::vector>; + + small_vector () + : base_type (allocator_type (this)) + { + reserve (); + } + + small_vector (std::initializer_list v) + : base_type (allocator_type (this)) + { + if (v.size () <= N) + reserve (); + + static_cast (*this) = v; + } + + template + small_vector (I b, I e) + : base_type (allocator_type (this)) + { + // While we could optimize this for random access iterators, N will + // usually be pretty small. Let's hope the compiler sees this and does + // some magic for us. + // + std::size_t n (0); + for (I i (b); i != e && n <= N; ++i) ++n; + + if (n <= N) + reserve (); + + this->assign (b, e); + } + + explicit + small_vector (std::size_t n) + : base_type (allocator_type (this)) + { + if (n <= N) + reserve (); + + this->resize (n); + } + + small_vector (std::size_t n, const T& x) + : base_type (allocator_type (this)) + { + if (n <= N) + reserve (); + + this->assign (n, x); + } + + small_vector (const small_vector& v) + : buffer_type (), base_type (allocator_type (this)) + { + if (v.size () <= N) + reserve (); + + static_cast (*this) = v; + } + + small_vector& + operator= (const small_vector& v) + { + // Note: propagate_on_container_copy_assignment = false + // + static_cast (*this) = v; + return *this; + } + + small_vector (small_vector&& v) + : base_type (allocator_type (this)) + { + if (v.size () <= N) + reserve (); + + *this = std::move (v); // Delegate to operator=(&&). + } + + small_vector& + operator= (small_vector&& v) + { + // VC's implementation of operator=(&&) (both 14 and 15) frees the + // memory and then reallocated with capacity equal to v.size(). This is + // clearly sub-optimal (the existing buffer could be reused) so we hope + // this will be fixed eventually (VSO#367146; reportedly fixed for + // VC15U1). + // +#if defined(_MSC_VER) && _MSC_VER <= 1910 + if (v.size () < N) + { + clear (); + for (T& x: v) + push_back (std::move (x)); + v.clear (); + } + else +#endif + + // Note: propagate_on_container_move_assignment = false + // + static_cast (*this) = std::move (v); + + return *this; + } + + small_vector& + operator= (std::initializer_list v) + { + static_cast (*this) = v; + return *this; + } + + // Implementing swap() under small buffer optimization is not trivial, to + // say the least (think of swapping two such buffers of different sizes). + // One easy option would be to force both in to the heap. + // + void + swap (small_vector&) = delete; + + void + reserve (std::size_t n = N) + { + base_type::reserve (n < N ? N : n); + } + + void + shrink_to_fit () + { + if (this->capacity () > N) + base_type::shrink_to_fit (); + } + }; +} diff --git a/libbutl/standard-version.cxx b/libbutl/standard-version.cxx index 13d3987..d8582dd 100644 --- a/libbutl/standard-version.cxx +++ b/libbutl/standard-version.cxx @@ -2,15 +2,41 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif #include + +#ifndef __cpp_lib_modules +#include +#include +#include +#include + #include // strtoull() -#include // size_t #include // move() #include // invalid_argument - -#include // alnum() +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.standard_version; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.optional; +#endif + +import butl.utility; +#else +#include // alnum() +#endif using namespace std; diff --git a/libbutl/standard-version.hxx b/libbutl/standard-version.hxx deleted file mode 100644 index ebb3994..0000000 --- a/libbutl/standard-version.hxx +++ /dev/null @@ -1,267 +0,0 @@ -// file : libbutl/standard-version.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_STANDARD_VERSION_HXX -#define LIBBUTL_STANDARD_VERSION_HXX - -#include -#include // uint*_t -#include // size_t -#include - -#include - -#include - -// FreeBSD defines these macros in its . -// -#ifdef major -# undef major -#endif - -#ifdef minor -# undef minor -#endif - -namespace butl -{ - // The build2 "standard version" (specific, earliest and stub): - // - // [~]..[-(a|b).[.[.]]][+] - // [~]..- - // 0[+] - // - struct LIBBUTL_SYMEXPORT standard_version - { - // Invariants: - // - // 1. allow_earliest - // ? (E == 1) || (snapshot_sn == 0) - // : (E == 0) == (snapshot_sn == 0) - // - // 2. version != 0 || allow_stub && epoch == 0 && snapshot_sn == 0 - // - // 3. snapshot_sn != latest_sn && snapshot_sn != 0 || snapshot_id.empty () - // - static const std::uint64_t latest_sn = std::uint64_t (~0); - - std::uint16_t epoch = 0; // 0 if not specified. - std::uint64_t version = 0; // AAABBBCCCDDDE - std::uint64_t snapshot_sn = 0; // 0 if not specifed, latest_sn if 'z'. - std::string snapshot_id; // Empty if not specified. - std::uint16_t revision = 0; // 0 if not specified. - - std::uint16_t major () const noexcept; - std::uint16_t minor () const noexcept; - std::uint16_t patch () const noexcept; - - // Note: 0 is ambiguous (-a.0.z). - // - std::uint16_t pre_release () const noexcept; - - // Note: return empty if the corresponding component is unspecified. - // - std::string string () const; // Package version. - std::string string_project () const; // Project version (no epoch/rev). - std::string string_project_id () const; // Project version id (no snapsn). - std::string string_version () const; // Version only (no snapshot). - std::string string_pre_release () const; // Pre-release part only (a.1). - std::string string_snapshot () const; // Snapshot part only (1234.1f23). - - bool empty () const noexcept {return version == 0;} - - bool alpha () const noexcept; - bool beta () const noexcept; - bool snapshot () const noexcept {return snapshot_sn != 0;} - - // Represented by DDDE in version being 0001 and snapshot_sn being 0. - // - // Note that the earliest version is a final alpha pre-release. - // - bool - earliest () const noexcept; - - bool - stub () const noexcept {return version == std::uint64_t (~0);} - - // Comparison of empty or stub versions doesn't make sense. - // - int - compare (const standard_version& v) const noexcept - { - if (epoch != v.epoch) - return epoch < v.epoch ? -1 : 1; - - if (version != v.version) - return version < v.version ? -1 : 1; - - if (snapshot_sn != v.snapshot_sn) - return snapshot_sn < v.snapshot_sn ? -1 : 1; - - if (revision != v.revision) - return revision < v.revision ? -1 : 1; - - return 0; - } - - // Parse the version. Throw std::invalid_argument if the format is not - // recognizable or components are invalid. - // - enum flags - { - none = 0, - allow_earliest = 0x01, // Allow ..- form. - allow_stub = 0x02 // Allow 0[+] form. - }; - - explicit - standard_version (const std::string&, flags = none); - - explicit - standard_version (std::uint64_t version, flags = none); - - standard_version (std::uint64_t version, - const std::string& snapshot, - flags = none); - - standard_version (std::uint16_t epoch, - std::uint64_t version, - const std::string& snapshot, - std::uint16_t revision, - flags = none); - - standard_version (std::uint16_t epoch, - std::uint64_t version, - std::uint64_t snapshot_sn, - std::string snapshot_id, - std::uint16_t revision, - flags = none); - - // Create empty version. - // - standard_version () = default; - - private: - void - parse_snapshot (const std::string&, std::size_t&); - }; - - inline bool - operator< (const standard_version& x, const standard_version& y) noexcept - { - return x.compare (y) < 0; - } - - inline bool - operator> (const standard_version& x, const standard_version& y) noexcept - { - return x.compare (y) > 0; - } - - inline bool - operator== (const standard_version& x, const standard_version& y) noexcept - { - return x.compare (y) == 0; - } - - inline bool - operator<= (const standard_version& x, const standard_version& y) noexcept - { - return x.compare (y) <= 0; - } - - inline bool - operator>= (const standard_version& x, const standard_version& y) noexcept - { - return x.compare (y) >= 0; - } - - inline bool - operator!= (const standard_version& x, const standard_version& y) noexcept - { - return !(x == y); - } - - inline std::ostream& - operator<< (std::ostream& o, const standard_version& x) - { - return o << x.string (); - } - - inline standard_version::flags - operator& (standard_version::flags, standard_version::flags); - - inline standard_version::flags - operator| (standard_version::flags, standard_version::flags); - - inline standard_version::flags - operator&= (standard_version::flags&, standard_version::flags); - - inline standard_version::flags - operator|= (standard_version::flags&, standard_version::flags); - - // The build2 "standard version" constraint: - // - // ('==' | '>' | '<' | '>=' | '<=') - // ('(' | '[') (')' | ']') - // - struct LIBBUTL_SYMEXPORT standard_version_constraint - { - butl::optional min_version; - butl::optional max_version; - bool min_open; - bool max_open; - - // Parse the version constraint. Throw std::invalid_argument on error. - // - explicit - standard_version_constraint (const std::string&); - - // Throw std::invalid_argument if the specified version range is invalid. - // - standard_version_constraint ( - butl::optional min_version, bool min_open, - butl::optional max_version, bool max_open); - - explicit - standard_version_constraint (const standard_version& v) - : standard_version_constraint (v, false, v, false) {} - - standard_version_constraint () = default; - - std::string - string () const; - - bool - empty () const noexcept {return !min_version && !max_version;} - - bool - satisfies (const standard_version&) const noexcept; - }; - - inline bool - operator== (const standard_version_constraint& x, - const standard_version_constraint& y) - { - return x.min_version == y.min_version && x.max_version == y.max_version && - x.min_open == y.min_open && x.max_open == y.max_open; - } - - inline bool - operator!= (const standard_version_constraint& x, - const standard_version_constraint& y) - { - return !(x == y); - } - - inline std::ostream& - operator<< (std::ostream& o, const standard_version_constraint& x) - { - return o << x.string (); - } -} - -#include - -#endif // LIBBUTL_STANDARD_VERSION_HXX diff --git a/libbutl/standard-version.mxx b/libbutl/standard-version.mxx new file mode 100644 index 0000000..5082d16 --- /dev/null +++ b/libbutl/standard-version.mxx @@ -0,0 +1,281 @@ +// file : libbutl/standard-version.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include // uint*_t +#include // size_t +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.standard_version; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.optional; +#else +#include +#endif + +#include + +// FreeBSD defines these macros in its . +// +#ifdef major +# undef major +#endif + +#ifdef minor +# undef minor +#endif + +LIBBUTL_MODEXPORT namespace butl +{ + // The build2 "standard version" (specific, earliest and stub): + // + // [~]..[-(a|b).[.[.]]][+] + // [~]..- + // 0[+] + // + struct LIBBUTL_SYMEXPORT standard_version + { + // Invariants: + // + // 1. allow_earliest + // ? (E == 1) || (snapshot_sn == 0) + // : (E == 0) == (snapshot_sn == 0) + // + // 2. version != 0 || allow_stub && epoch == 0 && snapshot_sn == 0 + // + // 3. snapshot_sn != latest_sn && snapshot_sn != 0 || snapshot_id.empty () + // + static const std::uint64_t latest_sn = std::uint64_t (~0); + + std::uint16_t epoch = 0; // 0 if not specified. + std::uint64_t version = 0; // AAABBBCCCDDDE + std::uint64_t snapshot_sn = 0; // 0 if not specifed, latest_sn if 'z'. + std::string snapshot_id; // Empty if not specified. + std::uint16_t revision = 0; // 0 if not specified. + + std::uint16_t major () const noexcept; + std::uint16_t minor () const noexcept; + std::uint16_t patch () const noexcept; + + // Note: 0 is ambiguous (-a.0.z). + // + std::uint16_t pre_release () const noexcept; + + // Note: return empty if the corresponding component is unspecified. + // + std::string string () const; // Package version. + std::string string_project () const; // Project version (no epoch/rev). + std::string string_project_id () const; // Project version id (no snapsn). + std::string string_version () const; // Version only (no snapshot). + std::string string_pre_release () const; // Pre-release part only (a.1). + std::string string_snapshot () const; // Snapshot part only (1234.1f23). + + bool empty () const noexcept {return version == 0;} + + bool alpha () const noexcept; + bool beta () const noexcept; + bool snapshot () const noexcept {return snapshot_sn != 0;} + + // Represented by DDDE in version being 0001 and snapshot_sn being 0. + // + // Note that the earliest version is a final alpha pre-release. + // + bool + earliest () const noexcept; + + bool + stub () const noexcept {return version == std::uint64_t (~0);} + + // Comparison of empty or stub versions doesn't make sense. + // + int + compare (const standard_version& v) const noexcept + { + if (epoch != v.epoch) + return epoch < v.epoch ? -1 : 1; + + if (version != v.version) + return version < v.version ? -1 : 1; + + if (snapshot_sn != v.snapshot_sn) + return snapshot_sn < v.snapshot_sn ? -1 : 1; + + if (revision != v.revision) + return revision < v.revision ? -1 : 1; + + return 0; + } + + // Parse the version. Throw std::invalid_argument if the format is not + // recognizable or components are invalid. + // + enum flags + { + none = 0, + allow_earliest = 0x01, // Allow ..- form. + allow_stub = 0x02 // Allow 0[+] form. + }; + + explicit + standard_version (const std::string&, flags = none); + + explicit + standard_version (std::uint64_t version, flags = none); + + standard_version (std::uint64_t version, + const std::string& snapshot, + flags = none); + + standard_version (std::uint16_t epoch, + std::uint64_t version, + const std::string& snapshot, + std::uint16_t revision, + flags = none); + + standard_version (std::uint16_t epoch, + std::uint64_t version, + std::uint64_t snapshot_sn, + std::string snapshot_id, + std::uint16_t revision, + flags = none); + + // Create empty version. + // + standard_version () = default; + + private: + void + parse_snapshot (const std::string&, std::size_t&); + }; + + inline bool + operator< (const standard_version& x, const standard_version& y) noexcept + { + return x.compare (y) < 0; + } + + inline bool + operator> (const standard_version& x, const standard_version& y) noexcept + { + return x.compare (y) > 0; + } + + inline bool + operator== (const standard_version& x, const standard_version& y) noexcept + { + return x.compare (y) == 0; + } + + inline bool + operator<= (const standard_version& x, const standard_version& y) noexcept + { + return x.compare (y) <= 0; + } + + inline bool + operator>= (const standard_version& x, const standard_version& y) noexcept + { + return x.compare (y) >= 0; + } + + inline bool + operator!= (const standard_version& x, const standard_version& y) noexcept + { + return !(x == y); + } + + inline std::ostream& + operator<< (std::ostream& o, const standard_version& x) + { + return o << x.string (); + } + + inline standard_version::flags + operator& (standard_version::flags, standard_version::flags); + + inline standard_version::flags + operator| (standard_version::flags, standard_version::flags); + + inline standard_version::flags + operator&= (standard_version::flags&, standard_version::flags); + + inline standard_version::flags + operator|= (standard_version::flags&, standard_version::flags); + + // The build2 "standard version" constraint: + // + // ('==' | '>' | '<' | '>=' | '<=') + // ('(' | '[') (')' | ']') + // + struct LIBBUTL_SYMEXPORT standard_version_constraint + { + butl::optional min_version; + butl::optional max_version; + bool min_open; + bool max_open; + + // Parse the version constraint. Throw std::invalid_argument on error. + // + explicit + standard_version_constraint (const std::string&); + + // Throw std::invalid_argument if the specified version range is invalid. + // + standard_version_constraint ( + butl::optional min_version, bool min_open, + butl::optional max_version, bool max_open); + + explicit + standard_version_constraint (const standard_version& v) + : standard_version_constraint (v, false, v, false) {} + + standard_version_constraint () = default; + + std::string + string () const; + + bool + empty () const noexcept {return !min_version && !max_version;} + + bool + satisfies (const standard_version&) const noexcept; + }; + + inline bool + operator== (const standard_version_constraint& x, + const standard_version_constraint& y) + { + return x.min_version == y.min_version && x.max_version == y.max_version && + x.min_open == y.min_open && x.max_open == y.max_open; + } + + inline bool + operator!= (const standard_version_constraint& x, + const standard_version_constraint& y) + { + return !(x == y); + } + + inline std::ostream& + operator<< (std::ostream& o, const standard_version_constraint& x) + { + return o << x.string (); + } +} + +#include diff --git a/libbutl/string-parser.cxx b/libbutl/string-parser.cxx index c579db0..53c1d1a 100644 --- a/libbutl/string-parser.cxx +++ b/libbutl/string-parser.cxx @@ -2,131 +2,156 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif -#include // move() +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#include +#include // move() +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.string_parser; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +#endif +#endif + +#endif using namespace std; namespace butl { - // Utility functions - // - inline static bool - space (char c) noexcept - { - return c == ' ' || c == '\t'; - } - - // string_parser - // - vector> string_parser:: - parse_quoted_position (const string& s, bool unquote) + namespace string_parser { - vector> r; - for (auto b (s.begin ()), i (b), e (s.end ()); i != e; ) + // Utility functions. + // + inline static bool + space (char c) noexcept { - for (; i != e && space (*i); ++i) ; // Skip spaces. + return c == ' ' || c == '\t'; + } - if (i == e) // No more strings. - break; + vector> + parse_quoted_position (const string& s, bool unquote) + { + vector> r; + for (auto b (s.begin ()), i (b), e (s.end ()); i != e; ) + { + for (; i != e && space (*i); ++i) ; // Skip spaces. - string s; - char quoting ('\0'); // Current quoting mode, can be used as bool. - size_t pos (i - b); // String position. + if (i == e) // No more strings. + break; - for (; i != e; ++i) - { - char c (*i); + string s; + char quoting ('\0'); // Current quoting mode, can be used as bool. + size_t pos (i - b); // String position. - if (!quoting) + for (; i != e; ++i) { - if (space (c)) // End of string. - break; + char c (*i); - if (c == '"' || c == '\'') // Begin of quoted substring. + if (!quoting) { - quoting = c; + if (space (c)) // End of string. + break; + + if (c == '"' || c == '\'') // Begin of quoted substring. + { + quoting = c; + + if (!unquote) + s += c; + + continue; + } + } + else if (c == quoting) // End of quoted substring. + { + quoting = '\0'; if (!unquote) s += c; continue; } - } - else if (c == quoting) // End of quoted substring. - { - quoting = '\0'; - - if (!unquote) - s += c; - continue; + s += c; } - s += c; - } + if (quoting) + throw invalid_string (i - b, "unterminated quoted string"); - if (quoting) - throw invalid_string (i - b, "unterminated quoted string"); + r.emplace_back (move (s), pos); + } - r.emplace_back (move (s), pos); + return r; } - return r; - } - - vector string_parser:: - parse_quoted (const string& s, bool unquote) - { - vector> sp (parse_quoted_position (s, unquote)); - - vector r; - r.reserve (sp.size ()); - for (auto& s: sp) - r.emplace_back (move (s.first)); + vector + parse_quoted (const string& s, bool unquote) + { + vector> sp (parse_quoted_position (s, unquote)); - return r; - } + vector r; + r.reserve (sp.size ()); + for (auto& s: sp) + r.emplace_back (move (s.first)); - string string_parser:: - unquote (const string& s) - { - string r; - char quoting ('\0'); // Current quoting mode, can be used as bool. + return r; + } - for (auto i (s.begin ()), e (s.end ()); i != e; ++i) + string + unquote (const string& s) { - char c (*i); + string r; + char quoting ('\0'); // Current quoting mode, can be used as bool. - if (!quoting) + for (auto i (s.begin ()), e (s.end ()); i != e; ++i) { - if (c == '"' || c == '\'') // Begin of quoted substring. + char c (*i); + + if (!quoting) { - quoting = c; + if (c == '"' || c == '\'') // Begin of quoted substring. + { + quoting = c; + continue; + } + } + else if (c == quoting) // End of quoted substring. + { + quoting = '\0'; continue; } - } - else if (c == quoting) // End of quoted substring. - { - quoting = '\0'; - continue; + + r += c; } - r += c; + return r; } - return r; - } - - vector string_parser:: - unquote (const vector& v) - { - vector r; - r.reserve (v.size ()); - for (auto& s: v) - r.emplace_back (unquote (s)); + vector + unquote (const vector& v) + { + vector r; + r.reserve (v.size ()); + for (auto& s: v) + r.emplace_back (unquote (s)); - return r; + return r; + } } } diff --git a/libbutl/string-parser.hxx b/libbutl/string-parser.hxx deleted file mode 100644 index b445f34..0000000 --- a/libbutl/string-parser.hxx +++ /dev/null @@ -1,56 +0,0 @@ -// file : libbutl/string-parser.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_STRING_PARSER_HXX -#define LIBBUTL_STRING_PARSER_HXX - -#include -#include -#include // size_t -#include // pair -#include // invalid_argument - -#include - -namespace butl -{ - class LIBBUTL_SYMEXPORT invalid_string: public std::invalid_argument - { - public: - invalid_string (std::size_t p, const std::string& d) - : invalid_argument (d), position (p) {} - - std::size_t position; // Zero-based. - }; - - class LIBBUTL_SYMEXPORT string_parser - { - public: - // Parse a whitespace-separated list of strings. Can contain single or - // double quoted substrings. No escaping is supported. If unquote is true, - // return one-level unquoted values. Throw invalid_string in case of - // invalid quoting. - // - static std::vector - parse_quoted (const std::string&, bool unquote); - - // As above but return a list of string and zero-based position pairs. - // Position is useful for issuing diagnostics about an invalid string - // during second-level parsing. - // - static std::vector> - parse_quoted_position (const std::string&, bool unquote); - - // Remove a single level of quotes. Note that the format or the - // correctness of the quotation is not validated. - // - static std::string - unquote (const std::string&); - - static std::vector - unquote (const std::vector&); - }; -} - -#endif // LIBBUTL_STRING_PARSER_HXX diff --git a/libbutl/string-parser.mxx b/libbutl/string-parser.mxx new file mode 100644 index 0000000..559b938 --- /dev/null +++ b/libbutl/string-parser.mxx @@ -0,0 +1,67 @@ +// file : libbutl/string-parser.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#include // size_t +#include // pair +#include // invalid_argument +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.string_parser; +#ifdef __cpp_lib_modules +import std.core; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + namespace string_parser + { + class LIBBUTL_SYMEXPORT invalid_string: public std::invalid_argument + { + public: + invalid_string (std::size_t p, const std::string& d) + : invalid_argument (d), position (p) {} + + std::size_t position; // Zero-based. + }; + + // Parse a whitespace-separated list of strings. Can contain single or + // double quoted substrings. No escaping is supported. If unquote is true, + // return one-level unquoted values. Throw invalid_string in case of + // invalid quoting. + // + LIBBUTL_SYMEXPORT std::vector + parse_quoted (const std::string&, bool unquote); + + // As above but return a list of string and zero-based position pairs. + // Position is useful for issuing diagnostics about an invalid string + // during second-level parsing. + // + LIBBUTL_SYMEXPORT std::vector> + parse_quoted_position (const std::string&, bool unquote); + + // Remove a single level of quotes. Note that the format or the + // correctness of the quotation is not validated. + // + LIBBUTL_SYMEXPORT std::string + unquote (const std::string&); + + LIBBUTL_SYMEXPORT std::vector + unquote (const std::vector&); + } +} diff --git a/libbutl/string-table.hxx b/libbutl/string-table.hxx deleted file mode 100644 index 6898a52..0000000 --- a/libbutl/string-table.hxx +++ /dev/null @@ -1,98 +0,0 @@ -// file : libbutl/string-table.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_STRING_TABLE_HXX -#define LIBBUTL_STRING_TABLE_HXX - -#include -#include -#include - -#include - -#include - -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, uint16_t, etc., depending on how many entries are expected). - // Index value 0 is reserved to indicate the "no entry" condition. - // - template - struct string_table_element - { - const I i; - const D d; - }; - - template - struct string_table_element - { - const I i; - const std::string d; - }; - - // For custom data the options are to call the data member 'key' or to - // specialize this traits. - // - template - struct string_table_traits - { - static const std::string& - key (const D& d) {return d.key;} - }; - - template <> - struct string_table_traits - { - static const std::string& - key (const std::string& d) {return d;} - }; - - template - struct string_table - { - // Insert new entry unless one already exists. - // - I - insert (const D&); - - // Find existing. - // - I - find (const std::string& k) const - { - auto i (map_.find (key_type (&k))); - return i != map_.end () ? i->second.i : 0; - } - - // Reverse lookup. - // - const D& - operator[] (I i) const {assert (i > 0); return vec_[i - 1]->second.d;} - - I - size () const {return static_cast (vec_.size ());} - - bool - empty () const {return vec_.empty ();} - - void - clear () {vec_.clear (); map_.clear ();} - - private: - using key_type = butl::map_key; - using value_type = string_table_element; - using map_type = std::unordered_map; - using traits = string_table_traits; - - map_type map_; - std::vector vec_; - }; -} - -#include - -#endif // LIBBUTL_STRING_TABLE_HXX diff --git a/libbutl/string-table.mxx b/libbutl/string-table.mxx new file mode 100644 index 0000000..2db8d6a --- /dev/null +++ b/libbutl/string-table.mxx @@ -0,0 +1,114 @@ +// file : libbutl/string-table.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +#include + +#ifndef __cpp_lib_modules +#include +#include +#include + +#include // numeric_limits +#include // size_t +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.string_table; +#ifdef __cpp_lib_modules +import std.core; +#endif +import butl.multi_index; +#else +#include +#endif + +#include + +LIBBUTL_MODEXPORT 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, + // uint16_t, etc., depending on how many entries are expected). Index value + // 0 is reserved to indicate the "no entry" condition. + // + template + struct string_table_element + { + const I i; + const D d; + }; + + template + struct string_table_element + { + const I i; + const std::string d; + }; + + // For custom data the options are to call the data member 'key' or to + // specialize this traits. + // + template + struct string_table_traits + { + static const std::string& + key (const D& d) {return d.key;} + }; + + template <> + struct string_table_traits + { + static const std::string& + key (const std::string& d) {return d;} + }; + + template + struct string_table + { + // Insert new entry unless one already exists. + // + I + insert (const D&); + + // Find existing. + // + I + find (const std::string& k) const + { + auto i (map_.find (key_type (&k))); + return i != map_.end () ? i->second.i : 0; + } + + // Reverse lookup. + // + const D& + operator[] (I i) const {assert (i > 0); return vec_[i - 1]->second.d;} + + I + size () const {return static_cast (vec_.size ());} + + bool + empty () const {return vec_.empty ();} + + void + clear () {vec_.clear (); map_.clear ();} + + private: + using key_type = butl::map_key; + using value_type = string_table_element; + using map_type = std::unordered_map; + using traits = string_table_traits; + + map_type map_; + std::vector vec_; + }; +} + +#include diff --git a/libbutl/string-table.txx b/libbutl/string-table.txx index b248ef3..f1b03bf 100644 --- a/libbutl/string-table.txx +++ b/libbutl/string-table.txx @@ -2,10 +2,6 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // numeric_limits -#include // size_t -#include - namespace butl { template diff --git a/libbutl/tab-parser.cxx b/libbutl/tab-parser.cxx index bf0a7dd..183e6d7 100644 --- a/libbutl/tab-parser.cxx +++ b/libbutl/tab-parser.cxx @@ -2,12 +2,39 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif #include + +#ifndef __cpp_lib_modules +#include +#include +#include +#include + +#include #include +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.tab_parser; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif -#include +import butl.string_parser; +#else +#include +#endif using namespace std; @@ -53,7 +80,7 @@ namespace butl { sp = string_parser::parse_quoted_position (s, false); } - catch (const invalid_string& e) + catch (const string_parser::invalid_string& e) { throw parsing (name_, line_, e.position + 1, e.what ()); } @@ -69,7 +96,7 @@ namespace butl // tab_parsing // - static string + static inline string format (const string& n, uint64_t l, uint64_t c, const string& d) { ostringstream os; diff --git a/libbutl/tab-parser.hxx b/libbutl/tab-parser.hxx deleted file mode 100644 index 688dedc..0000000 --- a/libbutl/tab-parser.hxx +++ /dev/null @@ -1,72 +0,0 @@ -// file : libbutl/tab-parser.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_TAB_PARSER_HXX -#define LIBBUTL_TAB_PARSER_HXX - -#include -#include -#include -#include // uint64_t -#include // runtime_error - -#include - -namespace butl -{ - class LIBBUTL_SYMEXPORT tab_parsing: public std::runtime_error - { - public: - tab_parsing (const std::string& name, - std::uint64_t line, - std::uint64_t column, - const std::string& description); - - std::string name; - std::uint64_t line; - std::uint64_t column; - std::string description; - }; - - // Line and columns are useful for issuing diagnostics about invalid or - // missing fields. - // - struct tab_field - { - std::string value; // Field string (quoting preserved). - std::uint64_t column; // Field start column number (one-based). - }; - - struct tab_fields: std::vector - { - std::uint64_t line; // Line number (one-based). - std::uint64_t end_column; // End-of-line column (line length). - }; - - // Read and parse lines consisting of space-separated fields. Field can - // contain single or double quoted substrings (with spaces) which are - // interpreted but preserved. No escaping of the quote characters is - // supported. Blank lines and lines that start with # (collectively called - // empty lines) are ignored. - // - class LIBBUTL_SYMEXPORT tab_parser - { - public: - tab_parser (std::istream& is, const std::string& name) - : is_ (is), name_ (name) {} - - // Return next line of fields. Skip empty lines. Empty result denotes the - // end of stream. - // - tab_fields - next (); - - private: - std::istream& is_; - const std::string name_; - std::uint64_t line_ = 0; - }; -} - -#endif // LIBBUTL_TAB_PARSER_HXX diff --git a/libbutl/tab-parser.mxx b/libbutl/tab-parser.mxx new file mode 100644 index 0000000..609a46d --- /dev/null +++ b/libbutl/tab-parser.mxx @@ -0,0 +1,85 @@ +// file : libbutl/tab-parser.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#include +#include // uint64_t +#include // runtime_error +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.tab_parser; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT namespace butl +{ + class LIBBUTL_SYMEXPORT tab_parsing: public std::runtime_error + { + public: + tab_parsing (const std::string& name, + std::uint64_t line, + std::uint64_t column, + const std::string& description); + + std::string name; + std::uint64_t line; + std::uint64_t column; + std::string description; + }; + + // Line and columns are useful for issuing diagnostics about invalid or + // missing fields. + // + struct tab_field + { + std::string value; // Field string (quoting preserved). + std::uint64_t column; // Field start column number (one-based). + }; + + struct tab_fields: std::vector + { + std::uint64_t line; // Line number (one-based). + std::uint64_t end_column; // End-of-line column (line length). + }; + + // Read and parse lines consisting of space-separated fields. Field can + // contain single or double quoted substrings (with spaces) which are + // interpreted but preserved. No escaping of the quote characters is + // supported. Blank lines and lines that start with # (collectively called + // empty lines) are ignored. + // + class LIBBUTL_SYMEXPORT tab_parser + { + public: + tab_parser (std::istream& is, const std::string& name) + : is_ (is), name_ (name) {} + + // Return next line of fields. Skip empty lines. Empty result denotes the + // end of stream. + // + tab_fields + next (); + + private: + std::istream& is_; + const std::string name_; + std::uint64_t line_ = 0; + }; +} diff --git a/libbutl/target-triplet.cxx b/libbutl/target-triplet.cxx index fc805a4..88a4262 100644 --- a/libbutl/target-triplet.cxx +++ b/libbutl/target-triplet.cxx @@ -2,9 +2,33 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include #include // invalid_argument +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.target_triplet; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +#endif using namespace std; diff --git a/libbutl/target-triplet.hxx b/libbutl/target-triplet.hxx deleted file mode 100644 index 849541d..0000000 --- a/libbutl/target-triplet.hxx +++ /dev/null @@ -1,155 +0,0 @@ -// file : libbutl/target-triplet.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_TARGET_TRIPLET_HXX -#define LIBBUTL_TARGET_TRIPLET_HXX - -#include -#include - -#include - -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 - // some fields can sometimes be omitted. This looseness makes it hard to base - // any kind of decisions on the triplet without canonicalizing it and then - // splitting it into components. The way we are going to split it is like - // this: - // - // CPU - // - // This one is reasonably straightforward. Note that we always expect at - // least two components with the first being the CPU. In other words, we - // don't try to guess what just 'mingw32' might mean like config.sub does. - // - // VENDOR - // - // This can be a machine vendor as in i686-apple-darwin8, a toolchain vendor - // as in i686-lfs-linux-gnu, or something else as in arm-softfloat-linux-gnu. - // Just as we think vendor is pretty irrelevant and can be ignored, comes - // MinGW-W64 and calls itself *-w64-mingw32. While it is tempting to - // attribute w64 to OS-ABI, the MinGW-W64 folks insist it is a (presumably - // toolchain) vendor. - // - // Another example where the vendor seems to be reused for something else - // entirely is the Intel's MIC architecture: x86_64-k1om-linux. - // - // To make things more regular we also convert the information-free vendor - // names 'pc', 'unknown' and 'none' to the empty name. - // - // OS/KERNEL-OS/OS-ABI - // - // This is where things get really messy and instead of trying to guess, we - // call the entire thing SYSTEM. Except, in certain cases, we factor out the - // trailing version, again, to make SYSTEM easier to compare to. For example, - // *-darwin14.5.0 becomes 'darwin' and '14.5.0'. - // - // Again, to make things more regular, if the first component in SYSTEM is - // none, then it is removed (so *-none-eabi becomes just 'eabi'). - // - // Values for two-component systems (e.g., linux-gnu) that don't specify - // VENDOR explicitly are inherently ambiguous: is 'linux' VENDOR or part of - // SYSTEM? The only way to handle this is to recognize their specific names - // as special cases and this is what we do for some of the more common - // ones. The alternative would be to first run such names through config.sub - // which adds explicit VENDOR and this could be a reasonable fallback - // strategy for (presumably less common) cases were we don't split things - // correctly. - // - // Note also that the version splitting is only done for certain commonly- - // used targets. - // - // Some examples of canonicalization and splitting: - // - // x86_64-apple-darwin14.5.0 x86_64 apple darwin 14.5.0 - // x86_64-unknown-freebsd10.2 x86_64 freebsd 10.2 - // i686-elf i686 elf - // arm-eabi arm eabi - // arm-none-eabi arm eabi - // arm-none-linux-gnueabi arm linux-gnueabi - // arm-softfloat-linux-gnu arm softfloat linux-gnu - // i686-pc-mingw32 i686 mingw32 - // i686-w64-mingw32 i686 w64 mingw32 - // i686-lfs-linux-gnu i686 lfs linux-gnu - // x86_64-unknown-linux-gnu x86_64 linux-gnu - // x86_64-linux-gnux32 x86_64 linux-gnux32 - // x86_64-microsoft-win32-msvc14.0 x86_64 microsoft win32-msvc 14.0 - // - // Similar to version splitting, for certain commonly-used targets we also - // derive the "target class" which can be used as a shorthand, more - // convenient way to identify a targets. If the target is not recognized, - // then the special 'other' value is used. Currently the following classes - // are recognized: - // - // linux *-*-linux-* - // macos *-apple-darwin* - // bsd *-*-(freebsd|openbsd|netbsd)* - // windows *-*-win32-* | *-*-mingw32 - // - // References: - // - // 1. The libtool repository contains the PLATFORM file that lists many known - // triplets. - // - // 2. LLVM has the Triple class with similar goals. - // - struct LIBBUTL_SYMEXPORT target_triplet - { - std::string cpu; - std::string vendor; - std::string system; - std::string version; - std::string class_; - - // Assemble and returning the canonical (i.e., the one we round-trip) - // target triplet string. - // - std::string - string () const; - - bool - empty () const {return cpu.empty ();} - - int - compare (const target_triplet& y) const - { - int r; - return - (r = cpu.compare (y.cpu)) != 0 ? r : - (r = vendor.compare (y.vendor)) != 0 ? r : - (r = system.compare (y.system)) != 0 ? r : - ( version.compare (y.version)); - } - - // Parse the triplet throw std::invalid_argument if the triplet is not - // recognizable. - // - explicit - target_triplet (const std::string&); - - target_triplet () = default; - }; - - inline bool - operator== (const target_triplet& x, const target_triplet& y) - { - return x.compare (y) == 0; - } - - inline bool - operator!= (const target_triplet& x, const target_triplet& y) - { - return !(x == y); - } - - inline std::ostream& - operator<< (std::ostream& o, const target_triplet& x) - { - return o << x.string (); - } -} - -#endif // LIBBUTL_TARGET_TRIPLET_HXX diff --git a/libbutl/target-triplet.mxx b/libbutl/target-triplet.mxx new file mode 100644 index 0000000..9445aba --- /dev/null +++ b/libbutl/target-triplet.mxx @@ -0,0 +1,169 @@ +// file : libbutl/target-triplet.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.target_triplet; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT 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 + // some fields can sometimes be omitted. This looseness makes it hard to base + // any kind of decisions on the triplet without canonicalizing it and then + // splitting it into components. The way we are going to split it is like + // this: + // + // CPU + // + // This one is reasonably straightforward. Note that we always expect at + // least two components with the first being the CPU. In other words, we + // don't try to guess what just 'mingw32' might mean like config.sub does. + // + // VENDOR + // + // This can be a machine vendor as in i686-apple-darwin8, a toolchain vendor + // as in i686-lfs-linux-gnu, or something else as in arm-softfloat-linux-gnu. + // Just as we think vendor is pretty irrelevant and can be ignored, comes + // MinGW-W64 and calls itself *-w64-mingw32. While it is tempting to + // attribute w64 to OS-ABI, the MinGW-W64 folks insist it is a (presumably + // toolchain) vendor. + // + // Another example where the vendor seems to be reused for something else + // entirely is the Intel's MIC architecture: x86_64-k1om-linux. + // + // To make things more regular we also convert the information-free vendor + // names 'pc', 'unknown' and 'none' to the empty name. + // + // OS/KERNEL-OS/OS-ABI + // + // This is where things get really messy and instead of trying to guess, we + // call the entire thing SYSTEM. Except, in certain cases, we factor out the + // trailing version, again, to make SYSTEM easier to compare to. For example, + // *-darwin14.5.0 becomes 'darwin' and '14.5.0'. + // + // Again, to make things more regular, if the first component in SYSTEM is + // none, then it is removed (so *-none-eabi becomes just 'eabi'). + // + // Values for two-component systems (e.g., linux-gnu) that don't specify + // VENDOR explicitly are inherently ambiguous: is 'linux' VENDOR or part of + // SYSTEM? The only way to handle this is to recognize their specific names + // as special cases and this is what we do for some of the more common + // ones. The alternative would be to first run such names through config.sub + // which adds explicit VENDOR and this could be a reasonable fallback + // strategy for (presumably less common) cases were we don't split things + // correctly. + // + // Note also that the version splitting is only done for certain commonly- + // used targets. + // + // Some examples of canonicalization and splitting: + // + // x86_64-apple-darwin14.5.0 x86_64 apple darwin 14.5.0 + // x86_64-unknown-freebsd10.2 x86_64 freebsd 10.2 + // i686-elf i686 elf + // arm-eabi arm eabi + // arm-none-eabi arm eabi + // arm-none-linux-gnueabi arm linux-gnueabi + // arm-softfloat-linux-gnu arm softfloat linux-gnu + // i686-pc-mingw32 i686 mingw32 + // i686-w64-mingw32 i686 w64 mingw32 + // i686-lfs-linux-gnu i686 lfs linux-gnu + // x86_64-unknown-linux-gnu x86_64 linux-gnu + // x86_64-linux-gnux32 x86_64 linux-gnux32 + // x86_64-microsoft-win32-msvc14.0 x86_64 microsoft win32-msvc 14.0 + // + // Similar to version splitting, for certain commonly-used targets we also + // derive the "target class" which can be used as a shorthand, more + // convenient way to identify a targets. If the target is not recognized, + // then the special 'other' value is used. Currently the following classes + // are recognized: + // + // linux *-*-linux-* + // macos *-apple-darwin* + // bsd *-*-(freebsd|openbsd|netbsd)* + // windows *-*-win32-* | *-*-mingw32 + // + // References: + // + // 1. The libtool repository contains the PLATFORM file that lists many known + // triplets. + // + // 2. LLVM has the Triple class with similar goals. + // + struct LIBBUTL_SYMEXPORT target_triplet + { + std::string cpu; + std::string vendor; + std::string system; + std::string version; + std::string class_; + + // Assemble and returning the canonical (i.e., the one we round-trip) + // target triplet string. + // + std::string + string () const; + + bool + empty () const {return cpu.empty ();} + + int + compare (const target_triplet& y) const + { + int r; + return + (r = cpu.compare (y.cpu)) != 0 ? r : + (r = vendor.compare (y.vendor)) != 0 ? r : + (r = system.compare (y.system)) != 0 ? r : + ( version.compare (y.version)); + } + + // Parse the triplet throw std::invalid_argument if the triplet is not + // recognizable. + // + explicit + target_triplet (const std::string&); + + //target_triplet () = default; + target_triplet () {} //@@ MOD (VC ICE) + }; + + inline bool + operator== (const target_triplet& x, const target_triplet& y) + { + return x.compare (y) == 0; + } + + inline bool + operator!= (const target_triplet& x, const target_triplet& y) + { + return !(x == y); + } + + inline std::ostream& + operator<< (std::ostream& o, const target_triplet& x) + { + return o << x.string (); + } +} diff --git a/libbutl/timestamp.cxx b/libbutl/timestamp.cxx index b712b0c..3fececf 100644 --- a/libbutl/timestamp.cxx +++ b/libbutl/timestamp.cxx @@ -2,14 +2,43 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif #include // localtime_{r,s}(), gmtime_{r,s}(), strptime(), timegm() #include // EINVAL -#include // tm, time_t, mktime() +#include // Note: also gets __GLIBCXX__. + +// Implementation of strptime() and timegm() for Windows. +// +// Here we have several cases. If this is VC++, then we implement strptime() +// via C++11 std::get_time(). And if this is MINGW GCC (or, more precisely, +// libstdc++), then we have several problems. Firstly, GCC prior to 5 doesn't +// implement std::get_time(). Secondly, GCC 5 and even 6 have buggy +// std::get_time() (it cannot parse single-digit days). So what we are going +// to do in this case is use a FreeBSD-based strptime() implementation. +// +// Include the C implementation here, out of module purview. +// +#ifdef _WIN32 +#ifdef __GLIBCXX__ +extern "C" +{ +#include "strptime.c" +} +#else +#include // LC_ALL +#endif +#endif + +#ifndef __cpp_lib_modules +#include +#include + +#include // tm, time_t, mktime(), strftime()[__GLIBCXX__] #include // strtoull() -#include #include #include // put_time(), setw(), dec, right #include // strlen(), memcpy() @@ -17,7 +46,34 @@ #include // pair, make_pair() #include // runtime_error -#include // throw_generic_error() +// Implementation of strptime() for VC. +// +#ifdef _WIN32 +#ifndef __GLIBCXX__ +#include +#include +#include +#endif +#endif +#endif + +// Other includes. + +#ifdef __cpp_modules +module butl.timestamp; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +import butl.utility; +#else +#include // throw_generic_error() +#endif using namespace std; @@ -33,10 +89,6 @@ using namespace std; // of the std::tm argument. // #ifdef __GLIBCXX__ - -#include // tm, strftime() -#include - namespace details { struct put_time_data @@ -64,7 +116,6 @@ namespace details } using namespace details; - #endif // Thread-safe implementations of gmtime() and localtime(). @@ -115,6 +166,91 @@ namespace details } } +// Implementation of strptime() for VC and timegm() for Windows. +// +#ifdef _WIN32 +#ifndef __GLIBCXX__ +static char* +strptime (const char* input, const char* format, tm* time) +{ + // VC std::get_time()-based implementation. + // + istringstream is (input); + + // The original strptime() function behaves according to the process' C + // locale (set with std::setlocale()), which can differ from the process C++ + // locale (set with std::locale::global()). + // + is.imbue (locale (setlocale (LC_ALL, nullptr))); + + if (!(is >> get_time (time, format))) + return nullptr; + else + // tellg() behaves as UnformattedInputFunction, so returns failure status + // if eofbit is set. + // + return const_cast ( + input + (is.eof () + ? strlen (input) + : static_cast (is.tellg ()))); +} +#endif + +static time_t +timegm (tm* ctm) +{ + const time_t e (static_cast (-1)); + + // We will use an example to explain how it works. Say *ctm contains 9 AM of + // some day. Note that no time zone information is available. + // + // Convert it to the time from Epoch as if it's in the local time zone. + // + ctm->tm_isdst = -1; + time_t t (mktime (ctm)); + if (t == e) + return e; + + // Let's say we are in Moscow, and t contains the time passed from Epoch till + // 9 AM MSK. But that is not what we need. What we need is the time passed + // from Epoch till 9 AM GMT. This is some bigger number, as it takes longer + // to achieve the same calendar time for more Western location. So we need to + // find that offset, and increment t with it to obtain the desired value. The + // offset is effectively the time difference between MSK and GMT time zones. + // + tm gtm; + if (details::gmtime (&t, >m) == nullptr) + return e; + + // gmtime() being called for the timepoint t returns 6 AM. So now we have + // *ctm and gtm, which value difference (3 hours) reflects the desired + // offset. The only problem is that we can not deduct gtm from *ctm, to get + // the offset expressed as time_t. To do that we need to apply to both of + // them the same conversion function transforming std::tm to std::time_t. The + // mktime() can do that, so the expression (mktime(ctm) - mktime(>m)) + // calculates the desired offset. + // + // To ensure mktime() works exactly the same way for both cases, we need to + // reset Daylight Saving Time flag for each of *ctm and gtm. + // + ctm->tm_isdst = 0; + time_t lt (mktime (ctm)); + if (lt == e) + return e; + + gtm.tm_isdst = 0; + time_t gt (mktime (>m)); + if (gt == e) + return e; + + // C11 standard specifies time_t to be a real type (integer and real floating + // types are collectively called real types). So we can not consider it to be + // signed. + // + return lt > gt ? t + (lt - gt) : t - (gt - lt); +} +#endif // _WIN32 + namespace butl { ostream& @@ -337,123 +473,7 @@ namespace butl to_stream (o, d, nsec); return o.str (); } -} - -// Implementation of strptime() and timegm() for Windows. -// -// Here we have several cases. If this is VC++, then we implement strptime() -// via C++11 std::get_time(). And if this is MINGW GCC (or, more precisely, -// libstdc++), then we have several problems. Firstly, GCC prior to 5 doesn't -// implement std::get_time(). Secondly, GCC 5 and even 6 have buggy -// std::get_time() (it cannot parse single-digit days). So what we are going -// to do in this case is use a FreeBSD-based strptime() implementation. -// -#ifdef _WIN32 - -#ifdef __GLIBCXX__ - -// Fallback to a FreeBSD-based implementation. -// -extern "C" -{ -#include "strptime.c" -} - -#else // NOT __GLIBCXX__ - -#include // tm -#include -#include -#include -#include // strlen() - -// VC++ std::get_time()-based implementation. -// -static char* -strptime (const char* input, const char* format, tm* time) -{ - istringstream is (input); - - // The original strptime() function behaves according to the process' C - // locale (set with std::setlocale()), which can differ from the process C++ - // locale (set with std::locale::global()). - // - is.imbue (locale (setlocale (LC_ALL, nullptr))); - - if (!(is >> get_time (time, format))) - return nullptr; - else - // tellg() behaves as UnformattedInputFunction, so returns failure status - // if eofbit is set. - // - return const_cast ( - input + (is.eof () - ? strlen (input) - : static_cast (is.tellg ()))); -} - -#endif // __GLIBCXX__ - -#include // time_t, tm, mktime() -static time_t -timegm (tm* ctm) -{ - const time_t e (static_cast (-1)); - - // We will use an example to explain how it works. Say *ctm contains 9 AM of - // some day. Note that no time zone information is available. - // - // Convert it to the time from Epoch as if it's in the local time zone. - // - ctm->tm_isdst = -1; - time_t t (mktime (ctm)); - if (t == e) - return e; - - // Let's say we are in Moscow, and t contains the time passed from Epoch till - // 9 AM MSK. But that is not what we need. What we need is the time passed - // from Epoch till 9 AM GMT. This is some bigger number, as it takes longer - // to achieve the same calendar time for more Western location. So we need to - // find that offset, and increment t with it to obtain the desired value. The - // offset is effectively the time difference between MSK and GMT time zones. - // - tm gtm; - if (details::gmtime (&t, >m) == nullptr) - return e; - - // gmtime() being called for the timepoint t returns 6 AM. So now we have - // *ctm and gtm, which value difference (3 hours) reflects the desired - // offset. The only problem is that we can not deduct gtm from *ctm, to get - // the offset expressed as time_t. To do that we need to apply to both of - // them the same conversion function transforming std::tm to std::time_t. The - // mktime() can do that, so the expression (mktime(ctm) - mktime(>m)) - // calculates the desired offset. - // - // To ensure mktime() works exactly the same way for both cases, we need to - // reset Daylight Saving Time flag for each of *ctm and gtm. - // - ctm->tm_isdst = 0; - time_t lt (mktime (ctm)); - if (lt == e) - return e; - - gtm.tm_isdst = 0; - time_t gt (mktime (>m)); - if (gt == e) - return e; - - // C11 standard specifies time_t to be a real type (integer and real floating - // types are collectively called real types). So we can not consider it to be - // signed. - // - return lt > gt ? t + (lt - gt) : t - (gt - lt); -} - -#endif // _WIN32 - -namespace butl -{ static pair from_string (const char* input, const char* format, const char** end) { diff --git a/libbutl/timestamp.hxx b/libbutl/timestamp.hxx deleted file mode 100644 index c1621a7..0000000 --- a/libbutl/timestamp.hxx +++ /dev/null @@ -1,169 +0,0 @@ -// file : libbutl/timestamp.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_TIMESTAMP_HXX -#define LIBBUTL_TIMESTAMP_HXX - -#include -#include - -#include - -#include - -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, - // system_clock has nanoseconds resolution and counts from the UNIX - // epoch. The latter is important since struct stat also returns times - // based on UNIX epoch. - // - // The underlying type for nanoseconds duration is signed integer type - // of at least 64 bits (currently int64_t, available as duration::rep). - // Because it is signed, we will overflow in year 2262 but by then the - // underlying type will most likely have changed to something larger - // than 64-bit. - // - // So to support other platforms that could possibly use a different - // system_clock resolutions (e.g., microseconds), we actually not going - // to assume anywhere (except perhaps timestamp.cxx) that we are dealing - // with nanoseconds or the 64-bit underlying type. - // - using std::chrono::system_clock; - - // Note that the default-initialized timestamp has the timestamp_nonexistent - // value. - // - using timestamp = system_clock::time_point; - using duration = system_clock::duration; - - // Generally-useful special values. Note that unknown is less than - // nonexistent and is less than any non-special value. - // - 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)); - - // Print human-readable representation of the timestamp. - // - // By default the timestamp is printed by localtime_r() in the local - // timezone, so tzset() from should be called prior to using the - // corresponding operator or the to_stream() function (normally from main() - // or equivalent). - // - // The format argument in the to_stream() function is the put_time() format - // string except that it also supports the nanoseconds conversion specifier - // in the form %[N] where is the optional single delimiter character, - // for example '.'. If the nanoseconds part is 0, then it is not printed - // (nor the delimiter character). Otherwise, if necessary, the nanoseconds - // part is padded to 9 characters with leading zeros. - // - // The special argument in the to_stream() function indicates whether the - // special timestamp_unknown and timestamp_nonexistent values should be - // printed as '' and '', respectively. - // - // The local argument in the to_stream() function indicates whether to use - // localtime_r() or gmtime_r(). - // - // Note also that these operators/function may throw std::system_error. - // - // Finally, padding is not fully supported by these operators/function. They - // throw runtime_error if nanoseconds conversion specifier is present and - // the stream's width field has been set to non-zero value before the call. - // - // Potential improvements: - // - add flag to to_stream() to use - // - support %[U] (microseconds) and %[M] (milliseconds). - // - make to_stream() a manipulator, similar to put_time() - // - support %(N) version for non-optional printing - // - support for suffix %[N], for example %[N nsec] - // - LIBBUTL_SYMEXPORT std::ostream& - to_stream (std::ostream&, - const timestamp&, - const char* format, - bool special, - bool local); - - // Same as above, but provide the result as a string. Note that it is - // implemented via to_stream() and std::ostringstream. - // - LIBBUTL_SYMEXPORT std::string - to_string (const timestamp&, - const char* format, - bool special, - bool local); - - inline std::ostream& - operator<< (std::ostream& os, const timestamp& ts) - { - return to_stream (os, ts, "%Y-%m-%d %H:%M:%S%[.N]", true, true); - } - - // Print human-readable representation of the duration. - // - LIBBUTL_SYMEXPORT std::ostream& - to_stream (std::ostream&, const duration&, bool nanoseconds); - - // Same as above, but provide the result as a string. Note that it is - // implemented via to_stream() and std::ostringstream. - // - LIBBUTL_SYMEXPORT std::string - to_string (const duration&, bool nanoseconds); - - inline std::ostream& - operator<< (std::ostream& os, const duration& d) - { - return to_stream (os, d, true); - } - - // Parse human-readable representation of the timestamp. - // - // The format argument is the strptime() format string except that it also - // supports the fraction of a second specifier in the form %[], where - // is the optional single delimiter character, for example '.', and - // is one of the 'N', 'U', 'M' characters, denoting nanoseconds, - // microseconds and milliseconds, respectively. - // - // The delimiter is mandatory. If no such character is encountered at - // the corresponding position of the input string, the function behaves as - // if no %[] specifier were provided. Only single %[] specifier in the - // format string is currently supported. - // - // If the delimiter is present, then it should be followed by 9 (N), 6 (U), - // or 3 (M) digit value padded with leading zeros if necessary. - // - // If the local argument is true, then the input is assume to be local time - // and the result is returned as local time as well. Otherwise, UCT is used - // in both cases. - // - // If the end argument is not NULL, then it points to the first character - // that was not parsed. Otherwise, throw invalid_argument in case of any - // unparsed characters. - // - // Throw std::system_error on input/format mismatch and underlying time - // conversion function failures. - // - // Note that internally from_string() calls strptime(), which behaves - // according to the process' C locale (set with std::setlocale()) and not - // the C++ locale (set with std::locale::global()). However the behaviour - // can be affected by std::locale::global() as well, as it itself calls - // std::setlocale() for the locale with a name. - // - // Potential improvements: - // - support %() version for non-optional component but with optional - // delimiter - // - ability to parse local, return UTC and vice-versa - // - handle timezone parsing - // - LIBBUTL_SYMEXPORT timestamp - from_string (const char* input, - const char* format, - bool local, - const char** end = nullptr); -} - -#endif // LIBBUTL_TIMESTAMP_HXX diff --git a/libbutl/timestamp.mxx b/libbutl/timestamp.mxx new file mode 100644 index 0000000..cde0e01 --- /dev/null +++ b/libbutl/timestamp.mxx @@ -0,0 +1,191 @@ +// file : libbutl/timestamp.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.timestamp; +#ifdef __cpp_lib_modules +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_MODEXPORT 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, + // system_clock has nanoseconds resolution and counts from the UNIX + // epoch. The latter is important since struct stat also returns times + // based on UNIX epoch. + // + // The underlying type for nanoseconds duration is signed integer type + // of at least 64 bits (currently int64_t, available as duration::rep). + // Because it is signed, we will overflow in year 2262 but by then the + // underlying type will most likely have changed to something larger + // than 64-bit. + // + // So to support other platforms that could possibly use a different + // system_clock resolutions (e.g., microseconds), we actually not going + // to assume anywhere (except perhaps timestamp.cxx) that we are dealing + // with nanoseconds or the 64-bit underlying type. + // + using std::chrono::system_clock; + + // Note that the default-initialized timestamp has the timestamp_nonexistent + // value. + // + using timestamp = system_clock::time_point; + using duration = system_clock::duration; + + // Generally-useful special values. Note that unknown is less than + // nonexistent and is less than any non-special value. + // +#if defined(__cpp_modules) && 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)); +#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)); +#endif + + // Print human-readable representation of the timestamp. + // + // By default the timestamp is printed by localtime_r() in the local + // timezone, so tzset() from should be called prior to using the + // corresponding operator or the to_stream() function (normally from main() + // or equivalent). + // + // The format argument in the to_stream() function is the put_time() format + // string except that it also supports the nanoseconds conversion specifier + // in the form %[N] where is the optional single delimiter character, + // for example '.'. If the nanoseconds part is 0, then it is not printed + // (nor the delimiter character). Otherwise, if necessary, the nanoseconds + // part is padded to 9 characters with leading zeros. + // + // The special argument in the to_stream() function indicates whether the + // special timestamp_unknown and timestamp_nonexistent values should be + // printed as '' and '', respectively. + // + // The local argument in the to_stream() function indicates whether to use + // localtime_r() or gmtime_r(). + // + // Note also that these operators/function may throw std::system_error. + // + // Finally, padding is not fully supported by these operators/function. They + // throw runtime_error if nanoseconds conversion specifier is present and + // the stream's width field has been set to non-zero value before the call. + // + // Potential improvements: + // - add flag to to_stream() to use + // - support %[U] (microseconds) and %[M] (milliseconds). + // - make to_stream() a manipulator, similar to put_time() + // - support %(N) version for non-optional printing + // - support for suffix %[N], for example %[N nsec] + // + LIBBUTL_SYMEXPORT std::ostream& + to_stream (std::ostream&, + const timestamp&, + const char* format, + bool special, + bool local); + + // Same as above, but provide the result as a string. Note that it is + // implemented via to_stream() and std::ostringstream. + // + LIBBUTL_SYMEXPORT std::string + to_string (const timestamp&, + const char* format, + bool special, + bool local); + + inline std::ostream& + operator<< (std::ostream& os, const timestamp& ts) + { + return to_stream (os, ts, "%Y-%m-%d %H:%M:%S%[.N]", true, true); + } + + // Print human-readable representation of the duration. + // + LIBBUTL_SYMEXPORT std::ostream& + to_stream (std::ostream&, const duration&, bool nanoseconds); + + // Same as above, but provide the result as a string. Note that it is + // implemented via to_stream() and std::ostringstream. + // + LIBBUTL_SYMEXPORT std::string + to_string (const duration&, bool nanoseconds); + + inline std::ostream& + operator<< (std::ostream& os, const duration& d) + { + return to_stream (os, d, true); + } + + // Parse human-readable representation of the timestamp. + // + // The format argument is the strptime() format string except that it also + // supports the fraction of a second specifier in the form %[], where + // is the optional single delimiter character, for example '.', and + // is one of the 'N', 'U', 'M' characters, denoting nanoseconds, + // microseconds and milliseconds, respectively. + // + // The delimiter is mandatory. If no such character is encountered at + // the corresponding position of the input string, the function behaves as + // if no %[] specifier were provided. Only single %[] specifier in the + // format string is currently supported. + // + // If the delimiter is present, then it should be followed by 9 (N), 6 (U), + // or 3 (M) digit value padded with leading zeros if necessary. + // + // If the local argument is true, then the input is assume to be local time + // and the result is returned as local time as well. Otherwise, UCT is used + // in both cases. + // + // If the end argument is not NULL, then it points to the first character + // that was not parsed. Otherwise, throw invalid_argument in case of any + // unparsed characters. + // + // Throw std::system_error on input/format mismatch and underlying time + // conversion function failures. + // + // Note that internally from_string() calls strptime(), which behaves + // according to the process' C locale (set with std::setlocale()) and not + // the C++ locale (set with std::locale::global()). However the behaviour + // can be affected by std::locale::global() as well, as it itself calls + // std::setlocale() for the locale with a name. + // + // Potential improvements: + // - support %() version for non-optional component but with optional + // delimiter + // - ability to parse local, return UTC and vice-versa + // - handle timezone parsing + // + LIBBUTL_SYMEXPORT timestamp + from_string (const char* input, + const char* format, + bool local, + const char** end = nullptr); +} diff --git a/libbutl/utility.cxx b/libbutl/utility.cxx index 8ce2cbc..c8e4e85 100644 --- a/libbutl/utility.cxx +++ b/libbutl/utility.cxx @@ -2,15 +2,39 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#ifndef __cpp_modules +#include +#endif #ifdef _WIN32 -# include +#include #endif +#ifndef __cpp_lib_modules #include +#include +#include + #include #include +#endif + +#include +#include + +#ifdef __cpp_modules +module butl.utility; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +#endif + namespace butl { diff --git a/libbutl/utility.hxx b/libbutl/utility.hxx deleted file mode 100644 index 3a994bb..0000000 --- a/libbutl/utility.hxx +++ /dev/null @@ -1,264 +0,0 @@ -// file : libbutl/utility.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_UTILITY_HXX -#define LIBBUTL_UTILITY_HXX - -#include -#include // ostream -#include // size_t -#include // move(), forward() -#include // strcmp(), strlen() -#include // uncaught_exceptions -#include // exception, uncaught_exception(s)() -#include // thread_local - -//#include // hash - -#include - -namespace butl -{ - // Throw std::system_error with generic_category or system_category, - // respectively. - // - // The generic version should be used for portable errno codes (those that - // are mapped to std::errc). The system version should be used for platform- - // specific codes, for example, additional errno codes on POSIX systems or - // the result of GetLastError() on Windows. - // - // See also the exception sanitization below. - // - [[noreturn]] LIBBUTL_SYMEXPORT void - throw_generic_error (int errno_code, const char* what = nullptr); - - [[noreturn]] LIBBUTL_SYMEXPORT void - throw_system_error (int system_code, int fallback_errno_code = 0); - - // Convert ASCII character/string case. If there is no upper/lower case - // counterpart, leave the character unchanged. The POSIX locale (also known - // as C locale) must be the current application locale. Otherwise the - // behavior is undefined. - // - // Note that the POSIX locale specifies behaviour on data consisting - // entirely of characters from the portable character set (subset of ASCII - // including 103 non-negative characters and English alphabet letters in - // particular) and the control character set (more about them at - // http://pubs.opengroup.org/onlinepubs/009696899/basedefs/xbd_chap06.html). - // - // Also note that according to the POSIX locale definition the case - // conversion can be applied only to [A-Z] and [a-z] character ranges being - // translated to each other (more about that at - // http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_02) - // - char ucase (char); - std::string ucase (const char*, std::size_t = std::string::npos); - std::string ucase (const std::string&); - std::string& ucase (std::string&); - void ucase (char*, std::size_t); - - char lcase (char); - std::string lcase (const char*, std::size_t = std::string::npos); - std::string lcase (const std::string&); - std::string& lcase (std::string&); - void lcase (char*, std::size_t); - - // Compare ASCII characters/strings ignoring case. Behave as if characters - // had been converted to the lower case and then byte-compared. Return a - // negative, zero or positive value if the left hand side is less, equal or - // greater than the right hand side, respectivelly. The POSIX locale (also - // known as C locale) must be the current application locale. Otherwise the - // behavior is undefined. - // - // The optional size argument specifies the maximum number of characters - // to compare. - // - int casecmp (char, char); - - int casecmp (const std::string&, const std::string&, - std::size_t = std::string::npos); - - int casecmp (const std::string&, const char*, - std::size_t = std::string::npos); - - int casecmp (const char*, const char*, std::size_t = std::string::npos); - - // Case-insensitive key comparators (i.e., to be used in sets, maps, etc). - // - struct case_compare_string - { - bool operator() (const std::string& x, const std::string& y) const - { - return casecmp (x, y) < 0; - } - }; - - struct case_compare_c_string - { - bool operator() (const char* x, const char* y) const - { - return casecmp (x, y) < 0; - } - }; - - bool - alpha (char); - - bool - digit (char); - - bool - alnum (char); - - // Key comparators (i.e., to be used in sets, maps, etc). - // - struct compare_c_string - { - bool operator() (const char* x, const char* y) const noexcept - { - return std::strcmp (x, y) < 0; - } - }; - - struct compare_pointer_target - { - template - bool operator() (const P& x, const P& y) const {return *x < *y;} - }; - - //struct hash_pointer_target - //{ - // template - // std::size_t operator() (const P& x) const {return std::hash (*x);} - //}; - - // Combine one or more hash values. - // - inline std::size_t - combine_hash (std::size_t s, std::size_t h) - { - // Magic formula from boost::hash_combine(). - // - return s ^ (h + 0x9e3779b9 + (s << 6) + (s >> 2)); - } - - template - inline std::size_t - combine_hash (std::size_t s, std::size_t h, S... hs) - { - return combine_hash (combine_hash (s, h), hs...); - } - - // Support for reverse iteration using range-based for-loop: - // - // for (... : reverse_iterate (x)) ... - // - template - class reverse_range - { - T x_; - - public: - reverse_range (T&& x): x_ (std::forward (x)) {} - - auto begin () const -> decltype (this->x_.rbegin ()) {return x_.rbegin ();} - auto end () const -> decltype (this->x_.rend ()) {return x_.rend ();} - }; - - template - inline reverse_range - reverse_iterate (T&& x) {return reverse_range (std::forward (x));} - - // Call a function if there is an exception. - // - - template - struct exception_guard; - - template - inline exception_guard - make_exception_guard (F f) - { - return exception_guard (std::move (f)); - } - -#ifdef __cpp_lib_uncaught_exceptions - template - struct exception_guard - { - exception_guard (F f) - : f_ (std::move (f)), - u_ (std::uncaught_exceptions ()) {} - - ~exception_guard () - { - if (u_ != std::uncaught_exceptions ()) - f_ (); - } - - private: - F f_; - int u_; - }; -#else - // Fallback implementation using a TLS flag. - // - // True means we are in the body of a destructor that is being called as - // part of the exception stack unwindining. - // - extern -#ifdef __cpp_thread_local - thread_local -#else - __thread -#endif - bool exception_unwinding_dtor_; - - // On Windows one cannot export a thread-local variable so we have to - // use a wrapper functions. - // -#ifdef _WIN32 - LIBBUTL_SYMEXPORT bool& - exception_unwinding_dtor (); -#else - inline bool& - exception_unwinding_dtor () {return exception_unwinding_dtor_;} -#endif - - template - struct exception_guard - { - exception_guard (F f): f_ (std::move (f)) {} - ~exception_guard () - { - if (std::uncaught_exception ()) - { - exception_unwinding_dtor () = true; - f_ (); - exception_unwinding_dtor () = false; - } - } - - private: - F f_; - }; -#endif -} - -namespace std -{ - // Sanitize the exception description before printing. This includes: - // - // - stripping leading colons and spaces (see fdstream.cxx) - // - stripping trailing newlines, periods, and spaces - // - stripping system error redundant suffix (see utility.cxx) - // - lower-case the first letter if the beginning looks like a word - // - LIBBUTL_SYMEXPORT ostream& - operator<< (ostream&, const exception&); -} - -#include - -#endif // LIBBUTL_UTILITY_HXX diff --git a/libbutl/utility.ixx b/libbutl/utility.ixx index e45a729..cec61c8 100644 --- a/libbutl/utility.ixx +++ b/libbutl/utility.ixx @@ -2,14 +2,6 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#ifndef _WIN32 -# include // strcasecmp(), strncasecmp() -#else -# include // _stricmp(), _strnicmp() -#endif - -#include // toupper(), tolower(), isalpha(), isdigit(), isalnum() - namespace butl { inline char diff --git a/libbutl/utility.mxx b/libbutl/utility.mxx new file mode 100644 index 0000000..6a50186 --- /dev/null +++ b/libbutl/utility.mxx @@ -0,0 +1,285 @@ +// file : libbutl/utility.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +#ifndef _WIN32 +#if !defined(__cpp_lib_modules) || !defined(__clang__) //@@ MOD Clang ICE +# include // strcasecmp(), strncasecmp() +#endif +#else +# include // _stricmp(), _strnicmp() +#endif + +#ifndef __cpp_lib_modules +#include +#include // ostream +#include // size_t +#include // move(), forward() +#include // strcmp(), strlen() +#include // exception, uncaught_exception(s)() +//#include // hash + +#include // toupper(), tolower(), isalpha(), isdigit(), isalnum() +#endif + +#include // thread_local +#include // uncaught_exceptions + +#ifdef __cpp_modules +export module butl.utility; +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +#endif + +#include + + +LIBBUTL_MODEXPORT namespace butl +{ + // Throw std::system_error with generic_category or system_category, + // respectively. + // + // The generic version should be used for portable errno codes (those that + // are mapped to std::errc). The system version should be used for platform- + // specific codes, for example, additional errno codes on POSIX systems or + // the result of GetLastError() on Windows. + // + // See also the exception sanitization below. + // + [[noreturn]] LIBBUTL_SYMEXPORT void + throw_generic_error (int errno_code, const char* what = nullptr); + + [[noreturn]] LIBBUTL_SYMEXPORT void + throw_system_error (int system_code, int fallback_errno_code = 0); + + // Convert ASCII character/string case. If there is no upper/lower case + // counterpart, leave the character unchanged. The POSIX locale (also known + // as C locale) must be the current application locale. Otherwise the + // behavior is undefined. + // + // Note that the POSIX locale specifies behaviour on data consisting + // entirely of characters from the portable character set (subset of ASCII + // including 103 non-negative characters and English alphabet letters in + // particular) and the control character set (more about them at + // http://pubs.opengroup.org/onlinepubs/009696899/basedefs/xbd_chap06.html). + // + // Also note that according to the POSIX locale definition the case + // conversion can be applied only to [A-Z] and [a-z] character ranges being + // translated to each other (more about that at + // http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_02) + // + char ucase (char); + std::string ucase (const char*, std::size_t = -1 /*@@ MOD VC ICE = std::string::npos*/); + + std::string ucase (const std::string&); + std::string& ucase (std::string&); + void ucase (char*, std::size_t); + + char lcase (char); + std::string lcase (const char*, std::size_t = -1 /*@@ MOD VC ICE = std::string::npos*/); + std::string lcase (const std::string&); + std::string& lcase (std::string&); + void lcase (char*, std::size_t); + + // Compare ASCII characters/strings ignoring case. Behave as if characters + // had been converted to the lower case and then byte-compared. Return a + // negative, zero or positive value if the left hand side is less, equal or + // greater than the right hand side, respectivelly. The POSIX locale (also + // known as C locale) must be the current application locale. Otherwise the + // behavior is undefined. + // + // The optional size argument specifies the maximum number of characters + // to compare. + // + int casecmp (char, char); + + int casecmp (const std::string&, const std::string&, + std::size_t = -1 /*@@ MOD VC ICE std::string::npos*/); + + int casecmp (const std::string&, const char*, + std::size_t = -1 /*@@ MOD VC ICE std::string::npos*/); + + int casecmp (const char*, const char*, std::size_t = -1 /*MOD VC ICE std::string::npos*/); + + // Case-insensitive key comparators (i.e., to be used in sets, maps, etc). + // + struct case_compare_string + { + bool operator() (const std::string& x, const std::string& y) const + { + return casecmp (x, y) < 0; + } + }; + + struct case_compare_c_string + { + bool operator() (const char* x, const char* y) const + { + return casecmp (x, y) < 0; + } + }; + + bool + alpha (char); + + bool + digit (char); + + bool + alnum (char); + + // Key comparators (i.e., to be used in sets, maps, etc). + // + struct compare_c_string + { + bool operator() (const char* x, const char* y) const noexcept + { + return std::strcmp (x, y) < 0; + } + }; + + struct compare_pointer_target + { + template + bool operator() (const P& x, const P& y) const {return *x < *y;} + }; + + //struct hash_pointer_target + //{ + // template + // std::size_t operator() (const P& x) const {return std::hash (*x);} + //}; + + // Combine one or more hash values. + // + inline std::size_t + combine_hash (std::size_t s, std::size_t h) + { + // Magic formula from boost::hash_combine(). + // + return s ^ (h + 0x9e3779b9 + (s << 6) + (s >> 2)); + } + + template + inline std::size_t + combine_hash (std::size_t s, std::size_t h, S... hs) + { + return combine_hash (combine_hash (s, h), hs...); + } + + // Support for reverse iteration using range-based for-loop: + // + // for (... : reverse_iterate (x)) ... + // + template + class reverse_range + { + T x_; + + public: + reverse_range (T&& x): x_ (std::forward (x)) {} + + auto begin () const -> decltype (this->x_.rbegin ()) {return x_.rbegin ();} + auto end () const -> decltype (this->x_.rend ()) {return x_.rend ();} + }; + + template + inline reverse_range + reverse_iterate (T&& x) {return reverse_range (std::forward (x));} + + // Call a function if there is an exception. + // + + template + struct exception_guard; + + template + inline exception_guard + make_exception_guard (F f) + { + return exception_guard (std::move (f)); + } + +#ifdef __cpp_lib_uncaught_exceptions + template + struct exception_guard + { + exception_guard (F f) + : f_ (std::move (f)), + u_ (std::uncaught_exceptions ()) {} + + ~exception_guard () + { + if (u_ != std::uncaught_exceptions ()) + f_ (); + } + + private: + F f_; + int u_; + }; +#else + // Fallback implementation using a TLS flag. + // + // True means we are in the body of a destructor that is being called as + // part of the exception stack unwindining. + // + extern +#ifdef __cpp_thread_local + thread_local +#else + __thread +#endif + bool exception_unwinding_dtor_; + + // On Windows one cannot export a thread-local variable so we have to + // use a wrapper functions. + // +#ifdef _WIN32 + LIBBUTL_SYMEXPORT bool& + exception_unwinding_dtor (); +#else + inline bool& + exception_unwinding_dtor () {return exception_unwinding_dtor_;} +#endif + + template + struct exception_guard + { + exception_guard (F f): f_ (std::move (f)) {} + ~exception_guard () + { + if (std::uncaught_exception ()) + { + exception_unwinding_dtor () = true; + f_ (); + exception_unwinding_dtor () = false; + } + } + + private: + F f_; + }; +#endif +} + +LIBBUTL_MODEXPORT namespace std +{ + // Sanitize the exception description before printing. This includes: + // + // - stripping leading colons and spaces (see fdstream.cxx) + // - stripping trailing newlines, periods, and spaces + // - stripping system error redundant suffix (see utility.cxx) + // - lower-case the first letter if the beginning looks like a word + // + LIBBUTL_SYMEXPORT ostream& + operator<< (ostream&, const exception&); +} + +#include diff --git a/libbutl/vector-view.hxx b/libbutl/vector-view.hxx deleted file mode 100644 index 98b314c..0000000 --- a/libbutl/vector-view.hxx +++ /dev/null @@ -1,120 +0,0 @@ -// file : libbutl/vector-view.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUTL_VECTOR_VIEW_HXX -#define LIBBUTL_VECTOR_VIEW_HXX - -#include -#include // size_t, ptrdiff_t -#include // swap() -#include // reverse_iterator -#include // out_of_range - -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). - // - // @@ If T is const T1, could be useful to have a c-tor from vector. - // - template - class vector_view - { - public: - using value_type = T; - using pointer = T*; - using reference = T&; - using const_pointer = const T*; - using const_reference = const T&; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - - using iterator = T*; - using const_iterator = const T*; - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; - - // construct/copy/destroy: - // - vector_view (): data_ (nullptr), size_ (0) {} - vector_view (T* d, size_type s): data_ (d), size_ (s) {} - - template - vector_view (std::vector& v) - : data_ (v.data ()), size_ (v.size ()) {} - - template - vector_view (const std::vector& v) - : data_ (v.data ()), size_ (v.size ()) {} - - template - vector_view (const vector_view& v) - : data_ (v.data ()), size_ (v.size ()) {} - - vector_view (vector_view&&) = default; - vector_view (const vector_view&) = default; - vector_view& operator= (vector_view&&) = default; - vector_view& operator= (const vector_view&) = default; - - // iterators: - // - iterator begin() const {return data_;} - iterator end() const {return data_ + size_;} - - const_iterator cbegin() const {return data_;} - const_iterator cend() const {return data_ + size_;} - - reverse_iterator rbegin() const {return reverse_iterator (end ());} - reverse_iterator rend() const {return reverse_iterator (begin ());} - - const_reverse_iterator crbegin() const { - return const_reverse_iterator (cend ());} - const_reverse_iterator crend() const { - return const_reverse_iterator (cbegin ());} - - // capacity: - // - size_type size() const {return size_;} - bool empty() const {return size_ == 0;} - - // element access: - // - reference operator[](size_type n) const {return data_[n];} - reference front() const {return data_[0];} - reference back() const {return data_[size_ - 1];} - - reference at(size_type n) const - { - if (n >= size_) - throw std::out_of_range ("index out of range"); - return data_[n]; - } - - // data access: - // - T* data() const {return data_;} - - // modifiers: - // - void assign (T* d, size_type s) {data_ = d; size_ = s;} - void clear () {data_ = nullptr; size_ = 0;} - void swap (vector_view& v) { - std::swap (data_, v.data_); std::swap (size_, v.size_);} - - private: - T* data_; - size_type size_; - }; - - //@@ TODO. - // - template bool operator== (vector_view l, vector_view r); - template bool operator!= (vector_view l, vector_view r); - template bool operator< (vector_view l, vector_view r); - template bool operator> (vector_view l, vector_view r); - template bool operator<= (vector_view l, vector_view r); - template bool operator>= (vector_view l, vector_view r); -} - -#endif // LIBBUTL_VECTOR_VIEW_HXX diff --git a/libbutl/vector-view.mxx b/libbutl/vector-view.mxx new file mode 100644 index 0000000..e2c9d7e --- /dev/null +++ b/libbutl/vector-view.mxx @@ -0,0 +1,134 @@ +// file : libbutl/vector-view.mxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules +#pragma once +#endif + +// C includes. + +#ifndef __cpp_lib_modules +#include +#include // size_t, ptrdiff_t +#include // swap() +#include // reverse_iterator +#include // out_of_range +#endif + +// Other includes. + +#ifdef __cpp_modules +export module butl.vector_view; +#ifdef __cpp_lib_modules +import std.core; +#endif +#endif + +#include + +LIBBUTL_MODEXPORT 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). + // + // @@ If T is const T1, could be useful to have a c-tor from vector. + // + template + class vector_view + { + public: + using value_type = T; + using pointer = T*; + using reference = T&; + using const_pointer = const T*; + using const_reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + // construct/copy/destroy: + // + vector_view (): data_ (nullptr), size_ (0) {} + vector_view (T* d, size_type s): data_ (d), size_ (s) {} + + template + vector_view (std::vector& v) + : data_ (v.data ()), size_ (v.size ()) {} + + template + vector_view (const std::vector& v) + : data_ (v.data ()), size_ (v.size ()) {} + + template + vector_view (const vector_view& v) + : data_ (v.data ()), size_ (v.size ()) {} + + vector_view (vector_view&&) = default; + vector_view (const vector_view&) = default; + vector_view& operator= (vector_view&&) = default; + vector_view& operator= (const vector_view&) = default; + + // iterators: + // + iterator begin() const {return data_;} + iterator end() const {return data_ + size_;} + + const_iterator cbegin() const {return data_;} + const_iterator cend() const {return data_ + size_;} + + reverse_iterator rbegin() const {return reverse_iterator (end ());} + reverse_iterator rend() const {return reverse_iterator (begin ());} + + const_reverse_iterator crbegin() const { + return const_reverse_iterator (cend ());} + const_reverse_iterator crend() const { + return const_reverse_iterator (cbegin ());} + + // capacity: + // + size_type size() const {return size_;} + bool empty() const {return size_ == 0;} + + // element access: + // + reference operator[](size_type n) const {return data_[n];} + reference front() const {return data_[0];} + reference back() const {return data_[size_ - 1];} + + reference at(size_type n) const + { + if (n >= size_) + throw std::out_of_range ("index out of range"); + return data_[n]; + } + + // data access: + // + T* data() const {return data_;} + + // modifiers: + // + void assign (T* d, size_type s) {data_ = d; size_ = s;} + void clear () {data_ = nullptr; size_ = 0;} + void swap (vector_view& v) { + std::swap (data_, v.data_); std::swap (size_, v.size_);} + + private: + T* data_; + size_type size_; + }; + + //@@ TODO. + // + template bool operator== (vector_view l, vector_view r); + template bool operator!= (vector_view l, vector_view r); + template bool operator< (vector_view l, vector_view r); + template bool operator> (vector_view l, vector_view r); + template bool operator<= (vector_view l, vector_view r); + template bool operator>= (vector_view l, vector_view r); +} diff --git a/libbutl/version.hxx.in b/libbutl/version.hxx.in index 780d21a..add5154 100644 --- a/libbutl/version.hxx.in +++ b/libbutl/version.hxx.in @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#ifndef LIBBUTL_VERSION // Note: using the version macro itself. +#pragma once // Note: using build2 standard versioning scheme. The numeric version format // is AAABBBCCCDDDE where: @@ -36,5 +36,3 @@ #define LIBBUTL_SNAPSHOT $libbutl.version.snapshot_sn$ULL #define LIBBUTL_SNAPSHOT_ID "$libbutl.version.snapshot_id$" - -#endif // LIBBUTL_VERSION diff --git a/libbutl/win32-utility.cxx b/libbutl/win32-utility.cxx index c16f8cc..9e7b936 100644 --- a/libbutl/win32-utility.cxx +++ b/libbutl/win32-utility.cxx @@ -9,7 +9,11 @@ // #ifdef _WIN32 +#ifndef __cpp_lib_modules #include // unique_ptr +#else +import std.core; +#endif using namespace std; diff --git a/libbutl/win32-utility.hxx b/libbutl/win32-utility.hxx index d9a3bcb..8c1e6d7 100644 --- a/libbutl/win32-utility.hxx +++ b/libbutl/win32-utility.hxx @@ -2,8 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#ifndef LIBBUTL_WIN32_UTILITY_HXX -#define LIBBUTL_WIN32_UTILITY_HXX +#pragma once // Use this header to include and a couple of Win32-specific // utilities. @@ -33,7 +32,11 @@ # endif #endif +#ifndef __cpp_lib_modules #include +#else +import std.core; +#endif #include @@ -50,5 +53,3 @@ namespace butl }; #endif // _WIN32 - -#endif // LIBBUTL_WIN32_UTILITY_HXX diff --git a/tests/base64/buildfile b/tests/base64/buildfile index 710499e..535b9dc 100644 --- a/tests/base64/buildfile +++ b/tests/base64/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/base64/driver.cxx b/tests/base64/driver.cxx index 040eaf8..fb65a49 100644 --- a/tests/base64/driver.cxx +++ b/tests/base64/driver.cxx @@ -2,11 +2,25 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include +#include #include -#include +#endif -#include +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.base64; +#else +#include +#endif using namespace std; using namespace butl; diff --git a/tests/build/root.build b/tests/build/root.build index 633ccb3..6c98ba9 100644 --- a/tests/build/root.build +++ b/tests/build/root.build @@ -2,6 +2,11 @@ # copyright : Copyright (c) 2014-2017 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file +using cxx.guess + +if ($force_modules != true && $cxx.id == 'clang') + cxx.features.modules = false + cxx.std = experimental using cxx diff --git a/tests/cpfile/buildfile b/tests/cpfile/buildfile index 0d95110..41275fa 100644 --- a/tests/cpfile/buildfile +++ b/tests/cpfile/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/cpfile/driver.cxx b/tests/cpfile/driver.cxx index 3a19322..db529ba 100644 --- a/tests/cpfile/driver.cxx +++ b/tests/cpfile/driver.cxx @@ -2,14 +2,29 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include #include -#include #include +#endif -#include -#include -#include +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.path; +import butl.fdstream; +import butl.filesystem; +#else +#include +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/curl/buildfile b/tests/curl/buildfile index 20617cf..dae375b 100644 --- a/tests/curl/buildfile +++ b/tests/curl/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/curl/driver.cxx b/tests/curl/driver.cxx index b5fc400..326d717 100644 --- a/tests/curl/driver.cxx +++ b/tests/curl/driver.cxx @@ -2,12 +2,35 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include #include - -#include -#include // operator<<(ostream, exception) -#include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +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 +#include +#include +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/dir-iterator/buildfile b/tests/dir-iterator/buildfile index e536536..7f83e4f 100644 --- a/tests/dir-iterator/buildfile +++ b/tests/dir-iterator/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/dir-iterator/driver.cxx b/tests/dir-iterator/driver.cxx index f584c68..1adef6f 100644 --- a/tests/dir-iterator/driver.cxx +++ b/tests/dir-iterator/driver.cxx @@ -2,14 +2,30 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // size_t #include + +#ifndef __cpp_lib_modules +#include // size_t #include +#endif + +// Other includes. -#include -#include -#include // operator<<(ostream, exception) -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.path; +import butl.path_io; +import butl.utility; +import butl.filesystem; +#else +#include +#include +#include // operator<<(ostream, exception) +#include +#endif using namespace std; using namespace butl; diff --git a/tests/fdstream/buildfile b/tests/fdstream/buildfile index 5440bc1..6028f3f 100644 --- a/tests/fdstream/buildfile +++ b/tests/fdstream/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/fdstream/driver.cxx b/tests/fdstream/driver.cxx index 42414bc..dd70cff 100644 --- a/tests/fdstream/driver.cxx +++ b/tests/fdstream/driver.cxx @@ -2,6 +2,9 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #ifndef _WIN32 # include # include // this_thread::sleep_for() @@ -11,18 +14,35 @@ #include #include #include -#include #include #include #include // move() #include #include +#endif + +// Other includes. -#include -#include -#include -#include -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#ifndef _WIN32 +//@@ MOD TODO import std.threading; +#endif +#endif +import butl.path; +import butl.process; +import butl.fdstream; +import butl.timestamp; +import butl.filesystem; +#else +#include +#include +#include +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/link/buildfile b/tests/link/buildfile index 9d4f6b5..17e3eac 100644 --- a/tests/link/buildfile +++ b/tests/link/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/link/driver.cxx b/tests/link/driver.cxx index a5e94bb..352cadd 100644 --- a/tests/link/driver.cxx +++ b/tests/link/driver.cxx @@ -2,14 +2,28 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include #include + +#ifndef __cpp_lib_modules +#include #include // pair #include +#endif -#include -#include -#include +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +#endif +import butl.path; +import butl.fdstream; +import butl.filesystem; +#else +#include +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/manifest-parser/buildfile b/tests/manifest-parser/buildfile index b57cac6..2a89e29 100644 --- a/tests/manifest-parser/buildfile +++ b/tests/manifest-parser/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/manifest-parser/driver.cxx b/tests/manifest-parser/driver.cxx index 037df96..76959e5 100644 --- a/tests/manifest-parser/driver.cxx +++ b/tests/manifest-parser/driver.cxx @@ -2,14 +2,27 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include #include #include // pair -#include #include #include - -#include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.manifest_parser; +#else +#include +#endif using namespace std; using namespace butl; diff --git a/tests/manifest-roundtrip/buildfile b/tests/manifest-roundtrip/buildfile index a2fb3ac..5cd296f 100644 --- a/tests/manifest-roundtrip/buildfile +++ b/tests/manifest-roundtrip/buildfile @@ -4,5 +4,11 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs exe{driver}: test.roundtrip = manifest diff --git a/tests/manifest-roundtrip/driver.cxx b/tests/manifest-roundtrip/driver.cxx index 2068409..1f0b311 100644 --- a/tests/manifest-roundtrip/driver.cxx +++ b/tests/manifest-roundtrip/driver.cxx @@ -3,12 +3,29 @@ // license : MIT; see accompanying LICENSE file #include + +#ifndef __cpp_lib_modules +#include #include +#endif + +// Other includes. -#include // operator<<(ostream, exception) -#include -#include -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +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 +#include +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/manifest-serializer/buildfile b/tests/manifest-serializer/buildfile index d13daab..df633ae 100644 --- a/tests/manifest-serializer/buildfile +++ b/tests/manifest-serializer/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/manifest-serializer/driver.cxx b/tests/manifest-serializer/driver.cxx index dad96bd..f373ecf 100644 --- a/tests/manifest-serializer/driver.cxx +++ b/tests/manifest-serializer/driver.cxx @@ -2,14 +2,27 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include #include #include // pair -#include #include #include - -#include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.manifest_serializer; +#else +#include +#endif using namespace std; using namespace butl; diff --git a/tests/mventry/buildfile b/tests/mventry/buildfile index b665b61..d9af3a2 100644 --- a/tests/mventry/buildfile +++ b/tests/mventry/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/mventry/driver.cxx b/tests/mventry/driver.cxx index 5639c2b..eab2ce3 100644 --- a/tests/mventry/driver.cxx +++ b/tests/mventry/driver.cxx @@ -2,12 +2,28 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include #include +#endif + +// Other includes. -#include -#include // operator<<(ostream, exception) -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.path; +import butl.utility; // operator<<(ostream, exception) +import butl.filesystem; +#else +#include +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/openssl/buildfile b/tests/openssl/buildfile index 7713f14..08b0878 100644 --- a/tests/openssl/buildfile +++ b/tests/openssl/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/openssl/driver.cxx b/tests/openssl/driver.cxx index b0e37ae..092b2ba 100644 --- a/tests/openssl/driver.cxx +++ b/tests/openssl/driver.cxx @@ -2,14 +2,36 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include #include #include #include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +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 -#include -#include // operator<<(ostream, exception) -#include +import butl.optional; // @@ MOD Clang should not be necessary. +import butl.small_vector; // @@ MOD Clang should not be necessary. +#else +#include +#include +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/pager/buildfile b/tests/pager/buildfile index 1c1e9f4..d7d0123 100644 --- a/tests/pager/buildfile +++ b/tests/pager/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/pager/driver.cxx b/tests/pager/driver.cxx index 87c811d..6842cef 100644 --- a/tests/pager/driver.cxx +++ b/tests/pager/driver.cxx @@ -2,15 +2,28 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include // ios_base::failure #include #include #include // move() -#include #include #include - -#include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.pager; +#else +#include +#endif using namespace std; using namespace butl; diff --git a/tests/path-entry/buildfile b/tests/path-entry/buildfile index 58e315c..e6515ae 100644 --- a/tests/path-entry/buildfile +++ b/tests/path-entry/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/path-entry/driver.cxx b/tests/path-entry/driver.cxx index 4f36f61..c69bf9c 100644 --- a/tests/path-entry/driver.cxx +++ b/tests/path-entry/driver.cxx @@ -2,11 +2,26 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include #include - -#include // operator<<(ostream, exception) -#include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.utility; // operator<<(ostream, exception) +import butl.filesystem; +#else +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/path/buildfile b/tests/path/buildfile index e7f19f2..aa39b2d 100644 --- a/tests/path/buildfile +++ b/tests/path/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/path/driver.cxx b/tests/path/driver.cxx index 4d859b3..c2919c0 100644 --- a/tests/path/driver.cxx +++ b/tests/path/driver.cxx @@ -3,11 +3,25 @@ // license : MIT; see accompanying LICENSE file #include + +#ifndef __cpp_lib_modules #include #include +#endif + +// Other includes. -#include -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.path; +import butl.path_io; +#else +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/prefix-map/buildfile b/tests/prefix-map/buildfile index 5c2dd69..140fda9 100644 --- a/tests/prefix-map/buildfile +++ b/tests/prefix-map/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/prefix-map/driver.cxx b/tests/prefix-map/driver.cxx index abf7e01..3e165b2 100644 --- a/tests/prefix-map/driver.cxx +++ b/tests/prefix-map/driver.cxx @@ -2,11 +2,24 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include #include -#include -#include +#ifndef __cpp_lib_modules +#include +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.prefix_map; +#else +#include +#endif using namespace std; using namespace butl; diff --git a/tests/process-run/buildfile b/tests/process-run/buildfile index 770357c..5200af7 100644 --- a/tests/process-run/buildfile +++ b/tests/process-run/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/process-run/driver.cxx b/tests/process-run/driver.cxx index 695fdb9..765bd7d 100644 --- a/tests/process-run/driver.cxx +++ b/tests/process-run/driver.cxx @@ -2,12 +2,31 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules +#include #include +#endif + +// Other includes. -#include -#include -#include -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +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 +#include +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/process/buildfile b/tests/process/buildfile index ddb4688..2ecc915 100644 --- a/tests/process/buildfile +++ b/tests/process/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx index 0338af2..01f2c30 100644 --- a/tests/process/driver.cxx +++ b/tests/process/driver.cxx @@ -4,17 +4,34 @@ #include // getenv(), setenv(), _putenv() +#include + +#ifndef __cpp_lib_modules #include #include #include -#include -#include #include // istreambuf_iterator, ostream_iterator #include // copy() +#include +#endif + +// Other includes. -#include -#include -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.path; +import butl.process; +import butl.optional; +import butl.fdstream; +#else +#include +#include +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/progress/buildfile b/tests/progress/buildfile index 19e85c0..d8cb6d6 100644 --- a/tests/progress/buildfile +++ b/tests/progress/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/progress/driver.cxx b/tests/progress/driver.cxx index 0ff68f0..c8c2080 100644 --- a/tests/progress/driver.cxx +++ b/tests/progress/driver.cxx @@ -4,20 +4,43 @@ #ifndef _WIN32 # include // write() - -# include // this_thread::sleep_for() #else # include - # include //_write() #endif +#include + +#ifndef __cpp_lib_modules +#include #include // size_t #include +#ifndef _WIN32 +# include // this_thread::sleep_for() +#endif +#endif -#include -#include // fdnull(), stderr_fd() -#include +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#ifndef _WIN32 +//@@ MOD TODO import std.threading; +#endif +#endif +import butl.process; +import butl.fdstream; +import butl.diagnostics; + +import butl.optional; // @@ MOD Clang should not be necessary. +import butl.small_vector; // @@ MOD Clang should not be necessary. +#else +#include +#include // fdnull(), stderr_fd() +#include +#endif using namespace std; using namespace butl; diff --git a/tests/regex/buildfile b/tests/regex/buildfile index baf4bca..ae7e2c3 100644 --- a/tests/regex/buildfile +++ b/tests/regex/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/regex/driver.cxx b/tests/regex/driver.cxx index 054eb31..d48b716 100644 --- a/tests/regex/driver.cxx +++ b/tests/regex/driver.cxx @@ -2,13 +2,27 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include #include + +#ifndef __cpp_lib_modules +#include #include #include +#endif + +// Other includes. -#include -#include // operator<<(ostream, exception) +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.regex; +import butl.utility; // operator<<(ostream, exception) +#else +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/sendmail/buildfile b/tests/sendmail/buildfile index a83cf40..a854e89 100644 --- a/tests/sendmail/buildfile +++ b/tests/sendmail/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/sendmail/driver.cxx b/tests/sendmail/driver.cxx index e48c6b4..f5c9227 100644 --- a/tests/sendmail/driver.cxx +++ b/tests/sendmail/driver.cxx @@ -2,12 +2,34 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include #include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +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; -#include -#include // operator<<(ostream, exception) -#include +import butl.optional; // @@ MOD Clang should not be necessary. +import butl.small_vector; // @@ MOD Clang should not be necessary. +#else +#include +#include +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/sha256/buildfile b/tests/sha256/buildfile index 627b78f..fb490d0 100644 --- a/tests/sha256/buildfile +++ b/tests/sha256/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/sha256/driver.cxx b/tests/sha256/driver.cxx index 2d2f963..191756c 100644 --- a/tests/sha256/driver.cxx +++ b/tests/sha256/driver.cxx @@ -1,12 +1,25 @@ -// file : tests/triplet/driver.cxx -*- C++ -*- +// file : tests/sha256/driver.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include #include + +#ifndef __cpp_lib_modules +#include #include +#endif + +// Other includes. -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.sha256; +#else +#include +#endif using namespace std; using namespace butl; diff --git a/tests/small-vector/buildfile b/tests/small-vector/buildfile index 33be9ad..16d61f8 100644 --- a/tests/small-vector/buildfile +++ b/tests/small-vector/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/small-vector/driver.cxx b/tests/small-vector/driver.cxx index c7cafd4..fe386a4 100644 --- a/tests/small-vector/driver.cxx +++ b/tests/small-vector/driver.cxx @@ -2,11 +2,25 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include #include -#include -#include +#ifndef __cpp_lib_modules +#include +#include +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.small_vector; +#else +#include +#endif using namespace std; using namespace butl; diff --git a/tests/standard-version/buildfile b/tests/standard-version/buildfile index 0583b62..eb30733 100644 --- a/tests/standard-version/buildfile +++ b/tests/standard-version/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/standard-version/driver.cxx b/tests/standard-version/driver.cxx index fa149ae..c6f9c11 100644 --- a/tests/standard-version/driver.cxx +++ b/tests/standard-version/driver.cxx @@ -2,14 +2,28 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include // ios::failbit, ios::badbit #include -#include #include #include // invalid_argument - -#include // operator<<(ostream,exception) -#include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.utility; // operator<<(ostream,exception) +import butl.standard_version; +#else +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/strcase/buildfile b/tests/strcase/buildfile index 71fec53..efe8442 100644 --- a/tests/strcase/buildfile +++ b/tests/strcase/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/strcase/driver.cxx b/tests/strcase/driver.cxx index f7f4bd1..285892d 100644 --- a/tests/strcase/driver.cxx +++ b/tests/strcase/driver.cxx @@ -2,10 +2,22 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include #include -#include +#ifndef __cpp_lib_modules +#include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +#endif +import butl.utility; +#else +#include +#endif using namespace std; using namespace butl; diff --git a/tests/string-parser/buildfile b/tests/string-parser/buildfile index 5969cd0..4613df1 100644 --- a/tests/string-parser/buildfile +++ b/tests/string-parser/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/string-parser/driver.cxx b/tests/string-parser/driver.cxx index 09443f2..f454a6f 100644 --- a/tests/string-parser/driver.cxx +++ b/tests/string-parser/driver.cxx @@ -2,17 +2,30 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // ios::failbit, ios::badbit -#include -#include #include + +#ifndef __cpp_lib_modules +#include +#include #include +#endif + +// Other includes. -#include // operator<<(ostream,exception) -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.utility; // operator<<(ostream,exception) +import butl.string_parser; +#else +#include +#include +#endif using namespace std; -using namespace butl; +using namespace butl::string_parser; // Usage: argv[0] [-l] [-u] [-p] // @@ -54,8 +67,7 @@ try string l; while (getline (cin, l)) { - vector> v ( - string_parser::parse_quoted_position (l, unquote)); + vector> v (parse_quoted_position (l, unquote)); if (!spl) { diff --git a/tests/tab-parser/buildfile b/tests/tab-parser/buildfile index 0583b62..eb30733 100644 --- a/tests/tab-parser/buildfile +++ b/tests/tab-parser/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/tab-parser/driver.cxx b/tests/tab-parser/driver.cxx index 238c12a..d5ff5a0 100644 --- a/tests/tab-parser/driver.cxx +++ b/tests/tab-parser/driver.cxx @@ -2,12 +2,26 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // ios::failbit, ios::badbit #include + +#ifndef __cpp_lib_modules +#include #include +#endif + +// Other includes. -#include // operator<<(ostream,exception) -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.utility; // operator<<(ostream,exception) +import butl.tab_parser; +#else +#include +#include +#endif using namespace std; using namespace butl; diff --git a/tests/target-triplet/buildfile b/tests/target-triplet/buildfile index b8ca712..7dec846 100644 --- a/tests/target-triplet/buildfile +++ b/tests/target-triplet/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/target-triplet/driver.cxx b/tests/target-triplet/driver.cxx index b446a6f..5e61a46 100644 --- a/tests/target-triplet/driver.cxx +++ b/tests/target-triplet/driver.cxx @@ -3,10 +3,24 @@ // license : MIT; see accompanying LICENSE file #include + +#ifndef __cpp_lib_modules +#include #include #include // invalid_argument - -#include +#endif + +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.target_triplet; +#else +#include +#endif using namespace std; using namespace butl; diff --git a/tests/timestamp/buildfile b/tests/timestamp/buildfile index 085daf0..c554fc3 100644 --- a/tests/timestamp/buildfile +++ b/tests/timestamp/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/timestamp/driver.cxx b/tests/timestamp/driver.cxx index 661deb2..ec4c49d 100644 --- a/tests/timestamp/driver.cxx +++ b/tests/timestamp/driver.cxx @@ -4,15 +4,28 @@ #include // tzset() (POSIX), _tzset() (Windows) +#include + +#ifndef __cpp_lib_modules #include #include #include -#include #include #include #include +#endif -#include +// Other includes. + +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.timestamp; +#else +#include +#endif using namespace std; using namespace butl; diff --git a/tests/wildcard/buildfile b/tests/wildcard/buildfile index fc8e94b..90e8b58 100644 --- a/tests/wildcard/buildfile +++ b/tests/wildcard/buildfile @@ -4,4 +4,10 @@ import libs = libbutl%lib{butl} +if ($cxx.features.modules && ($force_std_modules == true || $cxx.id != 'msvc')) +{ + import libs += libstd-modules%liba{std-modules} + cxx.poptions += -D__cpp_lib_modules +} + exe{driver}: {hxx cxx}{*} $libs test{testscript} diff --git a/tests/wildcard/driver.cxx b/tests/wildcard/driver.cxx index 502726f..d95d7cd 100644 --- a/tests/wildcard/driver.cxx +++ b/tests/wildcard/driver.cxx @@ -2,18 +2,34 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include + +#ifndef __cpp_lib_modules #include #include #include -#include -#include #include // sort() #include +#include +#endif + +// Other includes. -#include -#include // operator<<(ostream, exception) -#include -#include +#ifdef __cpp_modules +#ifdef __cpp_lib_modules +import std.core; +import std.io; +#endif +import butl.path; +import butl.utility; // operator<<(ostream, exception) +import butl.optional; +import butl.filesystem; +#else +#include +#include +#include +#include +#endif using namespace std; using namespace butl; -- cgit v1.1