Skip to content

Commit 2eeff03

Browse files
Zainullin DamirZainullin Damir
authored andcommitted
OutputConfigurationParser - Introduce OutputAction class
1 parent 6d9918d commit 2eeff03

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* @file
3+
* @brief Implementation of OutputAction.
4+
* @author Damir Zainullin <[email protected]>
5+
*
6+
* @copyright Copyright (c) 2025 CESNET, z.s.p.o.
7+
*/
8+
9+
#include "outputAction.hpp"
10+
11+
#include <algorithm>
12+
#include <cctype>
13+
#include <functional>
14+
#include <ranges>
15+
#include <stdexcept>
16+
#include <string>
17+
#include <variant>
18+
#include <vector>
19+
20+
namespace ipxp {
21+
22+
// Helper because std::isspace takes int
23+
constexpr static bool isSpace(const char c) noexcept
24+
{
25+
return std::isspace(c);
26+
}
27+
28+
OutputAction::OutputAction(std::string_view line)
29+
{
30+
if (std::ranges::any_of(line, isSpace)) {
31+
throw std::invalid_argument(
32+
"Invalid field specification: " + std::string(line)
33+
+ ". No whitespace characters are allowed.");
34+
}
35+
36+
type = line.starts_with('-') ? Type::Exclude : Type::Include;
37+
if (type == Type::Exclude) {
38+
line.remove_prefix(1);
39+
}
40+
41+
if (line.contains(".")) {
42+
if (std::ranges::count(line, '.') != 1) {
43+
throw std::invalid_argument(
44+
"Invalid field specification: " + std::string(line)
45+
+ ". Only one '.' is allowed to separate plugin and field name.");
46+
}
47+
const std::size_t dotPos = line.find('.');
48+
action = FieldAction {
49+
.pluginName = std::string(line.substr(0, dotPos)),
50+
.fieldName = std::string(line.substr(dotPos + 1))};
51+
return;
52+
}
53+
54+
if (line == "*") {
55+
action = GlobalAction {};
56+
return;
57+
}
58+
59+
action = PluginAction {std::string(line)};
60+
}
61+
62+
constexpr static std::string_view trimFieldsHeader(std::string_view content)
63+
{
64+
std::string_view header = "fields:";
65+
if (!content.starts_with(header)) {
66+
throw std::invalid_argument("Configuration file must start with \"fields:\" header");
67+
}
68+
69+
content.remove_prefix(header.size());
70+
const std::size_t firstQuotePos
71+
= std::ranges::find_if(content, std::not_fn(isSpace)) - content.begin();
72+
if (firstQuotePos == content.size() || content[firstQuotePos] != '\'') {
73+
throw std::invalid_argument(
74+
"Configuration file must contain opening quote after \"fields:\" header");
75+
}
76+
77+
const std::size_t lastQuotePos = content.size() - 1
78+
- (std::find_if(content.rbegin(), content.rend(), std::not_fn(isSpace)) - content.rbegin());
79+
if (lastQuotePos == -1ULL || content[lastQuotePos] != '\'') {
80+
throw std::invalid_argument(
81+
"Configuration file must contain closing quote after field definitions");
82+
}
83+
84+
return content.substr(firstQuotePos + 1, lastQuotePos - firstQuotePos - 1);
85+
}
86+
87+
constexpr static std::string removeComments(std::string_view content) noexcept
88+
{
89+
std::string result(content.begin(), content.end());
90+
for (std::size_t sharpPos = result.find('#'); sharpPos != std::string::npos;
91+
sharpPos = result.find('#', sharpPos)) {
92+
const std::size_t endOfLinePos = result.find('\n', sharpPos);
93+
if (endOfLinePos == std::string::npos) {
94+
result.erase(sharpPos);
95+
} else {
96+
result.erase(sharpPos, endOfLinePos - sharpPos + 1);
97+
}
98+
}
99+
100+
return result;
101+
}
102+
103+
std::vector<OutputAction> parseActions(std::string_view configurationContent)
104+
{
105+
return removeComments(trimFieldsHeader(configurationContent)) | std::views::split(',')
106+
| std::views::transform([](const auto lineRange) {
107+
return std::string_view(&*lineRange.begin(), std::ranges::distance(lineRange));
108+
})
109+
// Remove empty lines
110+
| std::views::filter([](std::string_view line) {
111+
return std::ranges::find_if(line, std::not_fn(isSpace)) != line.end();
112+
})
113+
// Trim spaces on the sides
114+
| std::views::transform([](std::string_view line) {
115+
const std::size_t firstNonSpacePos
116+
= std::ranges::find_if(line, std::not_fn(isSpace)) - line.begin();
117+
const std::size_t lastNonSpacePos = line.size() - 1
118+
- (std::find_if(line.rbegin(), line.rend(), std::not_fn(isSpace))
119+
- line.rbegin());
120+
return line.substr(firstNonSpacePos, lastNonSpacePos - firstNonSpacePos + 1);
121+
})
122+
| std::views::transform([](std::string_view line) { return OutputAction(line); })
123+
| std::ranges::to<std::vector>();
124+
}
125+
126+
} // namespace ipxp
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
/**
3+
* @file
4+
* @brief Declaration of OutputAction.
5+
* @author Damir Zainullin <[email protected]>
6+
*
7+
* @copyright Copyright (c) 2025 CESNET, z.s.p.o.
8+
*/
9+
10+
#pragma once
11+
12+
#include <ranges>
13+
#include <stdexcept>
14+
#include <string>
15+
#include <variant>
16+
#include <vector>
17+
18+
namespace ipxp {
19+
20+
struct OutputAction {
21+
enum class Type : bool { Include, Exclude };
22+
23+
struct GlobalAction {};
24+
25+
struct PluginAction {
26+
std::string pluginName;
27+
};
28+
29+
struct FieldAction {
30+
std::string pluginName;
31+
std::string fieldName;
32+
};
33+
34+
Type type;
35+
std::variant<GlobalAction, PluginAction, FieldAction> action;
36+
37+
OutputAction(std::string_view line);
38+
};
39+
40+
std::vector<OutputAction> parseActions(std::string_view configurationContent);
41+
42+
} // namespace ipxp

0 commit comments

Comments
 (0)