peary/utils/dispatcher.hpp

Command dispatcher that takes function names and string arguments. More…

Namespaces

Name
peary
peary::utils
peary::utils::dispatcher_impl

Classes

Name
class peary::utils::Dispatcher Command dispatcher class.
struct peary::utils::dispatcher_impl::NativeInterfaceWrappper Wrapper for native interface functions that return a value.
struct peary::utils::dispatcher_impl::NativeInterfaceWrappper< void, Args… > Wrapper for native interface functions that do not return a value.

Detailed Description

Command dispatcher that takes function names and string arguments.

Copyright: Copyright (c) 2018 Moritz Kiehn. This software is distributed under the terms of the LGPL-3.0-only License, copied verbatim in the file “LICENSE.md”. SPDX-License-Identifier: MIT

Source code

  
#pragma once

#include <cassert>
#include <functional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "peary/utils/exceptions.hpp"

namespace peary::utils {

    class Dispatcher {
    public:
        using NativeInterface = std::function<std::string(const std::vector<std::string>&)>;

        void add(std::string name, NativeInterface func, std::size_t nargs);

        template <typename R, typename... Args> void add(std::string name, std::function<R(Args...)> func);

        template <typename R, typename... Args> void add(std::string name, R (*func)(Args...));

        template <typename T, typename R, typename... Args> void add(std::string name, R (T::*member_func)(Args...), T* t);

        std::string call(const std::string& name, const std::vector<std::string>& args);

        std::vector<std::pair<std::string, std::size_t>> commands() const;

    private:
        struct Command {
            // The function that implements the command
            NativeInterface func;
            // The number of arguments that the command expects
            std::size_t nargs;
        };

        std::unordered_map<std::string, Command> m_commands;
    };

    inline void Dispatcher::add(std::string name, Dispatcher::NativeInterface func, std::size_t nargs) {
        if(name.empty()) {
            throw InvalidArgumentError(__func__, "Can not register command with empty name");
        }
        if(m_commands.count(name) != 0u) {
            throw utils::InvalidCommandError(name, "Command with this name already exists");
        }
        m_commands[std::move(name)] = Command {std::move(func), nargs};
    }

    namespace dispatcher_impl {

        template <typename T> inline T str_decode(const std::string& str) {
            T tmp;
            std::istringstream is(str);
            is >> tmp;
            if(is.fail()) {
                std::string msg;
                msg += "Could not convert value '";
                msg += str;
                msg += "' to type '";
                msg += typeid(T).name();
                msg += "'";
                throw InvalidArgumentError(__func__, msg);
            }
            return tmp;
        }

        template <> inline std::string str_decode(const std::string& str) {
            return str;
        }

        template <typename T> inline std::string str_encode(const T& value) {
            std::ostringstream os;
            os << value;
            if(os.fail()) {
                std::string msg;
                msg += "Could not convert type '";
                msg += typeid(T).name();
                msg += "' to std::string";
                throw InvalidArgumentError(__func__, msg);
            }
            return os.str();
        }

        template <typename R, typename... Args> struct NativeInterfaceWrappper {
            std::function<R(Args...)> func;

            std::string operator()(const std::vector<std::string>& args) {
                return decode_and_call(args, std::index_sequence_for<Args...> {});
            }

            template <std::size_t... I>
            std::string decode_and_call(const std::vector<std::string>& args, std::index_sequence<I...>) {
                return str_encode(func(str_decode<typename std::decay<Args>::type>(args.at(I))...));
            }
        };

        template <typename... Args> struct NativeInterfaceWrappper<void, Args...> {
            std::function<void(Args...)> func;

            std::string operator()(const std::vector<std::string>& args) {
                return decode_and_call(args, std::index_sequence_for<Args...> {});
            }

            template <std::size_t... I>
            std::string decode_and_call(const std::vector<std::string>& args, std::index_sequence<I...>) {
                func(str_decode<typename std::decay<Args>::type>(args.at(I))...);
                return std::string();
            }
        };

        template <typename R, typename... Args>
        inline Dispatcher::NativeInterface make_native_interface(std::function<R(Args...)>&& function) {
            return NativeInterfaceWrappper<R, Args...> {std::move(function)};
        }

    } // namespace dispatcher_impl

    template <typename R, typename... Args> inline void Dispatcher::add(std::string name, std::function<R(Args...)> func) {
        m_commands[std::move(name)] = Command {dispatcher_impl::make_native_interface(std::move(func)), sizeof...(Args)};
    }

    template <typename R, typename... Args> inline void Dispatcher::add(std::string name, R (*func)(Args...)) {
        assert(func && "Function pointer must be non-null");
        add(std::move(name), std::function<R(Args...)>(func));
    }

    template <typename T, typename R, typename... Args>
    inline void Dispatcher::add(std::string name, R (T::*member_func)(Args...), T* t) {
        assert(member_func && "Member function pointer must be non-null");
        assert(t && "Object pointer must be non-null");
        add(std::move(name), std::function<R(Args...)>([=](Args... args) { return (t->*member_func)(args...); }));
    }

    inline std::string Dispatcher::call(const std::string& name, const std::vector<std::string>& args) {
        auto cmd = m_commands.find(name);
        if(cmd == m_commands.end()) {
            throw utils::InvalidCommandError(name, "Unknown command");
        }
        if(args.size() != cmd->second.nargs) {
            throw InvalidArgumentError(__func__,
                                       "Command '" + name + "' expects " + std::to_string(cmd->second.nargs) +
                                           " arguments but " + std::to_string(args.size()) + " given");
        }
        return cmd->second.func(args);
    }

    inline std::vector<std::pair<std::string, std::size_t>> Dispatcher::commands() const {
        std::vector<std::pair<std::string, std::size_t>> cmds;

        for(const auto& cmd : m_commands) {
            cmds.emplace_back(cmd.first, cmd.second.nargs);
        }
        return cmds;
    }

} // namespace peary::utils
  

Updated on 2025-11-14 at 11:31:23 +0100