Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config file handling #34

Merged
merged 3 commits into from
Sep 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ Packages required in system to build the application
- CMake >= 3.23
- Make / Ninja
- GCC >= 14.x
- googlemock
- gstreamer >= 1.24
- cairo
- pango
- pugixml

For testing

- googlemock

## Alignment Mode

Expand Down
146 changes: 138 additions & 8 deletions src/arguments.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
#include "arguments.h"
#include "utils/argument_parser.h"

#include <fstream>
#include <sstream>

namespace vgraph {
namespace consts {
int defult_alignment_clip_time(60);
const int defult_alignment_clip_time(60);
const std::string whitespaces(" \t\v\r\n");
}
namespace key {
std::string help("help");
std::string config("config");
std::string debug("debug");
std::string gpu("gpu");
std::string timecode("timecode");
Expand All @@ -25,13 +30,77 @@ namespace key {
utils::argument_parser prepare_parser();

arguments read_args(const utils::argument_parser& parser, utils::logging::logger& log);

bool parse_config_file(const std::string& path, arguments& args);

void assert_arguments_valid(const arguments& args, utils::logging::logger& log);

template <typename T>
bool read_value(const utils::argument_parser& parser, const std::string& name, utils::logging::logger& log, std::optional<T>& value_out);

bool parse_resolution(const std::string& str, utils::logging::logger& log, std::optional<std::pair<int, int>>& out);

/* specialized parse_value functions */
template <typename T>
bool parse_value(const std::string& str, utils::logging::logger& log, T& value_out)
{
log.error("Unsupported value type");
return false;
}

template <>
bool parse_value<bool>(const std::string& str, utils::logging::logger& log, bool& value_out)
{
if (str == "true") {
value_out = true;
return true;
} else if (str == "false") {
value_out = false;
return true;
}

return false;
}

template <>
bool parse_value<std::optional<std::string>>(const std::string& str, utils::logging::logger& log, std::optional<std::string>& value_out)
{
value_out = str;
return true;
}

template <>
bool parse_value<std::optional<int>>(const std::string& str, utils::logging::logger& log, std::optional<int>& value_out)
{
try {
value_out = std::stoi(str);
} catch (std::invalid_argument) {
log.error("Could not parse value '{}' as integer point number", str);
return false;
} catch (std::out_of_range) {
log.error("Value '{}' is out of range", str);
return false;
}

return true;
}

template <>
bool parse_value<std::optional<double>>(const std::string& str, utils::logging::logger& log, std::optional<double>& value_out)
{
try {
value_out = std::stod(str);
} catch (std::invalid_argument) {
log.error("Could not parse value '{}' as floating point number", str);
return false;
} catch (std::out_of_range) {
log.error("Value '{}' is out of range", str);
return false;
}

return true;
}

/* interface */
arguments arguments::parse(int argc, char* argv[])
{
Expand Down Expand Up @@ -69,9 +138,10 @@ utils::argument_parser prepare_parser()
utils::argument_parser parser("vgraph");

parser.add_argument(key::help, utils::argument().flag().option("-h").option("--help") .description("Print this help message"));
parser.add_argument(key::config, utils::argument() .option("-c").option("--config") .description("Load config file"));
parser.add_argument(key::debug, utils::argument().flag().option("-d").option("--debug") .description("Enable debug logs"));
parser.add_argument(key::gpu, utils::argument().flag().option("-g").option("--gpu") .description("Use Nvidia GPU for processing"));
parser.add_argument(key::timecode, utils::argument().flag().option("-c").option("--timecode") .description("Draw a timecode on each frame"));
parser.add_argument(key::timecode, utils::argument().flag().option("-m").option("--timecode") .description("Draw a timecode on each frame"));
parser.add_argument(key::alignment, utils::argument().flag().option("-l").option("--alignment") .description(std::format("Enable alignment mode ({}s clip unless overriden by -T)", consts::defult_alignment_clip_time)));
parser.add_argument(key::telemetry, utils::argument() .option("-t").option("--telemetry") .description("Telemetry file path"));
parser.add_argument(key::layout, utils::argument() .option("-a").option("--layout") .description("Layout file path"));
Expand All @@ -90,10 +160,14 @@ arguments read_args(const utils::argument_parser& parser, utils::logging::logger
arguments a;
bool valid = true;

a.debug = parser.get<bool>(key::debug);
a.gpu = parser.get<bool>(key::gpu);
a.timecode = parser.get<bool>(key::timecode);
a.alignment_mode = parser.get<bool>(key::alignment);
if (parser.has(key::config)) {
valid = parse_config_file(parser.get<std::string>(key::config), a);
}

a.debug = a.debug || parser.get<bool>(key::debug);
a.gpu = a.gpu || parser.get<bool>(key::gpu);
a.timecode = a.timecode || parser.get<bool>(key::timecode);
a.alignment_mode = a.alignment_mode || parser.get<bool>(key::alignment);

valid = read_value<std::string>(parser, key::telemetry, log, a.telemetry) && valid;
valid = read_value<std::string>(parser, key::layout, log, a.layout) && valid;
Expand All @@ -119,6 +193,64 @@ arguments read_args(const utils::argument_parser& parser, utils::logging::logger
return std::move(a);
}

/* Expected format:
#comments are ignored
key=value
key=value
key=value
*/
bool parse_config_file(const std::string& path, arguments& args)
{
utils::logging::logger log{"arguments::parse_config_file()"};

std::ifstream file(path);
if (!file.is_open()) {
log.error("Failed to open '{}' file", path);
return false;
}

std::string line;
while(getline(file, line)) {
line = line.substr(line.find_first_not_of(consts::whitespaces)); //remove leading whitespaces
if (line.starts_with('#')) {
continue;
}

int pos = line.find('=');
const std::string& key = line.substr(0, pos);
const std::string& value = line.substr(pos+1);

log.debug("key: '{}' value: '{}'", key, value);
if (key == key::debug) {
parse_value(value, log, args.debug);
} else if (key == key::gpu) {
parse_value(value, log, args.gpu);
} else if (key == key::timecode) {
parse_value(value, log, args.timecode);
} else if (key == key::alignment) {
parse_value(value, log, args.alignment_mode);
} else if (key == key::telemetry) {
parse_value(value, log, args.telemetry);
} else if (key == key::layout) {
parse_value(value, log, args.layout);
} else if (key == key::input) {
parse_value(value, log, args.input);
} else if (key == key::output) {
parse_value(value, log, args.output);
} else if (key == key::offset) {
parse_value(value, log, args.offset);
} else if (key == key::resolution) {
parse_resolution(value, log, args.resolution);
} else if (key == key::bitrate) {
parse_value(value, log, args.bitrate);
} else if (key == key::clip_time) {
parse_value(value, log, args.clip_time);
}
}

return true;
}

void assert_arguments_valid(const arguments& args, utils::logging::logger& log)
{
bool valid = true;
Expand Down Expand Up @@ -178,6 +310,4 @@ bool parse_resolution(const std::string& str, utils::logging::logger& log, std::
return true;
}



}
4 changes: 2 additions & 2 deletions src/utils/argument_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,11 @@ void argument_parser::print_help_details() const
str = std::format("{} {}", str, helper::to_upper(key));
}

if (str.length() > 20) {
if (str.length() > 30) {
std::cout << std::format(" {}", str) << std::endl;
std::cout << std::format(" {}", arg.description_) << std::endl;
} else {
std::cout << std::format(" {:<16} {}", str, arg.description_) << std::endl;
std::cout << std::format(" {:<30} {}", str, arg.description_) << std::endl;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.20.0)
target_sources(vgraph_test
PRIVATE
main.cpp
config_file_parsing_test.cpp
)

add_subdirectory(telemetry)
Expand Down
66 changes: 66 additions & 0 deletions test/config_file_parsing_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <gtest/gtest.h>
#include "arguments.h"
#include "testutils/testdata.h"

#include <filesystem>

namespace vgraph {
namespace consts {
const std::filesystem::path full_conf = std::filesystem::path(TESTDATA_DIR) / "full.conf";
const std::filesystem::path partial_conf = std::filesystem::path(TESTDATA_DIR) / "partial.conf";
}

//forward declaration because function under test is internal to cpp
bool parse_config_file(const std::string& path, arguments& args);

TEST(config_file_parsing_test, full_config_file)
{
arguments args;

EXPECT_TRUE(parse_config_file(consts::full_conf, args));

EXPECT_TRUE(args.debug);
EXPECT_TRUE(args.gpu);
EXPECT_TRUE(args.timecode);
EXPECT_TRUE(args.alignment_mode);
EXPECT_EQ("/some/absolute/path", args.telemetry);
EXPECT_EQ("./a/different/relative/path", args.layout);
EXPECT_EQ("mp4_file.mp4", args.input);
EXPECT_EQ("/absolute/mp4/file.mp4", args.output);
EXPECT_EQ(100000, args.bitrate);

ASSERT_TRUE(args.resolution);
EXPECT_EQ(1234, args.resolution->first);
EXPECT_EQ(567, args.resolution->second);

ASSERT_TRUE(args.offset);
EXPECT_NEAR(123.456, *args.offset, 0.001);

ASSERT_TRUE(args.clip_time);
EXPECT_NEAR(60, *args.clip_time, 0.001);
}

TEST(config_file_parsing_test, partial_config_file)
{
arguments args;

EXPECT_TRUE(parse_config_file(consts::partial_conf, args));

EXPECT_FALSE(args.debug);
EXPECT_TRUE(args.gpu);
EXPECT_FALSE(args.timecode);
EXPECT_FALSE(args.alignment_mode);
EXPECT_EQ("./test/testdata/correct.fit", args.telemetry);
EXPECT_EQ("./test/testdata/layout.xml", args.layout);
EXPECT_FALSE(args.input);
EXPECT_FALSE(args.output);
EXPECT_EQ(80000, args.bitrate);
EXPECT_FALSE(args.offset);
EXPECT_FALSE(args.clip_time);

ASSERT_TRUE(args.resolution);
EXPECT_EQ(3840, args.resolution->first);
EXPECT_EQ(2160, args.resolution->second);
}

} // namespace vgraph
14 changes: 14 additions & 0 deletions test/testdata/full.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#comment is ignored
debug=true
gpu=true
timecode=true
alignment=true
telemetry=/some/absolute/path
layout=./a/different/relative/path
offset=123.456
input=mp4_file.mp4
output=/absolute/mp4/file.mp4
resolution=1234x567
bitrate=100000
clip_time=60
#comment is ignored
12 changes: 12 additions & 0 deletions test/testdata/partial.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# debug=true
gpu=true
# timecode=true
# alignment=true
telemetry=./test/testdata/correct.fit
layout=./test/testdata/layout.xml
# offset=123.456
# input=mp4_file.mp4
# output=/absolute/mp4/file.mp4
resolution=3840x2160
bitrate=80000
# clip_time=60