Skip to content

Commit 68f5c84

Browse files
committed
tmp/cli
Signed-off-by: turuslan <[email protected]>
1 parent e0b4ca0 commit 68f5c84

File tree

13 files changed

+421
-0
lines changed

13 files changed

+421
-0
lines changed

core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
add_subdirectory(adt)
77
add_subdirectory(api)
88
add_subdirectory(blockchain)
9+
add_subdirectory(cli)
910
add_subdirectory(clock)
1011
add_subdirectory(codec)
1112
add_subdirectory(common)

core/cli/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#
2+
# Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
6+
add_library(cli INTERFACE)
7+
target_link_libraries(cli INTERFACE
8+
Boost::program_options
9+
fmt::fmt
10+
)
11+
12+
add_subdirectory(example)

core/cli/cli.hpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#pragma once
7+
8+
#include <boost/optional.hpp>
9+
#include <boost/program_options/options_description.hpp>
10+
#include <map>
11+
#include <memory>
12+
#include <typeindex>
13+
14+
#define CLI_OPTS() ::fc::cli::Opts opts()
15+
#define CLI_RUN() \
16+
static ::fc::cli::RunResult run(const ::fc::cli::ArgsMap &argm, \
17+
const Args &args, \
18+
const ::fc::cli::Argv &argv)
19+
#define CLI_NO_RUN() constexpr static nullptr_t run{nullptr};
20+
21+
namespace fc::cli {
22+
namespace po = boost::program_options;
23+
using Opts = po::options_description;
24+
25+
using RunResult = void;
26+
struct ArgsMap {
27+
std::map<std::type_index, std::shared_ptr<void>> _;
28+
template <typename Args>
29+
void add(Args &&v) {
30+
_.emplace(typeid(Args), std::make_shared<Args>(std::forward<Args>(v)));
31+
}
32+
template <typename Cmd>
33+
const typename Cmd::Args &of() const {
34+
return *reinterpret_cast<const typename Cmd::Args *>(
35+
_.at(typeid(typename Cmd::Args)).get());
36+
}
37+
};
38+
// note: Args is defined inside command
39+
using Argv = std::vector<std::string>;
40+
41+
struct Empty {
42+
struct Args {
43+
CLI_OPTS() {
44+
return {};
45+
}
46+
};
47+
CLI_NO_RUN();
48+
};
49+
using Group = Empty;
50+
51+
struct ShowHelp {};
52+
} // namespace fc::cli

core/cli/example/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#
2+
# Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
6+
add_executable(cli_example main.cpp)
7+
target_link_libraries(cli_example
8+
cli
9+
rpc
10+
)

core/cli/example/main.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include "cli/node/_tree.hpp"
7+
#include "cli/run.hpp"
8+
9+
int main(int argc, const char *argv[]) {
10+
fc::cli::run("cli_example", fc::cli::_node::_tree, argc, argv);
11+
}

core/cli/node/_tree.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#pragma once
7+
8+
#include "cli/node/net.hpp"
9+
#include "cli/tree.hpp"
10+
11+
namespace fc::cli::_node {
12+
const auto _tree{tree<Node>({
13+
{"net",
14+
tree<Group>({
15+
{"listen", tree<Node_net_listen>()},
16+
})},
17+
})};
18+
} // namespace fc::cli::_node

core/cli/node/net.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#pragma once
7+
8+
#include "cli/node/node.hpp"
9+
10+
namespace fc::cli::_node {
11+
struct Node_net_listen : Empty {
12+
CLI_RUN() {
13+
Node::Api api{argm};
14+
const auto peer{api._->NetAddrsListen().value()};
15+
for (const auto &addr : peer.addresses) {
16+
fmt::print("{}\n", addr.getStringAddress());
17+
}
18+
}
19+
};
20+
} // namespace fc::cli::_node

core/cli/node/node.hpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#pragma once
7+
8+
#include <fmt/format.h>
9+
10+
#include "api/rpc/client_setup.hpp"
11+
#include "api/rpc/info.hpp"
12+
#include "cli/cli.hpp"
13+
#include "node/node_version.hpp"
14+
15+
namespace fc::cli::_node {
16+
struct Node {
17+
struct Args {
18+
bool version{};
19+
boost::optional<boost::filesystem::path> repo;
20+
21+
CLI_OPTS() {
22+
Opts opts;
23+
auto opt{opts.add_options()};
24+
opt("version,v", po::bool_switch(&version));
25+
opt("repo", po::value(&repo));
26+
return opts;
27+
}
28+
};
29+
CLI_RUN() {
30+
if (args.version) {
31+
fmt::print("{}\n", node::kNodeVersion);
32+
return;
33+
}
34+
throw ShowHelp{};
35+
}
36+
37+
struct Api {
38+
std::shared_ptr<api::FullNodeApi> _;
39+
IoThread thread;
40+
std::shared_ptr<api::rpc::Client> wsc;
41+
42+
Api(const ArgsMap &argm) {
43+
const auto &args{argm.of<Node>()};
44+
const auto info{
45+
*api::rpc::loadInfo(args.repo.value_or(""), "FULLNODE_API_INFO")};
46+
_ = std::make_shared<api::FullNodeApi>();
47+
wsc = std::make_shared<api::rpc::Client>(*thread.io);
48+
wsc->setup(*_);
49+
wsc->connect(info.first, "/rpc/v1", info.second).value();
50+
}
51+
};
52+
};
53+
} // namespace fc::cli::_node

core/cli/run.hpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#pragma once
7+
8+
#include <fmt/format.h>
9+
#include <fmt/ostream.h>
10+
#include <boost/program_options/parsers.hpp>
11+
#include <boost/program_options/variables_map.hpp>
12+
13+
#include "cli/tree.hpp"
14+
15+
namespace fc::cli {
16+
inline bool isDash(const std::string &s) {
17+
return !s.empty() && s[0] == '-';
18+
}
19+
inline bool isDashDash(const std::string &s) {
20+
return s.size() == 2 && s[0] == '-' && s[1] == '-';
21+
}
22+
// note: returns first positional arg or end
23+
inline Argv::iterator hackBoost(const Opts &opts,
24+
Argv::iterator begin,
25+
Argv::iterator end) {
26+
po::parsed_options parsed{&opts};
27+
while (true) {
28+
if (begin == end) {
29+
break;
30+
}
31+
if (!isDash(*begin)) {
32+
break;
33+
}
34+
if (isDashDash(*begin)) {
35+
++begin;
36+
break;
37+
}
38+
const auto it{std::find_if(begin + 1, end, isDash)};
39+
const auto options{
40+
po::command_line_parser{Argv{begin, it}}.options(opts).run().options};
41+
if (options.empty()) {
42+
break;
43+
}
44+
for (const auto &option : options) {
45+
parsed.options.emplace_back(option);
46+
if (option.string_key.empty()) {
47+
break;
48+
}
49+
begin += option.original_tokens.size();
50+
}
51+
}
52+
po::variables_map vm;
53+
po::store(parsed, vm);
54+
po::notify(vm);
55+
return begin;
56+
}
57+
58+
inline RunResult run(std::string app, const Tree &_tree, Argv argv) {
59+
auto tree{&_tree};
60+
std::vector<std::string> cmds;
61+
cmds.emplace_back(std::move(app));
62+
ArgsMap argm;
63+
auto argv_it{argv.begin()};
64+
while (true) {
65+
auto args{tree->args()};
66+
auto option{args.opts.add_options()};
67+
bool help{};
68+
// note: help doesn't work with required options
69+
option("help,h", po::bool_switch(&help));
70+
argv_it = hackBoost(args.opts, argv_it, argv.end());
71+
argm._.emplace(args._);
72+
if (!help) {
73+
if (argv_it != argv.end()) {
74+
auto sub_it{tree->sub.find(*argv_it)};
75+
if (sub_it != tree->sub.end()) {
76+
++argv_it;
77+
cmds.emplace_back(sub_it->first);
78+
tree = &sub_it->second;
79+
continue;
80+
}
81+
}
82+
if (tree->run) {
83+
try {
84+
return tree->run(argm, {argv_it, argv.end()});
85+
} catch (ShowHelp &) {
86+
}
87+
}
88+
}
89+
fmt::print("name:\n {}\n", fmt::join(cmds, " "));
90+
fmt::print("options:\n{}", args.opts);
91+
if (!tree->sub.empty()) {
92+
fmt::print("subcommands:\n");
93+
for (const auto &sub : tree->sub) {
94+
fmt::print(" {}\n", sub.first);
95+
}
96+
}
97+
return;
98+
}
99+
}
100+
inline RunResult run(std::string app,
101+
const Tree &tree,
102+
int argc,
103+
const char *argv[]) {
104+
return run(std::move(app), tree, {argv + 1, argv + argc});
105+
}
106+
} // namespace fc::cli

core/cli/tree.hpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#pragma once
7+
8+
#include "cli/cli.hpp"
9+
10+
namespace fc::cli {
11+
struct Tree {
12+
using Sub = std::map<std::string, Tree>;
13+
struct Args {
14+
std::pair<std::type_index, std::shared_ptr<void>> _;
15+
Opts opts;
16+
};
17+
std::function<Args()> args;
18+
std::function<RunResult(const ArgsMap &argm, const Argv &argv)> run;
19+
Sub sub;
20+
};
21+
template <typename Cmd>
22+
Tree tree(Tree::Sub sub = {}) {
23+
Tree t;
24+
t.args = [] {
25+
const auto ptr{std::make_shared<typename Cmd::Args>()};
26+
return Tree::Args{{typeid(typename Cmd::Args), ptr}, ptr->opts()};
27+
};
28+
constexpr auto run{!std::is_same_v<decltype(Cmd::run), const nullptr_t>};
29+
if constexpr (run) {
30+
t.run = [](const ArgsMap &argm, const Argv &argv) {
31+
if constexpr (run) {
32+
return Cmd::run(argm, argm.of<Cmd>(), argv);
33+
}
34+
};
35+
}
36+
t.sub = std::move(sub);
37+
return t;
38+
}
39+
} // namespace fc::cli

0 commit comments

Comments
 (0)