diff --git a/xo-webutil/.github/workflows/main.yml b/xo-webutil/.github/workflows/main.yml new file mode 100644 index 00000000..8b3795c9 --- /dev/null +++ b/xo-webutil/.github/workflows/main.yml @@ -0,0 +1,117 @@ +name: build xo-callback + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Configure self (webutil) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (webutil) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Test self (webutil) + working-directory: ${{github.workspace}}/build_webutil + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} diff --git a/xo-webutil/.gitignore b/xo-webutil/.gitignore new file mode 100644 index 00000000..b1adb07b --- /dev/null +++ b/xo-webutil/.gitignore @@ -0,0 +1,6 @@ +# lsp keeps state here +.cache +# symlink -> build/compile_commands.json should be created manually +compile_commands.json +# typical build directories +.build* diff --git a/xo-webutil/CMakeLists.txt b/xo-webutil/CMakeLists.txt new file mode 100644 index 00000000..76c8795a --- /dev/null +++ b/xo-webutil/CMakeLists.txt @@ -0,0 +1,28 @@ +# xo-webutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(webutil VERSION 1.0) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# common c++ settings + +# PROJECT_CXX_FLAGS: bespoke for this project - usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/webutil) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/xo-webutil/README.md b/xo-webutil/README.md new file mode 100644 index 00000000..0d906d20 --- /dev/null +++ b/xo-webutil/README.md @@ -0,0 +1,33 @@ +# webutil library + +## Getting Started + +### build + install dependencies + +- xo-callback [github/Rconybea/xo-callback](https://github.com/Rconybea/xo-callback) + +### clone repo + +``` +$ git clone git@github.com:Rconybea/xo-webutil.git +``` + +### build and install + +``` +$ cd xo-webutil +$ BUILDDIR=build # for example +$ mkdir $BUILDDIR +$ cd $BUILDDIR +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make +$ make install +``` + +### LSP support + +``` +$ cd xo-webutil +$ ln -s $BUILDDIR/compile_commands.json +``` diff --git a/xo-webutil/cmake/webutilConfig.cmake.in b/xo-webutil/cmake/webutilConfig.cmake.in new file mode 100644 index 00000000..79a9df3d --- /dev/null +++ b/xo-webutil/cmake/webutilConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(callback) +include("${CMAKE_CURRENT_LIST_DIR}/webutilTargets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/xo-webutil/cmake/xo-bootstrap-macros.cmake b/xo-webutil/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/xo-webutil/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/xo-webutil/include/xo/webutil/Alist.hpp b/xo-webutil/include/xo/webutil/Alist.hpp new file mode 100644 index 00000000..4bc5a305 --- /dev/null +++ b/xo-webutil/include/xo/webutil/Alist.hpp @@ -0,0 +1,33 @@ +/* file Alist.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include +#include +#include + +namespace xo { + namespace web { + /* assocation list, maps strings to strings + * use this for arguments to dynamic-endpoint-callbacks + */ + class Alist { + public: + Alist() = default; + + /* lookup association by name */ + std::string_view lookup(std::string n) const; + + void push_back(std::string n, std::string v); + + private: + std::vector> assoc_v_; + }; /*Alist*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end Alist.hpp */ diff --git a/xo-webutil/include/xo/webutil/HttpEndpointDescr.hpp b/xo-webutil/include/xo/webutil/HttpEndpointDescr.hpp new file mode 100644 index 00000000..f3598cbb --- /dev/null +++ b/xo-webutil/include/xo/webutil/HttpEndpointDescr.hpp @@ -0,0 +1,68 @@ +/* file EndpointDescr.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "Alist.hpp" +#include "xo/refcnt/Refcounted.hpp" +#include +#include + +namespace xo { + namespace web { + /* a function that can deliver http content on demand. */ + using HttpEndpointFn = std::function; + + /* describes an http endpoint -- + * this comprises: + * - a uri pattern. + * - a function that can deliver http content on demand + */ + class HttpEndpointDescr { + public: + HttpEndpointDescr(std::string uri_pattern, + HttpEndpointFn endpoint_fn); + + std::string const & uri_pattern() const { return uri_pattern_; } + HttpEndpointFn const & endpoint_fn() const { return endpoint_fn_; } + + void display(std::ostream & os) const; + + std::string display_string() const; + + private: + /* unique pattern in URI-space for this endpoint. + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that can construct http output on demand + * .endpoint_fn(uri, alist, &os) + * writes http output to os. output is parameterized + * by name-value pairs in alist, and is prepared on behalf + * of .uri_pattern + * alist will report name-value pairs for each variable that + * appears in .uri_pattern (surrounded by ${..}) + */ + HttpEndpointFn endpoint_fn_; + }; /*HttpEndpointDescr*/ + + inline std::ostream & + operator<<(std::ostream & os, HttpEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end EndpointDescr.hpp */ diff --git a/xo-webutil/include/xo/webutil/StreamEndpointDescr.hpp b/xo-webutil/include/xo/webutil/StreamEndpointDescr.hpp new file mode 100644 index 00000000..93a2e77b --- /dev/null +++ b/xo-webutil/include/xo/webutil/StreamEndpointDescr.hpp @@ -0,0 +1,69 @@ +/* file StreamEndpointDescr.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "Alist.hpp" +#include "xo/refcnt/Refcounted.hpp" +#include "xo/callback/CallbackSet.hpp" +#include + +namespace xo { + namespace reactor { class AbstractSink; } + + namespace web { + /* a function that creates an event subscription */ + using StreamSubscribeFn = std::function const & ws_sink)>; + using StreamUnsubscribeFn = std::function; + + /* describes a stream endpoint + * this comprises + * - a uri pattern (matches stream name) + * - a function that establishes subscription + * (by attaching supplied WebsocketSink to an event source) + */ + class StreamEndpointDescr { + public: + StreamEndpointDescr(std::string uri_pattern, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn); + + std::string const & uri_pattern() const { return uri_pattern_; } + StreamSubscribeFn const & subscribe_fn() const { return subscribe_fn_; } + StreamUnsubscribeFn const & unsubscribe_fn() const { return unsubscribe_fn_; } + + void display(std::ostream & os) const; + + std::string display_string() const; + + private: + /* unique pattern in URI-space for this endpoint + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that subscribes to an event stream + * (by attaching a websocket sink) + */ + StreamSubscribeFn subscribe_fn_; + /* reverses effect of a particular call to .subscribe_fn */ + StreamUnsubscribeFn unsubscribe_fn_; + }; /*StreamEndpointDescr*/ + + inline std::ostream & + operator<<(std::ostream & os, StreamEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end StreamEndpointDescr.hpp */ diff --git a/xo-webutil/src/webutil/Alist.cpp b/xo-webutil/src/webutil/Alist.cpp new file mode 100644 index 00000000..73905106 --- /dev/null +++ b/xo-webutil/src/webutil/Alist.cpp @@ -0,0 +1,28 @@ +/* @file Alist.cpp */ + +#include "Alist.hpp" + +namespace xo { + namespace web { + /* lookup association by name */ + std::string_view + Alist::lookup(std::string n) const { + for (auto const & ix : this->assoc_v_) { + if (ix.first == n) { + return ix.second; + } + } + + return ""; + } /*lookup*/ + + void + Alist::push_back(std::string n, std::string v) { + this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); + } + } /*namespace web*/ +} /*namespace xo*/ + + + +/* end Alist.cpp */ diff --git a/xo-webutil/src/webutil/CMakeLists.txt b/xo-webutil/src/webutil/CMakeLists.txt new file mode 100644 index 00000000..54fe2a88 --- /dev/null +++ b/xo-webutil/src/webutil/CMakeLists.txt @@ -0,0 +1,15 @@ +# webutil/CMakeLists.txt + +set(SELF_LIB webutil) +set(SELF_SRCS StreamEndpointDescr.cpp HttpEndpointDescr.cpp Alist.cpp) + +# reminder: can't be header-only library, because depends on non-header-only callback (bc of non-header-only refcnt) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +xo_dependency(${SELF_LIB} refcnt) +xo_dependency(${SELF_LIB} callback) + +# end CMakeLists.txt diff --git a/xo-webutil/src/webutil/HttpEndpointDescr.cpp b/xo-webutil/src/webutil/HttpEndpointDescr.cpp new file mode 100644 index 00000000..fb9ab261 --- /dev/null +++ b/xo-webutil/src/webutil/HttpEndpointDescr.cpp @@ -0,0 +1,27 @@ +/* @file HttpEndpointDescr.cpp */ + +#include "HttpEndpointDescr.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/tostr.hpp" + +namespace xo { + namespace web { + HttpEndpointDescr::HttpEndpointDescr(std::string uri_pattern, + HttpEndpointFn endpoint_fn) + : uri_pattern_{std::move(uri_pattern)}, + endpoint_fn_{std::move(endpoint_fn)} + {} + + void + HttpEndpointDescr::display(std::ostream & os) const { + os << ""; + } /*display*/ + + std::string + HttpEndpointDescr::display_string() const { return tostr(*this); } + } /*namespace web*/ + +} /*namespace xo*/ + + +/* end HttpEndpointDescr.cpp */ diff --git a/xo-webutil/src/webutil/StreamEndpointDescr.cpp b/xo-webutil/src/webutil/StreamEndpointDescr.cpp new file mode 100644 index 00000000..03c2e961 --- /dev/null +++ b/xo-webutil/src/webutil/StreamEndpointDescr.cpp @@ -0,0 +1,28 @@ +/* @file StreamEndpointDescr.cpp */ + +#include "StreamEndpointDescr.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/tostr.hpp" + +namespace xo { + namespace web { + StreamEndpointDescr::StreamEndpointDescr(std::string uri_pattern, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn) + : uri_pattern_{std::move(uri_pattern)}, + subscribe_fn_{std::move(subscribe_fn)}, + unsubscribe_fn_{std::move(unsubscribe_fn)} + {} + + void + StreamEndpointDescr::display(std::ostream & os) const { + os << ""; + } /*display*/ + + std::string + StreamEndpointDescr::display_string() const { return tostr(*this); } + } /*namespace web*/ +} /*namespace xo*/ + + +/* end StreamEndpointDescr.cpp */