ifw-daq  3.0.0-pre2
IFW Data Acquisition modules
keywordEx.cpp
Go to the documentation of this file.
1 /**
2  * @file
3  * @ingroup daq_dpm_libmerge
4  * @copyright ESO - European Southern Observatory
5  */
6 #include <daq/dpm/keywordEx.hpp>
7 
8 #include <fmt/format.h>
9 #include <fmt/ostream.h>
10 #include <fnmatch.h>
11 
12 #include <daq/fits/keyword.hpp>
13 
14 namespace daq::dpm {
15 namespace detail {
16 
17 Rule 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 '+':
29  op = Operator::Include;
30  break;
31  case '-':
32  op = Operator::Exclude;
33  break;
34  default:
35  throw std::invalid_argument(
36  fmt::format("invalid operator '{}', must be one of '+' or '-': '{}'", *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  *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;
103  case Scope::Commentary:
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  */
117 std::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 
139 std::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 
147 KeywordEx::KeywordEx(std::string_view rule) : m_rules(1u, detail::ParseEx(rule)) {
148 }
149 
150 bool 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 
164 KeywordTransform(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 
171 KeywordTransform(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(fmt::format(
177  "Keyword transform failed for keyword `{}` with transform format `{}`", keyword, fmt)));
178  }
179 }
180 
182 KeywordTransform(fits::EsoKeyword const& keyword, std::regex const& re, char const* fmt) {
183  try {
184  auto new_name = detail::RegexReplace(keyword.name, re, fmt);
185  return fits::EsoKeyword(new_name, keyword.value, keyword.comment);
186  } catch (...) {
187  std::throw_with_nested(std::runtime_error(fmt::format(
188  "Keyword transform failed for keyword `{}` with transform format `{}`", keyword, fmt)));
189  }
190 }
191 
193 KeywordTransform(fits::LiteralKeyword const& keyword, std::regex const& re, char const* fmt) {
194  try {
195  std::string new_name = detail::RegexReplace(keyword.GetName().name, re, fmt);
196  auto components = keyword.GetComponents();
197  components.name = new_name;
198  auto new_kw = Format(components);
199  if (new_kw.GetType() != keyword.GetType()) {
200  throw std::runtime_error(
201  fmt::format("Keyword transform mutated keyword into a different type. Before: {}, "
202  "after: {}. Before transformation: `{}`, after: `{}`",
203  keyword.GetType(),
204  new_kw.GetType(),
205  keyword.GetRecord(),
206  new_kw.GetRecord()));
207  }
208  if (new_kw.GetComponents().value != components.value) {
209  throw std::runtime_error(fmt::format(
210  "Keyword transform mutated keyword such that the value field was affected "
211  " Before transformation: `{}`, after: `{}`",
212  keyword.GetRecord(),
213  new_kw.GetRecord()));
214  }
215  return new_kw;
216  } catch (...) {
217  std::throw_with_nested(std::runtime_error(
218  fmt::format("Keyword transform failed for keyword `{}` with transform format `{}`",
219  keyword.GetRecord(),
220  fmt)));
221  }
222 }
223 
224 } // 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:125
constexpr Components GetComponents() const &noexcept
Get components of the keyword in its literal form with insignifant whitespaces removed.
Definition: keyword.hpp:202
constexpr KeywordType GetType() const noexcept
Definition: keyword.hpp:170
std::string_view GetRecord() const &noexcept
Definition: keyword.cpp:415
constexpr KeywordNameView GetName() const &noexcept
Query logical keyword name.
Definition: keyword.hpp:186
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:427
KeywordType
Type of FITS keyword.
Definition: keyword.hpp:64
@ Eso
An ESO hiearchical keyword.
Definition: keyword.hpp:72
@ Commentary
A commentary keyword, which are keywords that do not fall into the previous categories.
Definition: keyword.hpp:76
@ Value
A value keyword.
Definition: keyword.hpp:68
LiteralKeyword Format(KeywordVariant const &keyword)
Definition: keyword.cpp:626
std::variant< ValueKeyword, EsoKeyword, LiteralKeyword > KeywordVariant
The different variants of keywords that are supported.
Definition: keyword.hpp:400
BasicKeyword< ValueKeywordTraits > ValueKeyword
Standard FITS value keyword.
Definition: keyword.hpp:330
BasicKeyword< EsoKeywordTraits > EsoKeyword
ESO hiearchical keyword.
Definition: keyword.hpp:337
A type safe version of LiteralKeyword that consist of the three basic components of a FITS keyword ke...
Definition: keyword.hpp:266
std::string name
Trimmed keyword name.
Definition: keyword.hpp:306
std::optional< std::string > comment
Trimmed keyword comment.
Definition: keyword.hpp:311
std::string_view name
Definition: keyword.hpp:80