ifw-daq 3.1.0
IFW Data Acquisition modules
Loading...
Searching...
No Matches
keywordEx.cpp
Go to the documentation of this file.
1/**
2 * @file
3 * @ingroup daq_dpm_libmerge
4 * @copyright ESO - European Southern Observatory
5 */
7
8#include <fmt/format.h>
9#include <fmt/ostream.h>
10#include <fnmatch.h>
11
12#include <daq/fits/keyword.hpp>
13
14namespace daq::dpm {
15namespace detail {
16
17Rule ParseEx(std::string_view ex) {
18 auto it = std::begin(ex);
19 auto end = std::end(ex);
20
21 if (it == end) {
22 throw std::invalid_argument("empty expression is invalid");
23 }
24
25 // Operator
26 Operator op;
27 switch (*it) {
28 case '+':
30 break;
31 case '-':
33 break;
34 default:
35 throw std::invalid_argument(fmt::format(
36 "invalid operator '{}', must be one of '+' or '-': '{}'", fmt::streamed(*it), ex));
37 }
38
39 ++it;
40 if (it == end) {
41 throw std::invalid_argument(
42 fmt::format("unexpected end of expression, expected ' ' or scope: '{}'", ex));
43 }
44
45 // Optional scope
46 Scope scope;
47 switch (*it) {
48 case ' ':
49 scope = Scope::Any;
50 break;
51 case 'e':
52 scope = Scope::Eso;
53 break;
54 case 'v':
55 scope = Scope::Value;
56 break;
57 case 'c':
58 scope = Scope::Commentary;
59 break;
60 default:
61 throw std::invalid_argument(
62 fmt::format("invalid scope '{}', must be one of 'v' (value), 'e' (ESO hierarchical) or "
63 "'c' (commentary): '{}'",
64 fmt::streamed(*it),
65 ex));
66 }
67
68 // Advance past scope, if it was used.
69 if (scope != Scope::Any) {
70 ++it;
71 }
72 if (it == end) {
73 throw std::invalid_argument(
74 fmt::format("unexpected end of expression, expected pattern: '{}'", ex));
75 }
76
77 // Confirm whitespace
78 if (*it != ' ') {
79 throw std::invalid_argument(
80 fmt::format("whitespace ' ' expected after scope, got '{}': ", *it, ex));
81 }
82
83 // Advance past whitespace
84 ++it;
85 if (it == end) {
86 throw std::invalid_argument(
87 fmt::format("unexpected end of expression, expected pattern: '{}'", ex));
88 }
89
90 // Pattern (remaining characters)
91 std::string pattern(it, end);
92 return {op, scope, pattern};
93}
94
96 switch (scope) {
97 case Scope::Any:
98 return true;
99 case Scope::Value:
100 return kwtype == fits::KeywordType::Value;
101 case Scope::Eso:
102 return kwtype == fits::KeywordType::Eso;
104 return kwtype == fits::KeywordType::Commentary;
105 };
106 // Invalid scope
107 return false; // GCOVR_EXCL_LINE
108}
109
110/**
111 * Determines if keyword match rule and if yes, whether it matches;
112 *
113 * @returns std::nullopt if keyword does not match.
114 * @returns Include if keyword matches rule and should be included.
115 * @returns Exclude if keyword matches rule and should be excluded.
116 */
117std::optional<Operator> KeywordMatch(fits::KeywordNameView kw, Rule const& rule) {
118 // Test if scope matches
119 if (!KeywordScopeMatch(kw.type, rule.scope)) {
120 // Scope doesn't match
121 return std::nullopt;
122 }
123
124 // Test if glob match name
125 auto name = std::string(kw.name);
126 auto match = fnmatch(rule.pattern.c_str(), name.c_str(), 0);
127
128 if (match == 0) {
129 return rule.op;
130 } else if (match == FNM_NOMATCH) {
131 return std::nullopt; // no match
132 } else { // GCOVR_EXCL_START
133 // fnmatch seems to accept most strange input so have not been able to test this.
134 throw std::invalid_argument(fmt::format("invalid fnmatch pattern '{}'", rule.pattern));
135 // GCOVR_EXCL_STOP
136 }
137}
138
139std::string RegexReplace(std::string_view str, std::regex const& re, char const* fmt) {
140 std::string result;
141 std::regex_replace(std::back_inserter(result), std::begin(str), std::end(str), re, fmt);
142 return result;
143}
144
145} // namespace detail
146
147KeywordEx::KeywordEx(std::string_view rule) : m_rules(1u, detail::ParseEx(rule)) {
148}
149
150bool KeywordMatch(fits::KeywordVariant const& keyword, KeywordEx const& ex) {
151 bool match = false; // By default keywords don't match
152 auto name = fits::GetKeywordName(keyword);
153 for (auto const& rule : ex.GetRules()) {
154 // optimization: Exclusive rules can be skipped until there's a match
155 auto opt_match = detail::KeywordMatch(name, rule);
156 if (opt_match) {
157 match = (*opt_match) == detail::Operator::Include;
158 }
159 }
160 return match;
161}
162
164KeywordTransform(fits::KeywordVariant const& keyword, std::regex const& re, char const* fmt) {
165 return std::visit(
166 [&](auto const& kw) -> fits::KeywordVariant { return KeywordTransform(kw, re, fmt); },
167 keyword);
168}
169
171KeywordTransform(fits::ValueKeyword const& keyword, std::regex const& re, char const* fmt) {
172 try {
173 auto new_name = detail::RegexReplace(keyword.name, re, fmt);
174 return fits::ValueKeyword(new_name, keyword.value, keyword.comment);
175 } catch (...) {
176 std::throw_with_nested(std::runtime_error(
177 fmt::format("Keyword transform failed for keyword `{}` with transform format `{}`",
178 fmt::streamed(keyword),
179 fmt)));
180 }
181}
182
184KeywordTransform(fits::EsoKeyword const& keyword, std::regex const& re, char const* fmt) {
185 try {
186 auto new_name = detail::RegexReplace(keyword.name, re, fmt);
187 return fits::EsoKeyword(new_name, keyword.value, keyword.comment);
188 } catch (...) {
189 std::throw_with_nested(std::runtime_error(
190 fmt::format("Keyword transform failed for keyword `{}` with transform format `{}`",
191 fmt::streamed(keyword),
192 fmt)));
193 }
194}
195
197KeywordTransform(fits::LiteralKeyword const& keyword, std::regex const& re, char const* fmt) {
198 try {
199 std::string new_name = detail::RegexReplace(keyword.GetName().name, re, fmt);
200 auto components = keyword.GetComponents();
201 components.name = new_name;
202 auto new_kw = Format(components);
203 if (new_kw.GetType() != keyword.GetType()) {
204 throw std::runtime_error(
205 fmt::format("Keyword transform mutated keyword into a different type. Before: {}, "
206 "after: {}. Before transformation: `{}`, after: `{}`",
207 fmt::streamed(keyword.GetType()),
208 fmt::streamed(new_kw.GetType()),
209 keyword.GetRecord(),
210 new_kw.GetRecord()));
211 }
212 if (new_kw.GetComponents().value != components.value) {
213 throw std::runtime_error(fmt::format(
214 "Keyword transform mutated keyword such that the value field was affected "
215 " Before transformation: `{}`, after: `{}`",
216 keyword.GetRecord(),
217 new_kw.GetRecord()));
218 }
219 return new_kw;
220 } catch (...) {
221 std::throw_with_nested(std::runtime_error(
222 fmt::format("Keyword transform failed for keyword `{}` with transform format `{}`",
223 keyword.GetRecord(),
224 fmt)));
225 }
226}
227
228} // namespace daq::dpm
Create keyword expression that memoize the provided string pattern.
Definition: keywordEx.hpp:59
constexpr std::vector< detail::Rule > const & GetRules() const noexcept
Definition: keywordEx.hpp:96
Represents the literal 80-character FITS keyword record.
Definition: keyword.hpp:129
constexpr Components GetComponents() const &noexcept
Get components of the keyword in its literal form with insignifant whitespaces removed.
Definition: keyword.hpp:206
constexpr KeywordType GetType() const noexcept
Definition: keyword.hpp:174
std::string_view GetRecord() const &noexcept
Definition: keyword.cpp:532
constexpr KeywordNameView GetName() const &noexcept
Query logical keyword name.
Definition: keyword.hpp:190
Contains data structure for FITS keywords.
Scope
Rule scope.
Definition: keywordEx.hpp:27
Operator
Rule operator.
Definition: keywordEx.hpp:22
std::optional< Operator > KeywordMatch(fits::KeywordNameView kw, Rule const &rule)
Determines if keyword match rule and if yes, whether it matches;.
Definition: keywordEx.cpp:117
Rule ParseEx(std::string_view ex)
Parse expression of the form documented in KeywordEx.
Definition: keywordEx.cpp:17
std::string RegexReplace(std::string_view str, std::regex const &re, char const *fmt)
Definition: keywordEx.cpp:139
bool KeywordScopeMatch(fits::KeywordType kwtype, Scope scope)
Definition: keywordEx.cpp:95
Represents a keyword rule expression.
Definition: keywordEx.hpp:32
fits::KeywordVariant KeywordTransform(fits::KeywordVariant const &keyword, std::regex const &re, char const *fmt)
Transforms keyword name using regex.
Definition: keywordEx.cpp:164
bool KeywordMatch(fits::KeywordVariant const &keyword, KeywordEx const &ex)
Definition: keywordEx.cpp:150
constexpr KeywordNameView GetKeywordName(EsoKeyword const &keyword) noexcept
Get keyword name from keyword.
Definition: keyword.hpp:436
KeywordType
Type of FITS keyword.
Definition: keyword.hpp:68
@ Eso
An ESO hiearchical keyword.
Definition: keyword.hpp:76
@ Commentary
A commentary keyword, which are keywords that do not fall into the previous categories.
Definition: keyword.hpp:80
@ Value
A value keyword.
Definition: keyword.hpp:72
LiteralKeyword Format(KeywordVariant const &keyword)
Definition: keyword.cpp:782
std::variant< ValueKeyword, EsoKeyword, LiteralKeyword > KeywordVariant
The different variants of keywords that are supported.
Definition: keyword.hpp:409
BasicKeyword< ValueKeywordTraits > ValueKeyword
Standard FITS value keyword.
Definition: keyword.hpp:339
BasicKeyword< EsoKeywordTraits > EsoKeyword
ESO hiearchical keyword.
Definition: keyword.hpp:346
A type safe version of LiteralKeyword that consist of the three basic components of a FITS keyword ke...
Definition: keyword.hpp:275
std::string name
Trimmed keyword name.
Definition: keyword.hpp:315
std::optional< std::string > comment
Trimmed keyword comment.
Definition: keyword.hpp:320
std::string_view name
Definition: keyword.hpp:84