Commit 89115514 authored by Dominik Charousset's avatar Dominik Charousset

Add `message::filter` function & CLI utility

parent ac19cd13
......@@ -21,50 +21,26 @@
#include <QApplication>
#include "caf/all.hpp"
#include "cppa/opt.hpp"
#include "ui_chatwindow.h" // auto generated from chatwindow.ui
using namespace std;
using namespace caf;
/*
namespace {
struct line { string str; };
istream& operator>>(istream& is, line& l) {
getline(is, l.str);
return is;
}
any_tuple s_last_line;
any_tuple split_line(const line& l) {
vector<string> result;
stringstream strs(l.str);
string tmp;
while (getline(strs, tmp, ' ')) {
if (!tmp.empty()) result.push_back(std::move(tmp));
}
s_last_line = any_tuple::view(std::move(result));
return s_last_line;
}
} // namespace <anonymous>
*/
int main(int argc, char** argv) {
string name;
string group_id;
options_description desc;
bool args_valid = match_stream<string>(argv + 1, argv + argc) (
on_opt1('n', "name", &desc, "set name") >> rd_arg(name),
on_opt1('g', "group", &desc, "join group <arg1>") >> rd_arg(group_id),
on_opt0('h', "help", &desc, "print help") >> print_desc_and_exit(&desc)
);
auto res = message_builder(argv + 1, argv + argc).filter_cli({
{"name,n", "set chat name", name},
{"group,g", "join chat group", group_id}
});
if (!res.remainder.empty()) {
std::cerr << res.helptext << std::endl;
return 1;
}
if (res.opts.count("help") > 0) {
return 0;
}
group gptr;
// evaluate group parameter
if (!group_id.empty()) {
......@@ -72,13 +48,10 @@ int main(int argc, char** argv) {
if (p == std::string::npos) {
cerr << "*** error parsing argument " << group_id
<< ", expected format: <module_name>:<group_id>";
}
else {
} else {
try {
gptr = group::get(group_id.substr(0, p),
group_id.substr(p + 1));
}
catch (exception& e) {
gptr = group::get(group_id.substr(0, p), group_id.substr(p + 1));
} catch (exception& e) {
ostringstream err;
cerr << "*** exception: group::get(\"" << group_id.substr(0, p)
<< "\", \"" << group_id.substr(p + 1) << "\") failed; "
......@@ -86,9 +59,6 @@ int main(int argc, char** argv) {
}
}
}
if (!args_valid) print_desc_and_exit(&desc)();
QApplication app(argc, argv);
app.setQuitOnLastWindowClosed(true);
QMainWindow mw;
......@@ -102,8 +72,8 @@ int main(int argc, char** argv) {
send_as(client, client, atom("join"), gptr);
}
mw.show();
auto res = app.exec();
shutdown();
auto app_res = app.exec();
await_all_actors_done();
return res;
shutdown();
return app_res;
}
......@@ -21,9 +21,6 @@
#include "caf/all.hpp"
#include "caf/io/all.hpp"
// the options_description API is deprecated
#include "cppa/opt.hpp"
using namespace std;
using namespace caf;
......@@ -183,41 +180,40 @@ void client_repl(const string& host, uint16_t port) {
}
int main(int argc, char** argv) {
string mode;
string host;
uint16_t port = 0;
options_description desc;
auto set_mode = [&](const string& arg) -> function<bool()> {
return [arg, &mode]() -> bool {
if (!mode.empty()) {
cerr << "mode already set to " << mode << endl;
return false;
}
mode = move(arg);
return true;
};
};
string copts = "client options";
string sopts = "server options";
bool args_valid = match_stream<string> (argv + 1, argv + argc) (
on_opt1('p', "port", &desc, "set port") >> rd_arg(port),
on_opt1('H', "host", &desc, "set host (default: localhost)", copts) >> rd_arg(host),
on_opt0('s', "server", &desc, "run in server mode", sopts) >> set_mode("server"),
on_opt0('c', "client", &desc, "run in client mode", copts) >> set_mode("client"),
on_opt0('h', "help", &desc, "print help") >> print_desc_and_exit(&desc)
);
if (!args_valid || port == 0 || mode.empty()) {
if (port == 0) cerr << "*** no port specified" << endl;
if (mode.empty()) cerr << "*** no mode specified" << endl;
cerr << endl;
auto description_printer = print_desc(&desc, cerr);
description_printer();
return -1;
}
if (mode == "server") {
string host = "localhost";
auto res = message_builder(argv + 1, argv + argc).to_message().filter_cli({
{"port,p", "set port (either to publish at or to connect to)", port},
{"host,H", "set host (client mode only, default: localhost)", host},
{"server,s", "run in server mode"},
{"client,c", "run in client mode"}
});
if (res.opts.count("help") > 0) {
return 0;
}
if (!res.remainder.empty()) {
cerr << "*** invalid command line options" << endl << res.helptext << endl;
return 1;
}
bool is_server = res.opts.count("server") > 0;
if (is_server == (res.opts.count("client") > 0)) {
if (is_server) {
cerr << "*** cannot be server and client at the same time" << endl;
} else {
cerr << "*** either --server or --client option must be set" << endl;
}
return 1;
}
if (!is_server && port == 0) {
cerr << "*** no port to server specified" << endl;
return 1;
}
if (is_server) {
try {
// try to publish math actor at given port
io::publish(spawn(calculator), port);
cout << "*** try publish at port " << port << endl;
auto p = io::publish(spawn(calculator), port);
cout << "*** server successfully published at port " << p << endl;
}
catch (exception& e) {
cerr << "*** unable to publish math actor at port " << port << "\n"
......@@ -226,10 +222,8 @@ int main(int argc, char** argv) {
}
}
else {
if (host.empty()) host = "localhost";
client_repl(host, port);
}
await_all_actors_done();
shutdown();
return 0;
}
......@@ -18,9 +18,6 @@
#include "caf/all.hpp"
#include "caf/io/all.hpp"
// the options_description API is deprecated
#include "cppa/opt.hpp"
using namespace std;
using namespace caf;
......@@ -80,14 +77,16 @@ void client(event_based_actor* self, const string& name) {
int main(int argc, char** argv) {
string name;
string group_id;
options_description desc;
bool args_valid = match_stream<string>(argv + 1, argv + argc) (
on_opt1('n', "name", &desc, "set name") >> rd_arg(name),
on_opt1('g', "group", &desc, "join group <arg1>") >> rd_arg(group_id),
on_opt0('h', "help", &desc, "print help") >> print_desc_and_exit(&desc)
);
if (!args_valid) {
print_desc_and_exit(&desc)();
auto res = message_builder(argv + 1, argv + argc).filter_cli({
{"name,n", "set name", name},
{"group,g", "join group", group_id}
});
if (!res.remainder.empty()) {
std::cout << res.helptext << std::endl;
return 1;
}
if (res.opts.count("help") > 0) {
return 0;
}
while (name.empty()) {
cout << "please enter your name: " << flush;
......
......@@ -20,10 +20,13 @@
#ifndef CAF_MESSAGE_HPP
#define CAF_MESSAGE_HPP
#include <tuple>
#include <type_traits>
#include "caf/atom.hpp"
#include "caf/config.hpp"
#include "caf/from_string.hpp"
#include "caf/skip_message.hpp"
#include "caf/detail/int_list.hpp"
#include "caf/detail/comparable.hpp"
......@@ -93,29 +96,34 @@ class message {
}
/**
* Creates a new tuple with all but the first n values.
* Creates a new message with all but the first n values.
*/
message drop(size_t n) const;
/**
* Creates a new tuple with all but the last n values.
* Creates a new message with all but the last n values.
*/
message drop_right(size_t n) const;
/**
* Creates a new tuple from the first n values.
* Creates a new message from the first n values.
*/
inline message take(size_t n) const {
return n >= size() ? *this : drop_right(size() - n);
}
/**
* Creates a new tuple from the last n values.
* Creates a new message from the last n values.
*/
inline message take_right(size_t n) const {
return n >= size() ? *this : drop(size() - n);
}
/**
* Creates a new message of size `n` starting at element `pos`.
*/
message slice(size_t pos, size_t n) const;
/**
* Gets a mutable pointer to the element at position @p p.
*/
......@@ -199,21 +207,91 @@ class message {
}
/**
* Checks whether this tuple is dynamically typed, i.e.,
* Checks whether this message is dynamically typed, i.e.,
* its types were not known at compile time.
*/
bool dynamically_typed() const;
/**
* Applies @p handler to this message and returns the result
* Applies `handler` to this message and returns the result
* of `handler(*this)`.
*/
optional<message> apply(message_handler handler);
/**
* Returns a new message consisting of all elements that were *not*
* matched by `handler`.
*/
message filter(message_handler handler) const;
/**
* Stores the name of a command line option ("<long name>[,<short name>]")
* along with a description and a callback.
*/
struct cli_arg {
std::string name;
std::string text;
std::function<bool (const std::string&)> fun;
inline cli_arg(std::string nstr, std::string tstr)
: name(std::move(nstr)),
text(std::move(tstr)) {
// nop
}
inline cli_arg(std::string nstr, std::string tstr, std::string& arg)
: name(std::move(nstr)),
text(std::move(tstr)) {
fun = [&arg](const std::string& str) -> bool{
arg = str;
return true;
};
}
inline cli_arg(std::string nstr, std::string tstr, std::vector<std::string>& arg)
: name(std::move(nstr)),
text(std::move(tstr)) {
fun = [&arg](const std::string& str) -> bool {
arg.push_back(str);
return true;
};
}
template <class T>
cli_arg(std::string nstr, std::string tstr, T& arg)
: name(std::move(nstr)),
text(std::move(tstr)) {
fun = [&arg](const std::string& str) -> bool {
auto res = from_string<T>(str);
if (!res) {
return false;
}
arg = *res;
return true;
};
}
template <class T>
cli_arg(std::string nstr, std::string tstr, std::vector<T>& arg)
: name(std::move(nstr)),
text(std::move(tstr)) {
fun = [&arg](const std::string& str) -> bool {
auto res = from_string<T>(str);
if (!res) {
return false;
}
arg.push_back(*res);
return true;
};
}
};
struct cli_res;
/**
* A simplistic interface for using `filter` to parse command line options.
*/
cli_res filter_cli(std::vector<cli_arg> args) const;
/** @cond PRIVATE */
inline uint32_t type_token() const {
return m_vals->type_token();
return m_vals ? m_vals->type_token() : 0xFFFFFFFF;
}
inline void force_detach() {
......@@ -224,7 +302,7 @@ class message {
explicit message(raw_ptr);
inline const std::string* tuple_type_names() const {
inline std::string tuple_type_names() const {
return m_vals->tuple_type_names();
}
......@@ -246,9 +324,26 @@ class message {
/** @endcond */
private:
message filter_impl(size_t start, message_handler handler) const;
data_ptr m_vals;
};
/**
* Stores the result of `message::filter_cli`.
*/
struct message::cli_res {
/**
* Stores the remaining (unmatched) arguments.
*/
message remainder;
/**
* Stores the names of all active options.
*/
std::set<std::string> opts;
/**
* Stores the automatically generated help text.
*/
std::string helptext;
};
/**
......
......@@ -90,6 +90,14 @@ class message_builder {
*/
message to_message();
inline message filter(message_handler f) {
return to_message().filter(f);
}
inline message::cli_res filter_cli(std::vector<message::cli_arg> args) {
return to_message().filter_cli(std::move(args));
}
/**
* Convenience function for `to_message().apply(handler).
*/
......
......@@ -18,7 +18,11 @@
******************************************************************************/
#include "caf/message.hpp"
#include <iostream>
#include "caf/message_handler.hpp"
#include "caf/string_algorithms.hpp"
#include "caf/detail/singletons.hpp"
#include "caf/detail/decorated_tuple.hpp"
......@@ -90,8 +94,17 @@ message message::drop_right(size_t n) const {
return message{};
}
std::vector<size_t> mapping(size() - n);
size_t i = 0;
std::generate(mapping.begin(), mapping.end(), [&] { return i++; });
std::iota(mapping.begin(), mapping.end(), size_t{0});
return message{detail::decorated_tuple::create(m_vals, std::move(mapping))};
}
message message::slice(size_t pos, size_t n) const {
auto s = size();
if (pos >= s) {
return message{};
}
std::vector<size_t> mapping(std::min(s - pos, n));
std::iota(mapping.begin(), mapping.end(), pos);
return message{detail::decorated_tuple::create(m_vals, std::move(mapping))};
}
......@@ -103,11 +116,160 @@ message::const_iterator message::begin() const {
return m_vals ? m_vals->begin() : const_iterator{nullptr, 0};
}
/**
* Returns an iterator to the end.
*/
message::const_iterator message::end() const {
return m_vals ? m_vals->end() : const_iterator{nullptr, 0};
}
message message::filter_impl(size_t start, message_handler handler) const {
auto s = size();
for (size_t i = start; i < s; ++i) {
for (size_t n = (s - i) ; n > 0; --n) {
auto res = handler(slice(i, n));
if (res) {
std::vector<size_t> mapping(s);
std::iota(mapping.begin(), mapping.end(), size_t{0});
auto first = mapping.begin() + static_cast<ptrdiff_t>(i);
auto last = first + static_cast<ptrdiff_t>(n);
mapping.erase(first, last);
if (mapping.empty()) {
return message{};
}
message next{detail::decorated_tuple::create(m_vals,
std::move(mapping))};
return next.filter_impl(i, handler);
}
}
}
return *this;
}
message message::filter(message_handler handler) const {
return filter_impl(0, handler);
}
message::cli_res message::filter_cli(std::vector<cli_arg> cliargs) const {
std::set<std::string> opts;
cli_arg dummy{"help,h", ""};
std::map<std::string, cli_arg*> shorts;
std::map<std::string, cli_arg*> longs;
shorts["-h"] = &dummy;
shorts["-?"] = &dummy;
longs["--help"] = &dummy;
for (auto& cliarg : cliargs) {
std::vector<std::string> s;
split(s, cliarg.name, is_any_of(","), token_compress_on);
if (s.size() == 2 && s.back().size() == 1) {
longs["--" + s.front()] = &cliarg;
shorts["-" + s.back()] = &cliarg;
} else if (s.size() == 1) {
longs[s.front()] = &cliarg;
} else {
throw std::invalid_argument("invalid option name: " + cliarg.name);
}
}
auto insert_opt_name = [&](const cli_arg* ptr) {
auto separator = ptr->name.find(',');
if (separator == std::string::npos) {
opts.insert(ptr->name);
} else {
opts.insert(ptr->name.substr(0, separator));
}
};
auto res = filter({
[&](const std::string& arg) -> optional<skip_message_t> {
if (arg.empty() || arg.front() != '-') {
return skip_message();
}
auto i = shorts.find(arg);
if (i != shorts.end()) {
if (i->second->fun) {
return skip_message();
}
insert_opt_name(i->second);
return none;
}
auto eq_pos = arg.find('=');
auto j = longs.find(arg.substr(0, eq_pos));
if (j != longs.end()) {
if (j->second->fun) {
if (eq_pos == std::string::npos) {
std::cerr << "missing argument to " << arg << std::endl;
return none;
}
if (!j->second->fun(arg.substr(eq_pos + 1))) {
std::cerr << "invalid value for option "
<< j->second->name << ": " << arg << std::endl;
return none;
}
insert_opt_name(j->second);
return none;
}
insert_opt_name(j->second);
return none;
}
std::cerr << "unknown command line option: " << arg << std::endl;
return none;
},
[&](const std::string& arg1,
const std::string& arg2) -> optional<skip_message_t> {
if (arg1.size() < 2 || arg1[0] != '-' || arg1[1] == '-') {
return skip_message();
}
auto i = shorts.find(arg1);
if (i != shorts.end() && i->second->fun) {
if (!i->second->fun(arg2)) {
std::cerr << "invalid value for option "
<< i->second->name << ": " << arg2 << std::endl;
return none;
}
insert_opt_name(i->second);
return none;
}
std::cerr << "unknown command line option: " << arg1 << std::endl;
return none;
}
});
size_t name_width = 0;
for (auto& ca : cliargs) {
// name field contains either only "--<long_name>" or
// "-<short name> [--<long name>]" depending on whether or not
// a ',' appears in the name
auto nw = ca.name.find(',') == std::string::npos
? ca.name.size() + 2 // "--<name>"
: ca.name.size() + 5; // "-X [--<name>]" (minus trailing ",X")
if (ca.fun) {
nw += 4; // trailing " arg"
}
name_width = std::max(name_width, nw);
}
std::ostringstream oss;
oss << std::left;
oss << "Allowed options:" << std::endl;
for (auto& ca : cliargs) {
std::string lhs;
auto separator = ca.name.find(',');
if (separator == std::string::npos) {
lhs += "--";
lhs += ca.name;
} else {
lhs += "-";
lhs += ca.name.back();
lhs += " [--";
lhs += ca.name.substr(0, separator);
lhs += "]";
}
if (ca.fun) {
lhs += " arg";
}
oss << " ";
oss.width(static_cast<std::streamsize>(name_width));
oss << lhs << " : " << ca.text << std::endl;
}
auto helptext = oss.str();
if (opts.count("help") == 1) {
std::cout << helptext << std::endl;
}
return {res, std::move(opts), std::move(helptext)};
}
} // namespace caf
......@@ -90,10 +90,6 @@ class message_builder::dynamic_msg_data : public detail::message_data {
return m_type_token;
}
const std::string* tuple_type_names() const override {
return nullptr; // get_tuple_type_names(*this);
}
std::vector<uniform_value> m_elements;
uint32_t m_type_token;
};
......
......@@ -21,6 +21,7 @@ add_unit_test(atom)
add_unit_test(metaprogramming)
add_unit_test(intrusive_containers)
add_unit_test(match)
add_unit_test(message)
add_unit_test(serialization)
add_unit_test(uniform_type)
add_unit_test(fixed_vector)
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright (C) 2011 - 2014 *
* Dominik Charousset <dominik.charousset (at) haw-hamburg.de> *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "test.hpp"
#include "caf/message.hpp"
using std::cout;
using std::endl;
using namespace caf;
void test_drop() {
auto m1 = make_message(1, 2, 3, 4, 5);
std::vector<message> messages{
m1,
make_message(2, 3, 4, 5),
make_message(3, 4, 5),
make_message(4, 5),
make_message(5),
message{}
};
for (size_t i = 0; i < messages.size(); ++i) {
CAF_CHECK_EQUAL(to_string(m1.drop(i)), to_string(messages[i]));
}
}
void test_slice() {
auto m1 = make_message(1, 2, 3, 4, 5);
auto m2 = m1.slice(2, 2);
CAF_CHECK_EQUAL(to_string(m2), to_string(make_message(3, 4)));
}
void test_filter1(int lhs1, int lhs2, int lhs3, int rhs1, int rhs2, int val) {
auto m1 = make_message(lhs1, lhs2, lhs3);
auto m2 = make_message(rhs1, rhs2);
auto m3 = m1.filter(on(val) >> [] {});
CAF_CHECK_EQUAL(to_string(m2), to_string(m3));
}
void test_filter2() {
auto m1 = make_message(1.0, 2.0, 3.0);
auto m2 = make_message(1, 2, 1.0, 2.0, 3.0);
auto m3 = make_message(1.0, 1, 2, 2.0, 3.0);
auto m4 = make_message(1.0, 2.0, 1, 2, 3.0);
auto m5 = make_message(1.0, 2.0, 3.0, 1, 2);
auto m6 = make_message(1, 2, 1.0, 2.0, 3.0, 1, 2);
auto m7 = make_message(1.0, 1, 2, 3, 4, 2.0, 3.0);
message_handler f{
[](int, int) { },
[](float, float) { }
};
CAF_CHECK_EQUAL(to_string(m2.filter(f)), to_string(m1));
CAF_CHECK_EQUAL(to_string(m3.filter(f)), to_string(m1));
CAF_CHECK_EQUAL(to_string(m4.filter(f)), to_string(m1));
CAF_CHECK_EQUAL(to_string(m5.filter(f)), to_string(m1));
CAF_CHECK_EQUAL(to_string(m6.filter(f)), to_string(m1));
CAF_CHECK_EQUAL(to_string(m7.filter(f)), to_string(m1));
}
void test_filter3() {
auto m1 = make_message(1);
CAF_CHECK_EQUAL(to_string(m1.filter([](int) {})), to_string(message{}));
auto m2 = make_message(1.0, 2, 3, 4.0);
auto m3 = m2.filter({
[](int, int) { },
[](double, double) { }
});
// check for false positives through collapsing
CAF_CHECK_EQUAL(to_string(m3), to_string(make_message(1.0, 4.0)));
}
void test_filter_cli() {
auto f = [](std::vector<std::string> args) {
std::string filename;
auto res = message_builder(args.begin(), args.end()).filter_cli({
{"version,v", "print version"},
{"file,f", "set output file", filename},
{"whatever", "do whatever"}
});
CAF_CHECK_EQUAL(res.opts.count("file"), 1);
CAF_CHECK_EQUAL(to_string(res.remainder), to_string(message{}));
CAF_CHECK_EQUAL(filename, "hello.txt");
};
f({"--file=hello.txt"});
f({"-f", "hello.txt"});
}
void test_type_token() {
auto m1 = make_message(get_atom::value);
CAF_CHECK_EQUAL(m1.type_token(), detail::make_type_token<get_atom>());
}
int main() {
CAF_TEST(message);
test_drop();
test_slice();
test_filter1(1, 2, 3, 2, 3, 1);
test_filter1(1, 2, 3, 1, 3, 2);
test_filter1(1, 2, 3, 1, 2, 3);
test_filter2();
test_filter3();
test_filter_cli();
test_type_token();
return CAF_TEST_RESULT();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment