Skip to content

Commit d32d851

Browse files
committed
Add CmdArgs
- Support --help and --version by default. - Currently no custom options / parser in use.
1 parent 4b4f2e9 commit d32d851

File tree

4 files changed

+242
-88
lines changed

4 files changed

+242
-88
lines changed

lib/util/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ target_sources(${PROJECT_NAME} PRIVATE
6666
include/${target_prefix}/util/async_queue.hpp
6767
include/${target_prefix}/util/bool.hpp
6868
include/${target_prefix}/util/byte_buffer.hpp
69+
include/${target_prefix}/util/cmd_args.hpp
6970
include/${target_prefix}/util/colour_space.hpp
7071
include/${target_prefix}/util/data_provider.hpp
7172
include/${target_prefix}/util/enum_array.hpp
@@ -89,6 +90,7 @@ target_sources(${PROJECT_NAME} PRIVATE
8990
include/${target_prefix}/util/version.hpp
9091
include/${target_prefix}/util/visitor.hpp
9192

93+
src/cmd_args.cpp
9294
src/data_provider.cpp
9395
src/env.cpp
9496
src/image.cpp
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#pragma once
2+
#include <facade/util/ptr.hpp>
3+
#include <string>
4+
#include <vector>
5+
6+
namespace facade {
7+
struct CmdArgs {
8+
enum class Result { eContinue, eExitFailure, eExitSuccess };
9+
10+
struct Key {
11+
std::string_view full{};
12+
char single{};
13+
14+
constexpr bool valid() const { return !full.empty() || single != '\0'; }
15+
};
16+
17+
using Value = std::string_view;
18+
19+
struct Opt {
20+
Key key{};
21+
Value value{};
22+
bool is_optional_value{};
23+
std::string_view help{};
24+
};
25+
26+
struct Parser {
27+
virtual void opt(Key key, Value value) = 0;
28+
};
29+
30+
struct Spec {
31+
std::vector<Opt> options{};
32+
std::string_view version{"(unknown)"};
33+
};
34+
35+
static Result parse(Spec spec, Ptr<Parser> out, int argc, char const* const* argv);
36+
static Result parse(std::string_view version, int argc, char const* const* argv);
37+
};
38+
} // namespace facade

lib/util/src/cmd_args.cpp

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#include <fmt/format.h>
2+
#include <facade/util/cmd_args.hpp>
3+
#include <facade/util/visitor.hpp>
4+
#include <algorithm>
5+
#include <cassert>
6+
#include <filesystem>
7+
#include <iostream>
8+
#include <span>
9+
#include <variant>
10+
11+
namespace facade {
12+
namespace {
13+
namespace fs = std::filesystem;
14+
15+
CmdArgs::Opt const* find_opt(CmdArgs::Spec const& spec, std::variant<std::string_view, char> key) {
16+
for (auto const& opt : spec.options) {
17+
bool match{};
18+
auto const visitor = Visitor{
19+
[opt, &match](std::string_view const full) {
20+
if (opt.key.full == full) { match = true; }
21+
},
22+
[opt, &match](char const single) {
23+
if (opt.key.single == single) { match = true; }
24+
},
25+
};
26+
std::visit(visitor, key);
27+
if (match) { return &opt; }
28+
}
29+
return {};
30+
}
31+
32+
struct OptParser {
33+
using Result = CmdArgs::Result;
34+
35+
CmdArgs::Spec spec{};
36+
Ptr<CmdArgs::Parser> out;
37+
std::string exe_name{};
38+
39+
OptParser(CmdArgs::Spec spec, Ptr<CmdArgs::Parser> out, char const* arg0) : spec(std::move(spec)), out(out) {
40+
std::erase_if(this->spec.options, [](CmdArgs::Opt const& o) { return !o.key.valid(); });
41+
this->spec.options.push_back(CmdArgs::Opt{.key = {.full = "help"}, .help = "Show this help text"});
42+
this->spec.options.push_back(CmdArgs::Opt{.key = {.full = "version"}, .help = "Show the version"});
43+
exe_name = fs::path{arg0}.filename().stem().generic_string();
44+
}
45+
46+
std::size_t get_max_width() const {
47+
auto ret = std::size_t{};
48+
for (auto const& opt : spec.options) {
49+
auto width = opt.key.full.size();
50+
if (!opt.value.empty()) {
51+
width += 1; // =
52+
if (opt.is_optional_value) {
53+
width += 2; // []
54+
}
55+
width += opt.value.size();
56+
}
57+
ret = std::max(ret, width);
58+
}
59+
return ret;
60+
}
61+
62+
Result print_help() const {
63+
auto str = std::string{};
64+
str.reserve(1024);
65+
fmt::format_to(std::back_inserter(str), "Usage: {} [OPTION]...\n\n", exe_name);
66+
auto const max_width = get_max_width();
67+
for (auto const& opt : spec.options) {
68+
fmt::format_to(std::back_inserter(str), " {}{}", (opt.key.single ? '-' : ' '), (opt.key.single ? opt.key.single : ' '));
69+
if (!opt.key.full.empty()) { fmt::format_to(std::back_inserter(str), "{} --{}", (opt.key.single ? ',' : ' '), opt.key.full); }
70+
auto width = opt.key.full.size();
71+
if (!opt.value.empty()) {
72+
fmt::format_to(std::back_inserter(str), "{}={}{}", (opt.is_optional_value ? "[" : ""), opt.value, (opt.is_optional_value ? "]" : ""));
73+
width += (opt.is_optional_value ? 3 : 1) + opt.value.size();
74+
}
75+
assert(width <= max_width);
76+
auto const remain = max_width - width + 4;
77+
for (std::size_t i = 0; i < remain; ++i) { str += ' '; }
78+
fmt::format_to(std::back_inserter(str), "{}\n", opt.help);
79+
}
80+
std::cout << str << '\n';
81+
return Result::eExitSuccess;
82+
}
83+
84+
Result print_version() const {
85+
std::cout << fmt::format("{} version {}\n", exe_name, spec.version);
86+
return Result::eExitSuccess;
87+
}
88+
89+
CmdArgs::Result opt(CmdArgs::Key key, CmdArgs::Value value) const {
90+
if (key.full == "help") { return print_help(); }
91+
if (key.full == "version") { return print_version(); }
92+
if (out) { out->opt(key, value); }
93+
return Result::eContinue;
94+
}
95+
};
96+
97+
struct ParseOpt {
98+
using Result = CmdArgs::Result;
99+
100+
OptParser const& out;
101+
102+
std::string_view current{};
103+
104+
bool at_end() const { return current.empty(); }
105+
106+
char advance() {
107+
if (at_end()) { return {}; }
108+
auto ret = current[0];
109+
current = current.substr(1);
110+
return ret;
111+
}
112+
113+
char peek() const { return current[0]; }
114+
115+
Result unknown_option(std::string_view opt) const {
116+
std::cerr << fmt::format("unknown option: {}\n", opt);
117+
return Result::eExitFailure;
118+
}
119+
120+
Result missing_value(CmdArgs::Opt const& opt) const {
121+
auto str = opt.key.full;
122+
if (str.empty()) { str = {&opt.key.single, 1}; }
123+
std::cerr << fmt::format("missing required value for option: {}{}\n", (opt.key.full.empty() ? "-" : "--"), str);
124+
return Result::eExitFailure;
125+
}
126+
127+
Result parse_word() {
128+
auto const it = current.find('=');
129+
CmdArgs::Opt const* opt{};
130+
auto value = CmdArgs::Value{};
131+
if (it != std::string_view::npos) {
132+
auto const key = current.substr(0, it);
133+
opt = find_opt(out.spec, key);
134+
if (!opt) { return unknown_option(key); }
135+
value = current.substr(it + 1);
136+
} else {
137+
opt = find_opt(out.spec, current);
138+
if (!opt) { return unknown_option(current); }
139+
}
140+
if (!opt->value.empty() && !opt->is_optional_value && value.empty()) { return missing_value(*opt); }
141+
return out.opt(opt->key, value);
142+
}
143+
144+
Result parse_singles() {
145+
char prev{};
146+
while (!at_end() && peek() != '=') {
147+
prev = advance();
148+
auto const* opt = find_opt(out.spec, prev);
149+
if (!opt) { return unknown_option({&prev, 1}); }
150+
return out.opt(opt->key, {});
151+
}
152+
if (peek() == '=') {
153+
if (prev == '\0') { return unknown_option(""); }
154+
advance();
155+
auto const* opt = find_opt(out.spec, prev);
156+
if (!opt) { return unknown_option({&prev, 1}); }
157+
return out.opt(opt->key, current);
158+
}
159+
return Result::eContinue;
160+
}
161+
162+
Result parse() {
163+
auto const c = advance();
164+
if (c == '-') {
165+
advance();
166+
return parse_word();
167+
}
168+
return parse_singles();
169+
}
170+
171+
Result operator()(std::string_view opt_arg) {
172+
current = opt_arg;
173+
return parse();
174+
}
175+
};
176+
177+
} // namespace
178+
179+
auto CmdArgs::parse(Spec spec, Ptr<Parser> out, int argc, char const* const* argv) -> Result {
180+
if (argc < 1) { return Result::eContinue; }
181+
auto opt_parser = OptParser{std::move(spec), out, argv[0]};
182+
auto parse_opt = ParseOpt{opt_parser};
183+
for (int i = 1; i < argc; ++i) {
184+
if (auto const result = parse_opt(argv[i]); result != Result::eContinue) { return result; }
185+
}
186+
return Result::eContinue;
187+
}
188+
189+
auto CmdArgs::parse(std::string_view version, int argc, char const* const* argv) -> Result {
190+
auto spec = Spec{.version = version};
191+
return parse(std::move(spec), {}, argc, argv);
192+
}
193+
} // namespace facade

src/main.cpp

Lines changed: 9 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#include <facade/defines.hpp>
22

3+
#include <facade/util/cmd_args.hpp>
34
#include <facade/util/data_provider.hpp>
45
#include <facade/util/error.hpp>
56
#include <facade/util/logger.hpp>
7+
68
#include <facade/vk/geometry.hpp>
79

810
#include <facade/scene/fly_cam.hpp>
@@ -16,8 +18,6 @@
1618
#include <gui/gltf_sources.hpp>
1719
#include <gui/main_menu.hpp>
1820

19-
#include <djson/json.hpp>
20-
2121
#include <imgui.h>
2222

2323
#include <filesystem>
@@ -28,89 +28,6 @@ using namespace facade;
2828
namespace {
2929
namespace fs = std::filesystem;
3030

31-
static constexpr auto test_json_v = R"(
32-
{
33-
"scene": 0,
34-
"scenes" : [
35-
{
36-
"nodes" : [ 0 ]
37-
}
38-
],
39-
40-
"nodes" : [
41-
{
42-
"mesh" : 0
43-
}
44-
],
45-
46-
"meshes" : [
47-
{
48-
"primitives" : [ {
49-
"attributes" : {
50-
"POSITION" : 1
51-
},
52-
"indices" : 0,
53-
"material" : 0
54-
} ]
55-
}
56-
],
57-
58-
"buffers" : [
59-
{
60-
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
61-
"byteLength" : 44
62-
}
63-
],
64-
"bufferViews" : [
65-
{
66-
"buffer" : 0,
67-
"byteOffset" : 0,
68-
"byteLength" : 6,
69-
"target" : 34963
70-
},
71-
{
72-
"buffer" : 0,
73-
"byteOffset" : 8,
74-
"byteLength" : 36,
75-
"target" : 34962
76-
}
77-
],
78-
"accessors" : [
79-
{
80-
"bufferView" : 0,
81-
"byteOffset" : 0,
82-
"componentType" : 5123,
83-
"count" : 3,
84-
"type" : "SCALAR",
85-
"max" : [ 2 ],
86-
"min" : [ 0 ]
87-
},
88-
{
89-
"bufferView" : 1,
90-
"byteOffset" : 0,
91-
"componentType" : 5126,
92-
"count" : 3,
93-
"type" : "VEC3",
94-
"max" : [ 1.0, 1.0, 0.0 ],
95-
"min" : [ 0.0, 0.0, 0.0 ]
96-
}
97-
],
98-
99-
"materials" : [
100-
{
101-
"pbrMetallicRoughness": {
102-
"baseColorFactor": [ 1.000, 0.766, 0.336, 1.0 ],
103-
"metallicFactor": 0.5,
104-
"roughnessFactor": 0.1
105-
}
106-
}
107-
],
108-
"asset" : {
109-
"version" : "2.0"
110-
}
111-
}
112-
)";
113-
11431
void log_prologue() {
11532
auto const now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
11633
char buf[32]{};
@@ -161,8 +78,6 @@ void run() {
16178
engine->add_shader(lit);
16279
engine->add_shader(shaders::unlit());
16380

164-
auto& scene = engine->scene();
165-
scene.load_gltf(dj::Json::parse(test_json_v), DummyDataProvider{});
16681
post_scene_load();
16782
engine->show(true);
16883
};
@@ -260,9 +175,15 @@ void run() {
260175
}
261176
} // namespace
262177

263-
int main() {
178+
int main(int argc, char** argv) {
264179
try {
265180
auto logger_instance = logger::Instance{};
181+
auto const version_str = fmt::format("v{}.{}.{}", version_v.major, version_v.minor, version_v.patch);
182+
switch (CmdArgs::parse(version_str, argc, argv)) {
183+
case CmdArgs::Result::eExitSuccess: return EXIT_SUCCESS;
184+
case CmdArgs::Result::eExitFailure: return EXIT_FAILURE;
185+
case CmdArgs::Result::eContinue: break;
186+
}
266187
try {
267188
run();
268189
} catch (InitError const& e) {

0 commit comments

Comments
 (0)