diff --git a/.gitignore b/.gitignore index 1a8ca6e6..80df9688 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea .vs +CMakeSettings.json cmake-build-* _build diff --git a/.travis.yml b/.travis.yml index aac443a0..3121aa74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ matrix: apt: sources: ['ubuntu-toolchain-r-test'] packages: ['g++-7'] - env: COMPILER='g++-7' + env: [COMPILER='g++-7', BUILD_FILESYSTEM_PATH_FUNCTION='OFF'] - os: linux compiler: gcc @@ -42,12 +42,13 @@ matrix: apt: sources: ['ubuntu-toolchain-r-test'] packages: ['g++-8'] - env: COMPILER='g++-8' + env: [COMPILER='g++-8', BUILD_FILESYSTEM_PATH_FUNCTION='ON'] # 3/ OSX Clang Builds - os: osx osx_image: xcode10 compiler: clang + env: [BUILD_FILESYSTEM_PATH_FUNCTION='OFF'] install: - DEPS_DIR="${TRAVIS_BUILD_DIR}/deps_" @@ -68,7 +69,7 @@ before_script: script: - mkdir _build - cd _build - - cmake -DSkyr_BUILD_TESTS=ON -DSkyr_BUILD_DOCS=OFF -DSkyr_BUILD_EXAMPLES=OFF .. + - cmake -DSkyr_BUILD_TESTS=ON -DSkyr_BUILD_DOCS=OFF -DSkyr_BUILD_EXAMPLES=OFF -DSkyr_BUILD_FILESYSTEM_PATH_FUNCTIONS=$BUILD_FILESYSTEM_PATH_FUNCTIONS .. - make -j 8 - make test diff --git a/CMakeLists.txt b/CMakeLists.txt index fc7ebbe2..3399962d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ option(Skyr_BUILD_EXAMPLES "Build the URL examples." OFF) option(Skyr_FULL_WARNINGS "Build the library with all warnings turned on." ON) option(Skyr_WARNINGS_AS_ERRORS "Treat warnings as errors." ON) option(Skyr_USE_STATIC_CRT "Use static C Runtime library (/MT or MTd)." ON) +option(Skyr_BUILD_FILESYSTEM_PATH_FUNCTIONS "Build the filesystem path functions" ON) find_package(Threads REQUIRED) diff --git a/README.md b/README.md index 36be0429..646990bd 100644 --- a/README.md +++ b/README.md @@ -27,22 +27,22 @@ This library provides: This project requires the availability of a C++17 compliant compiler and standard library. -### Building with `CMake` and `Make` +### Building with `CMake` and `Ninja` From a terminal, execute the following sequence of commands: ```bash > mkdir _build > cd _build -> cmake .. -> make -j4 +> cmake .. -G "Ninja" +> ninja ``` -To run the tests, run `ctest` from the terminal while in the +To run the tests, run `ninja test` from the terminal while in the `_build` directory: ```bash -> ctest +> ninja test ``` ### Building with `CMake` and `Visual Studio 2017` @@ -63,7 +63,7 @@ These examples are based on the To build the examples, run `cmake` as follows: ```bash -> cmake .. -DSkyr_BUILD_EXAMPLES=ON +> cmake .. -G "Ninja" -DSkyr_BUILD_EXAMPLES=ON ``` ### Creating a URL without a base URL @@ -125,13 +125,13 @@ This gives the output: `https://example.org/%F0%9F%8F%B3%EF%B8%8F%E2%80%8D%F0%9F ## Installation -### Installing with `CMake` and `Make` +### Installing with `CMake` and `Ninja` ```bash -> cmake .. DCMAKE_INSTALL_PREFIX=$PREFIX -> make -j4 -> make test # optional -> make install +> cmake .. -G "Ninja" -DCMAKE_INSTALL_PREFIX=$PREFIX +> ninja +> ninja test # optional +> ninja install ``` Where `$PREFIX` is the location where you want to install the diff --git a/include/skyr/filesystem.hpp b/include/skyr/filesystem.hpp new file mode 100644 index 00000000..1a0d76c3 --- /dev/null +++ b/include/skyr/filesystem.hpp @@ -0,0 +1,29 @@ +// Copyright 2018 Glyn Matthews. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef SKYR_FILESYSTEM_PATH_HPP +#define SKYR_FILESYSTEM_PATH_HPP + +#include +#include +#include +#include + +namespace skyr { +namespace filesystem { +enum class path_errc { + invalid_path=1, + percent_decoding_error=2, +}; + +std::error_code make_error_code(path_errc error); + +expected from_path(const std::filesystem::path &path); + +expected to_path(const url &input); +} // namespace filesystem +} // namespace skyr + +#endif // SKYR_FILESYSTEM_PATH_HPP diff --git a/include/skyr/url.hpp b/include/skyr/url.hpp index 43099876..5360ae92 100644 --- a/include/skyr/url.hpp +++ b/include/skyr/url.hpp @@ -137,7 +137,7 @@ class url { /// \returns The underlying URL string string_type href() const; - /// + /// @{ /// \tparam Source The input string type /// \param input The input string /// \returns An error on failure to parse the new URL @@ -155,6 +155,9 @@ class url { return set_href(std::move(bytes.value())); } + expected set_href(string_type &&href); + /// @} + /// The URL string /// /// Equivalent to `skyr::serialize(url_).value()` @@ -168,6 +171,7 @@ class url { /// \returns The [URL protocol](https://url.spec.whatwg.org/#dom-url-protocol) string_type protocol() const; + /// @{ /// Sets the [URL protocol](https://url.spec.whatwg.org/#dom-url-protocol) /// /// \param protocol The new URL protocol @@ -186,9 +190,13 @@ class url { return set_protocol(std::move(bytes.value())); } + expected set_protocol(string_type &&protocol); + /// @} + /// \returns The [URL username](https://url.spec.whatwg.org/#dom-url-username) string_type username() const; + /// @{ /// Sets the [URL username](https://url.spec.whatwg.org/#dom-url-username) /// /// \param username The new username @@ -207,6 +215,9 @@ class url { return set_username(std::move(bytes.value())); } + expected set_username(string_type &&username); + /// @} + /// The [URL password](https://url.spec.whatwg.org/#dom-url-password) /// /// Equivalent to: `url_.password? url_.password.value() : string_type()` @@ -214,6 +225,7 @@ class url { /// \returns The URL password string_type password() const; + /// @{ /// Sets the [URL password](https://url.spec.whatwg.org/#dom-url-password) /// /// \param password The new password @@ -231,10 +243,14 @@ class url { } return set_password(std::move(bytes.value())); } + + expected set_password(string_type &&password); + /// @} /// \returns The [URL host](https://url.spec.whatwg.org/#dom-url-host) string_type host() const; + /// @{ /// Sets the [URL host](https://url.spec.whatwg.org/#dom-url-host) /// /// \param host The new URL host @@ -253,9 +269,13 @@ class url { return set_host(std::move(bytes.value())); } + expected set_host(string_type &&host); + /// @} + /// \returns The [URL hostname](https://url.spec.whatwg.org/#dom-url-hostname) string_type hostname() const; + /// @{ /// Sets the [URL hostname](https://url.spec.whatwg.org/#dom-url-hostname) /// /// \param hostname The new URL host name @@ -274,6 +294,9 @@ class url { return set_hostname(std::move(bytes.value())); } + expected set_hostname(string_type &&hostname); + /// @} + /// \returns The [URL port](https://url.spec.whatwg.org/#dom-url-port) string_type port() const; @@ -287,6 +310,7 @@ class url { std::strtoul(port_first, &port_last, 10)); } + /// @{ /// Sets the [URL port](https://url.spec.whatwg.org/#dom-url-port) /// /// \param port The new port @@ -296,11 +320,15 @@ class url { return set_port_impl(port); } + expected set_port(string_type &&port); + /// @} + /// Returns the [URL pathname](https://url.spec.whatwg.org/#dom-url-pathname) /// /// \returns The URL pathname string_type pathname() const; + /// @{ /// Sets the [URL pathname](https://url.spec.whatwg.org/#dom-url-pathname) /// /// \param pathname The new pathname @@ -319,11 +347,15 @@ class url { return set_pathname(std::move(bytes.value())); } + expected set_pathname(string_type &&pathname); + /// @} + /// Returns the [URL search string](https://url.spec.whatwg.org/#dom-url-search) /// /// \returns The URL search string string_type search() const; + /// @{ /// Sets the [URL search string](https://url.spec.whatwg.org/#dom-url-search) /// /// \param search The new search string @@ -342,6 +374,9 @@ class url { return set_search(std::move(bytes.value())); } + expected set_search(string_type &&search); + /// @} + /// \returns A reference to the search parameters url_search_parameters &search_parameters(); @@ -350,6 +385,7 @@ class url { /// \returns The URL hash string string_type hash() const; + /// @{ /// Sets the [URL hash string](https://url.spec.whatwg.org/#dom-url-hash) /// /// \param hash The new hash string @@ -368,6 +404,9 @@ class url { return set_hash(std::move(bytes.value())); } + expected set_hash(string_type &&hash); + /// @} + /// \returns A copy to the underlying `url_record` implementation. url_record record() const; @@ -441,16 +480,6 @@ class url { string_type &&input, optional base = nullopt); void update_record(url_record &&record); - expected set_href(string_type &&href); - expected set_protocol(string_type &&protocol); - expected set_username(string_type &&username); - expected set_password(string_type &&password); - expected set_host(string_type &&host); - expected set_hostname(string_type &&hostname); - expected set_port(string_type &&port); - expected set_pathname(string_type &&pathname); - expected set_search(string_type &&search); - expected set_hash(string_type &&hash); template expected set_port_impl( diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6869437f..2fe4148e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,11 @@ set(Skyr_SRCS ${CMAKE_SOURCE_DIR}/include/skyr/url_error.hpp ${CMAKE_SOURCE_DIR}/include/skyr/url_search_parameters.hpp) +if (Skyr_BUILD_FILESYSTEM_PATH_FUNCTIONS) + LIST(APPEND Skyr_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/filesystem.cpp) + LIST(APPEND Skyr_SRCS ${CMAKE_SOURCE_DIR}/include/skyr/filesystem.hpp) +endif() + add_library(skyr-url ${Skyr_SRCS}) target_link_libraries(skyr-url) if(${CMAKE_CXX_COMPILER_ID} MATCHES Clang) @@ -52,6 +57,12 @@ if(${CMAKE_CXX_COMPILER_ID} MATCHES Clang) endif() endif() +if(${CMAKE_CXX_COMPILER_ID} MATCHES GNU) + if (Skyr_BUILD_FILESYSTEM_PATH_FUNCTIONS) + target_link_libraries(skyr-url "stdc++fs") + endif() +endif() + if (${CMAKE_CXX_COMPILER_ID} MATCHES GNU OR ${CMAKE_CXX_COMPILER_ID} MATCHES Clang) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmissing-declarations") diff --git a/src/filesystem.cpp b/src/filesystem.cpp new file mode 100644 index 00000000..8f162919 --- /dev/null +++ b/src/filesystem.cpp @@ -0,0 +1,60 @@ +// Copyright 2018 Glyn Matthews. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "skyr/filesystem.hpp" +#include "skyr/percent_encode.hpp" + +namespace skyr { +namespace filesystem { +namespace { +class path_error_category : public std::error_category { + public: + const char *name() const noexcept override; + std::string message(int error) const noexcept override; +}; + +const char *path_error_category::name() const noexcept { + return "filesystem path"; +} + +std::string path_error_category::message(int error) const noexcept { + switch (static_cast(error)) { + case path_errc::invalid_path: + return "Invalid path"; + case path_errc::percent_decoding_error: + return "Percent decoding error"; + default: + return "(Unknown error)"; + } +} + +static const path_error_category category{}; +} // namespace + +std::error_code make_error_code(path_errc error) { + return std::error_code(static_cast(error), category); +} + +expected from_path(const std::filesystem::path &path) { + url result; + if (!result.set_protocol("file")) { + return make_unexpected(make_error_code(path_errc::invalid_path)); + } + if (!result.set_pathname(path.string())) { + return make_unexpected(make_error_code(path_errc::invalid_path)); + } + return result; +} + +expected to_path(const url &input) { + auto pathname = input.pathname(); + auto decoded = percent_decode(pathname); + if (!decoded) { + return make_unexpected(make_error_code(path_errc::percent_decoding_error)); + } + return std::filesystem::u8path(decoded.value()); +} +} // namespace filesystem +} // namespace skyr diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 03e07ea7..4dbb5b63 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,6 +24,10 @@ set( unicode_tests ) +if (Skyr_BUILD_FILESYSTEM_PATH_FUNCTIONS) + LIST(APPEND TESTS filesystem_path_tests) +endif() + foreach (test ${TESTS}) add_executable(${test} ${test}.cpp) add_dependencies(${test} skyr-url gtest_main) diff --git a/tests/filesystem_path_tests.cpp b/tests/filesystem_path_tests.cpp new file mode 100644 index 00000000..efa753df --- /dev/null +++ b/tests/filesystem_path_tests.cpp @@ -0,0 +1,32 @@ +// Copyright 2018 Glyn Matthews. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt of copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +TEST(filesystem_path_tests, empty_path) { + auto instance = skyr::url{}; + auto path = skyr::filesystem::to_path(instance); + ASSERT_TRUE(path); + EXPECT_TRUE(path.value().empty()); +} + +TEST(filesystem_path_tests, file_path) { + auto instance = skyr::url{"file:///path/to/file.txt"}; + auto path = skyr::filesystem::to_path(instance); + ASSERT_TRUE(path); + EXPECT_EQ(path.value().generic_string(), "/path/to/file.txt"); +} + +TEST(filesystem_path_tests, http_path) { + auto instance = skyr::url{"http://www.example.com/path/to/file.txt"}; + auto path = skyr::filesystem::to_path(instance); + ASSERT_TRUE(path); + EXPECT_EQ(path.value().generic_string(), "/path/to/file.txt"); +} + +// TODO: add test with percent encoding +// TODO: add test with unicode