15 #include <fmt/format.h>
19 inline constexpr
bool always_false_v =
false;
21 char const* TypeStr() noexcept;
23 char const* TypeStr<uint64_t>() noexcept {
27 char const* TypeStr<int64_t>() noexcept {
31 char const* TypeStr<double>() noexcept {
37 using namespace std::string_view_literals;
39 [&](
auto& value) -> std::string {
40 using T = std::decay_t<decltype(value)>;
41 if constexpr (std::is_same_v<T, std::string>) {
44 return fmt::format(
"'{: <8s}'", value);
46 return fmt::format(
"'{}'", value);
48 }
else if constexpr (std::is_same_v<T, bool>) {
49 return value ?
"T" :
"F";
50 }
else if constexpr (std::is_same_v<T, double>) {
52 return fmt::format(
"{:g}", value);
54 return fmt::format(
"{:.1f}", value);
56 }
else if constexpr (std::is_same_v<T, int64_t>) {
57 return fmt::format(
"{:d}", value);
58 }
else if constexpr (std::is_same_v<T, uint64_t>) {
59 return fmt::format(
"{:d}", value);
61 static_assert(always_false_v<T>,
"non-exhaustive visitor!");
67 constexpr
char const* FMT_VALUEKW_SCALAR =
"{:<8}= {:>20}";
68 constexpr
char const* FMT_VALUEKW_STR =
"{:<8}= {:<20}";
69 constexpr
char const* FMT_ESOKW =
"HIERARCH ESO {} = {}";
70 constexpr
char const* FMT_ALL_COMMENT =
"{} / {}";
71 constexpr
char const* FMT_COMMENTARYKW =
"{:<8} {}";
77 LiteralKeyword InternalValueKeywordFormat(std::string_view name,
78 std::string_view value,
79 std::string_view comment,
82 auto formatted_name_value =
83 fmt::format(is_string ? FMT_VALUEKW_STR : FMT_VALUEKW_SCALAR, name, value);
84 if (formatted_name_value.size() > constants::RECORD_LENGTH) {
85 throw std::invalid_argument(fmt::format(
86 "Formatted name and value does not fit in keyword record. Length: {}, keyword: '{}'",
87 formatted_name_value.size(),
88 formatted_name_value));
90 auto formatted = fmt::format(FMT_ALL_COMMENT, formatted_name_value, comment);
91 auto view = std::string_view(formatted).substr(0u, fits::constants::RECORD_LENGTH);
92 return LiteralKeyword(view);
96 InternalEsoKeywordFormat(std::string_view name, std::string_view value, std::string_view comment) {
97 auto formatted_name_value = fmt::format(FMT_ESOKW, name, value);
98 if (formatted_name_value.size() > constants::RECORD_LENGTH) {
99 throw std::invalid_argument(fmt::format(
100 "Formatted name and value does not fit in keyword record. Length: {}, keyword: '{}'",
101 formatted_name_value.size(),
102 formatted_name_value));
104 auto formatted = fmt::format(FMT_ALL_COMMENT, formatted_name_value, comment);
105 auto view = std::string_view(formatted).substr(0u, fits::constants::RECORD_LENGTH);
106 return LiteralKeyword(view);
109 LiteralKeyword InternalCommentaryKeywordFormat(std::string_view name, std::string_view value) {
110 auto formatted_name_value = fmt::format(FMT_COMMENTARYKW, name, value);
111 if (formatted_name_value.size() > constants::RECORD_LENGTH) {
112 throw std::invalid_argument(fmt::format(
113 "Formatted name and value does not fit in keyword record. Length: {}, keyword: '{}'",
114 formatted_name_value.size(),
115 formatted_name_value));
117 return LiteralKeyword(formatted_name_value);
138 void ValidateKeywordName(KeywordNameView keyword) {
139 if (keyword.name.empty()) {
140 throw std::invalid_argument(
"Keyword name cannot be empty");
142 auto max_length = keyword.type ==
KeywordType::Eso ? constants::ESO_HIERARCH_MAX_NAME_LENGTH
143 : constants::KEYWORD_NAME_LENGTH;
144 if (keyword.name.size() > max_length) {
145 auto msg = fmt::format(
"Keyword name too long. Maximum is {} and provided name '{}' is {}",
148 keyword.name.size());
149 throw std::invalid_argument(msg);
151 auto is_valid_char = [](
char c) ->
bool {
152 return (c >=
'0' && c <=
'9') || (c >=
'A' && c <=
'Z') || c ==
' ' || c ==
'_' || c ==
'-';
155 if (!std::all_of(std::begin(keyword.name), std::end(keyword.name), is_valid_char)) {
156 throw std::invalid_argument(fmt::format(
157 "Keyword name '{}' contains illegal character. Valid charset is [A-Z0-9_-].",
162 constexpr
KeywordType ParseKeywordType(std::string_view record) noexcept {
163 if (record.size() >= constants::KEYWORD_NAME_LENGTH + constants::VALUE_INDICATOR.size()) {
165 if (record.substr(constants::KEYWORD_NAME_LENGTH, constants::VALUE_INDICATOR.size()) ==
166 constants::VALUE_INDICATOR) {
169 if (record.size() > constants::ESO_HIERARCH_PREFIX.size()) {
171 auto kw = record.substr(0u, constants::ESO_HIERARCH_PREFIX.size());
172 auto remainder = record.substr(constants::ESO_HIERARCH_PREFIX.size(), record.npos);
173 if (kw == constants::ESO_HIERARCH_PREFIX && remainder.find(
'=') != kw.npos) {
196 constexpr std::string_view ParseLogicalCategory(std::string_view name) noexcept {
197 using namespace std::literals::string_view_literals;
199 auto end = name.find_first_of(
" ="sv);
201 auto full_category = name.substr(0, end);
202 auto logical_end = full_category.find_last_not_of(
"0123456789"sv);
203 if (logical_end != std::string_view::npos) {
204 auto view = name.substr(0, logical_end + 1);
207 return full_category;
210 enum class TrimType { Name, Full };
217 constexpr std::string_view TrimBlanks(std::string_view str, TrimType trim) noexcept {
222 auto trim_pos = str.find_first_not_of(constants::BLANK_CHARS);
223 if (trim_pos == std::string_view::npos) {
225 return trim == TrimType::Name ? str.substr(0u, 1u) : std::string_view();
227 str.remove_prefix(trim_pos);
230 auto trim_pos = str.find_last_not_of(constants::BLANK_CHARS);
231 if (trim_pos != std::string_view::npos) {
232 str.remove_suffix(str.size() - trim_pos - 1);
244 constexpr std::pair<std::string_view, std::optional<std::string_view::size_type>>
245 NextToken(std::string_view str,
char delimiter, TrimType trim) noexcept {
246 auto delimiter_pos = str.find(delimiter);
247 std::optional<std::string_view::size_type> ret_pos;
248 if (delimiter_pos != std::string_view::npos) {
250 ret_pos = delimiter_pos + 1;
251 str.remove_suffix(str.size() - delimiter_pos);
253 auto trimmed = TrimBlanks(str, trim);
254 return {trimmed, ret_pos};
257 constexpr LiteralKeyword::Components
258 ParseValueKeywordComponents(std::array<char, 80>
const& record_array) {
259 auto record = std::string_view(&record_array[0], record_array.size());
260 LiteralKeyword::Components components{};
264 components.name = TrimBlanks(record.substr(0u, constants::KEYWORD_NAME_LENGTH), TrimType::Name);
265 record.remove_prefix(constants::KEYWORD_NAME_LENGTH + constants::VALUE_INDICATOR.size());
267 auto [value, comment_start] = NextToken(record,
'/', TrimType::Full);
268 components.value = value;
270 if (comment_start.has_value()) {
271 record.remove_prefix(*comment_start);
272 components.comment = TrimBlanks(record, TrimType::Full);
274 components.comment = std::string_view();
280 constexpr LiteralKeyword::Components
281 ParseEsoKeywordComponents(std::array<char, 80>
const& record_array) {
282 auto record = std::string_view(&record_array[0], record_array.size());
283 LiteralKeyword::Components components{};
286 record.remove_prefix(constants::ESO_HIERARCH_PREFIX.size());
288 auto [name, value_start] = NextToken(record,
'=', TrimType::Name);
289 components.name = name;
291 if (!value_start.has_value()) {
292 throw std::invalid_argument(
293 fmt::format(
"Invalid ESO HIERARCH keyword. No '=' value indicator found: {}", record));
295 record.remove_prefix(*value_start);
296 auto [value, comment_start] = NextToken(record,
'/', TrimType::Full);
298 throw std::invalid_argument(
299 fmt::format(
"Invalid ESO HIERARCH keyword. No value found: {}", record));
301 components.value = value;
303 if (comment_start.has_value()) {
304 record.remove_prefix(*comment_start);
305 components.comment = TrimBlanks(record, TrimType::Full);
307 components.comment = std::string_view();
313 constexpr LiteralKeyword::Components
314 ParseCommentaryKeywordComponents(std::array<char, 80>
const& record_array) {
315 auto record = std::string_view(&record_array[0], record_array.size());
316 LiteralKeyword::Components components{};
320 components.name = TrimBlanks(record.substr(0u, constants::KEYWORD_NAME_LENGTH), TrimType::Name);
321 record.remove_prefix(constants::KEYWORD_NAME_LENGTH);
323 components.value = TrimBlanks(record, TrimType::Full);
326 components.comment = std::string_view();
331 [[nodiscard]] LiteralKeyword::Components
332 ParseKeywordComponents(std::array<char, 80>
const& record) {
333 auto sv_record = std::string_view(&record[0], record.size());
334 auto type = ParseKeywordType(sv_record);
339 return ParseValueKeywordComponents(record);
341 return ParseEsoKeywordComponents(record);
343 return ParseCommentaryKeywordComponents(record);
345 std::cerr << __PRETTY_FUNCTION__ <<
": Invalid type: " << type << std::endl;
352 return lhs.name < rhs.name;
356 return lhs.type == rhs.type && lhs.name == rhs.name;
360 return !(lhs == rhs);
364 std::fill(std::begin(m_record), std::end(m_record),
' ');
365 m_components = ParseKeywordComponents(m_record);
366 ValidateKeywordName(
GetName());
370 : m_record(record), m_components(ParseKeywordComponents(m_record)) {
372 ValidateKeywordName(
GetName());
374 std::throw_with_nested(std::invalid_argument(
"Failed to construct LiteralKeyword"));
380 if (record.size() > constants::RECORD_LENGTH) {
381 throw std::invalid_argument(
382 "LiteralKeyword: Keyword with record > 80 chars is invalid");
384 std::fill(std::begin(m_record), std::end(m_record),
' ');
385 std::copy(std::begin(record), std::end(record), std::begin(m_record));
386 m_components = ParseKeywordComponents(m_record);
387 ValidateKeywordName(
GetName());
389 std::throw_with_nested(std::invalid_argument(
"Failed to construct LiteralKeyword"));
398 m_record = other.m_record;
400 auto make_offset = [&](std::string_view sv) -> std::string_view {
402 return std::string_view();
404 auto start_offset = sv.data() - other.m_record.data();
405 return std::string_view(m_record.data() + start_offset, sv.size());
407 m_components.name = make_offset(other.m_components.name);
408 m_components.value = make_offset(other.m_components.value);
409 m_components.comment = make_offset(other.m_components.comment);
410 m_components.type = other.m_components.type;
415 auto full = std::string_view(&m_record[0], m_record.size());
416 return TrimBlanks(full, TrimType::Name);
420 return lhs.GetName() < rhs.GetName();
424 return lhs.GetRecord() == rhs.GetRecord();
428 return lhs.GetRecord() != rhs.GetRecord();
437 template struct BasicKeyword<ValueKeywordTraits>;
438 template struct BasicKeyword<EsoKeywordTraits>;
440 template std::ostream&
443 template std::ostream&
446 template <
class Trait>
448 char const* string_value,
449 std::optional<std::string> comment_arg)
450 : name(std::move(name_arg)), value(std::string(string_value)), comment(std::move(comment_arg)) {
451 ValidateKeywordName(
GetName());
454 template <
class Trait>
457 std::optional<std::string> comment_arg)
458 : name(std::move(name_arg)), value(std::move(value_arg)), comment(std::move(comment_arg)) {
459 ValidateKeywordName(
GetName());
462 template <
class Trait>
464 return name == rhs.name && value == rhs.value && comment == rhs.comment;
467 template <
class Trait>
469 return !(*
this == rhs);
472 template <
class Trait>
474 return name < rhs.name;
478 if (lhs.index() != rhs.index()) {
482 [&](
auto const& lhs,
auto const& rhs) noexcept ->
bool {
483 return lhs.GetName() == rhs.GetName();
490 if (name.size() > FLEN_KEYWORD) {
491 throw std::invalid_argument(
"Keyword too long");
493 char record[FLEN_CARD] = {
' '};
494 std::copy(name.begin(), name.end(), &record[0]);
496 return static_cast<KeywordClass>(fits_get_keyclass(record));
500 return lhs.GetName() < rhs.GetName();
504 return lhs.GetName() < rhs.GetName();
507 return lhs.GetName() < rhs.GetName();
511 return lhs.GetName() < rhs.GetName();
515 return lhs.GetName() < rhs.GetName();
518 return lhs.GetName() < rhs.GetName();
523 [&](
auto const& var)
mutable {
524 using T = std::decay_t<decltype(var)>;
525 if constexpr (std::is_same_v<T, std::string>) {
526 os <<
"(str)'" << var <<
"'";
527 }
else if constexpr (std::is_same_v<T, bool>) {
528 os <<
"(bool)" << (var ?
"true" :
"false");
530 os <<
"(" << TypeStr<T>() <<
")" << var;
537 template <
class Trait>
539 os <<
"name='" << kw.
name <<
"', value=" << kw.
value <<
", comment=";
541 os <<
"'" << *kw.
comment <<
"'";
549 std::visit([&](
auto const& var)
mutable { os << var; }, kw);
554 for (
auto const& kw : from) {
555 if (
auto it = std::find_if(
556 to.begin(), to.end(), [&](
auto const& val) { return NameEquals(val, kw); });
558 if (policy == ConflictPolicy::Replace) {
570 KeywordVector::iterator position,
571 KeywordVector::const_iterator from_first,
572 KeywordVector::const_iterator from_last) {
575 auto num_elements = std::distance(from_first, from_last);
576 auto pred = [&](KeywordVector::value_type
const& kw) ->
bool {
577 return std::find_if(from_first, from_last, [&](
auto const& val) {
581 auto inserted_first = keywords.insert(position, from_first, from_last);
582 auto after_inserted_first = inserted_first;
583 std::advance(after_inserted_first, num_elements);
584 keywords.erase(std::remove_if(after_inserted_first, keywords.end(), pred), keywords.end());
585 keywords.erase(std::remove_if(keywords.begin(), inserted_first, pred), inserted_first);
591 auto value = FormatFitsValue(name, keyword.
value);
592 auto comment = keyword.
comment.value_or(
"");
593 return InternalEsoKeywordFormat(name.name, value, comment);
599 auto value = FormatFitsValue(name, keyword.
value);
600 auto comment = keyword.
comment.value_or(
"");
601 return InternalValueKeywordFormat(
602 name.name, value, comment, std::holds_alternative<std::string>(keyword.
value));
606 switch (keyword.
type) {
608 bool is_str = keyword.
value.find(
'\'') != std::string_view::npos;
609 return InternalValueKeywordFormat(keyword.
name, keyword.
value, keyword.
comment, is_str);
612 return InternalEsoKeywordFormat(keyword.
name, keyword.
value, keyword.
comment);
615 return InternalCommentaryKeywordFormat(keyword.
name, keyword.
value);
618 std::cerr << __PRETTY_FUNCTION__ <<
": Invalid type: " << keyword.
type << std::endl;
625 using T = std::decay_t<decltype(kw)>;
626 if constexpr (std::is_same_v<T, LiteralKeyword>) {
627 return Format(kw.GetComponents());
637 constexpr
bool SortEsoKeywordName(std::string_view lhs, std::string_view rhs) noexcept {
638 using namespace std::literals::string_view_literals;
640 auto lhs_cat = ParseLogicalCategory(lhs);
641 auto rhs_cat = ParseLogicalCategory(rhs);
642 if (lhs_cat == rhs_cat) {
647 std::array<std::string_view, 8> categories = {
648 "DPR"sv,
"OBS"sv,
"TPL"sv,
"GEN"sv,
"TEL"sv,
"ADA"sv,
"INS"sv,
"DET"sv};
649 auto lhs_idx = std::find(std::begin(categories), std::end(categories), lhs_cat);
650 auto rhs_idx = std::find(std::begin(categories), std::end(categories), rhs_cat);
652 if (lhs_idx != std::end(categories)) {
653 if (rhs_idx != std::end(categories)) {
656 return std::distance(lhs_idx, rhs_idx) > 0;
662 if (rhs_idx != std::end(categories)) {
674 auto lhs = lhs_kw.GetName();
675 auto rhs = rhs_kw.GetName();
696 return SortEsoKeywordName(lhs.name, rhs.name);
710 return lhs.name < rhs.name;
717 std::stable_sort(std::begin(keywords), std::end(keywords),
StandardLess{});
722 #if defined(UNIT_TEST)
726 static_assert(NextToken(
" ='date'",
'=', TrimType::Name) ==
727 std::pair<std::string_view, std::optional<std::string_view::size_type>>(
" ", 9));