14#include <boost/algorithm/string/join.hpp>
22constexpr char const* TypeStr() noexcept;
25constexpr
char const* TypeStr<std::uint64_t>() noexcept {
29constexpr char const* TypeStr<std::int64_t>() noexcept {
34constexpr char const* TypeStr<double>() noexcept {
39constexpr char const* TypeStr<bool>() noexcept {
44constexpr char const* TypeStr<std::string>() noexcept {
49constexpr auto DitDataType() noexcept -> core::dit::did::DataType;
52constexpr auto DitDataType<std::uint64_t>() noexcept -> core::dit::did::DataType {
53 return core::dit::did::DataType::INTEGER;
57constexpr auto DitDataType<std::int64_t>() noexcept -> core::dit::did::DataType {
58 return core::dit::did::DataType::INTEGER;
62constexpr auto DitDataType<std::string>() noexcept -> core::dit::did::DataType {
63 return core::dit::did::DataType::STRING;
67constexpr auto DitDataType<bool>() noexcept -> core::dit::did::DataType {
68 return core::dit::did::DataType::BOOL;
74void ValidateString(std::string_view name, std::string_view value) {
75 auto bad = [&](std::string_view reason) {
77 name, fmt::format(
"validating string value `{}` failed - {}", value, reason));
79 if (value.size() < 2u) {
80 bad(
"string not enclosed with `'`");
82 if (*value.begin() !=
'\'') {
83 bad(
"must begin with '");
85 if (*(value.end() - 1) !=
'\'') {
86 bad(
"must end with '");
89 bool unescaped_quote =
false;
90 for (
auto c : value.substr(1, value.size() - 2)) {
91 if (unescaped_quote && c !=
'\'') {
92 bad(
"quote `'` must be escaped as `''`");
95 unescaped_quote = !unescaped_quote;
96 }
else if (c <
' ' || c >
'~') {
98 bad(fmt::format(
"invalid character `{}`", c));
102 if (unescaped_quote) {
103 bad(
"quote `'` must be escaped as `''`");
107void ValidateInteger(std::string_view name, std::string_view value) {
109 auto [ptr, ec] = std::from_chars(value.begin(), value.end(), int_value);
110 if (ec != std::errc{}) {
113 fmt::format(
"parsing base-10 integer value `{}` failed with {}",
115 std::make_error_code(ec).message()));
116 }
else if (ptr != value.end()) {
119 fmt::format(
"parsing base-10 integer value `{}` failed as not all characters was "
125auto FormatFloat(
double value,
126 std::string_view format,
127 std::array<char, constants::RECORD_LENGTH>& buffer) -> std::string_view {
128 std::string fmt = std::string(
"%") + std::string(format);
129 auto sz = snprintf(buffer.data(), buffer.size(), fmt.c_str(), value);
130 return std::string_view(buffer.data(), sz);
133auto ReformatFloat(std::string_view name,
134 std::string_view value,
135 std::string_view format,
136 std::array<char, constants::RECORD_LENGTH>& buffer) -> std::string_view {
137 double float_value = 0.0;
138 auto [ptr, ec] = std::from_chars(value.begin(), value.end(), float_value);
139 if (ec != std::errc{}) {
142 fmt::format(
"parsing floating point value `{}` failed with {}",
144 std::make_error_code(ec).message()));
145 }
else if (ptr != value.end()) {
148 fmt::format(
"parsing floating point value `{}` failed as not all characters was "
152 if (format.empty()) {
156 return FormatFloat(float_value, format, buffer);
160 using core::dit::did::DataType;
161 std::string_view logical_name = keyword.
GetName().
name;
162 std::array<char, constants::RECORD_LENGTH> value_buf;
163 auto const components = keyword.GetComponents();
164 auto new_components = components;
167 switch (record.GetDataType()) {
168 case DataType::DOUBLE:
170 ReformatFloat(logical_name, components.value, record.GetFormat(), value_buf);
171 !new_value.empty()) {
172 new_components.value = new_value;
177 if (components.value !=
"T" && components.value !=
"F") {
180 fmt::format(
"dictionary specifies value to be boolean but `{}` is not `T` or `F`",
184 case DataType::INTEGER:
185 ValidateInteger(logical_name, components.value);
186 case DataType::STRING:
188 ValidateString(logical_name, components.value);
200 if (keyword.GetComponents().comment.empty() && !record.GetComment().empty()) {
201 new_components.comment = record.GetComment();
205 return Format(new_components);
210 std::optional<std::string>
const& opt_comment,
212 namespace did = core::dit::did;
213 if (record.GetDataType() != did::DataType::DOUBLE) {
215 fmt::format(
"dictionary specifies value to be {} but value is {}",
216 DataTypeToStr(record.GetDataType()),
217 TypeStr<
decltype(value)>()));
219 auto comment = opt_comment.value_or(record.GetComment());
220 if (
auto const& fmt = record.GetFormat(); !fmt.empty()) {
222 std::array<char, constants::RECORD_LENGTH> buf;
223 return UntypedFormat(name, FormatFloat(value, fmt, buf), comment);
230template <
class Trait>
236 using T = std::decay_t<
decltype(value)>;
238 if constexpr (std::is_same_v<T, double>) {
239 return CheckedFormat(keyword.GetName(), value, keyword.comment, record);
241 if (DitDataType<T>() != record.GetDataType()) {
243 keyword.GetName().name,
244 fmt::format(
"dictionary specifies value to be {} but value is {}",
245 core::dit::did::DataTypeToStr(record.GetDataType()),
249 if (!keyword.comment && !record.GetComment().empty()) {
251 copy.comment = record.GetComment();
264 std::for_each(dictionaries.begin(), dictionaries.end(), [&](std::string
const& path) {
267 std::copy(dictionaries.begin(), dictionaries.end(), std::back_inserter(m_dictionaries));
269 char const* cfgpath = std::getenv(
"CFGPATH");
270 if (cfgpath ==
nullptr) {
273 std::throw_with_nested(
274 std::runtime_error(fmt::format(
"Failed to load dictionaries using $CFGPATH={}", cfgpath)));
279 core::dit::did::Record record;
281 std::string(name.name), record,
true ,
false )) {
287 return m_dictionaries;
290 return boost::algorithm::join(m_dictionaries,
", ");
Indicates keyword is invalid for some reason.
Represents the literal 80-character FITS keyword record.
constexpr KeywordNameView GetName() const &noexcept
Query logical keyword name.
Indicates keyword is unknown and cannot be formatted.
constexpr KeywordNameView GetKeywordName(EsoKeyword const &keyword) noexcept
Get keyword name from keyword.
LiteralKeyword Format(KeywordVariant const &keyword)
auto FormatFitsValue(KeywordNameView name, BasicKeywordBase::ValueType const &value) -> std::string
Format keyword value using built-in rules such that it can be used as a component when formatting a c...
auto UntypedFormat(KeywordNameView name, std::string_view value, std::string_view comment) -> LiteralKeyword
Untyped formatting where value already has been formatted correctly.
std::variant< ValueKeyword, EsoKeyword, LiteralKeyword > KeywordVariant
The different variants of keywords that are supported.
A type safe version of LiteralKeyword that consist of the three basic components of a FITS keyword ke...