...
This commit is contained in:
parent
c5aa3dc495
commit
0b7261a431
@ -104,7 +104,7 @@ struct Asibfm700MountConfigFileDefailts {
|
||||
// prohibited zones
|
||||
std::string pzMinAltitude{"10.0"}; // minimal altitude in degrees
|
||||
std::string pzLimitSwitchHAMin{""}; // HA-axis limit switch minimal value
|
||||
std::string pzLimitSwitchHAMaz{""}; // HA-axis limit switch maximal value
|
||||
std::string pzLimitSwitchHAMax{""}; // HA-axis limit switch maximal value
|
||||
|
||||
// hardware
|
||||
std::string hwMaxRateHA{""}; // maximal moving rate along HA-axis (Y-axis of Sidereal servo microcontroller)
|
||||
|
||||
@ -2,120 +2,456 @@
|
||||
|
||||
/**/
|
||||
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <mcc_angle.h>
|
||||
#include <mcc_pcm.h>
|
||||
#include <mcc_utils.h>
|
||||
|
||||
namespace asibfm700
|
||||
{
|
||||
|
||||
|
||||
// configuration record:
|
||||
// keyword
|
||||
// value
|
||||
// conversion function (deserializer)
|
||||
/* A SIMPLE "KEYWORD - VALUE" HOLDER CLASS SUITABLE TO STORE SOME APPLICATION CONFIGURATION */
|
||||
|
||||
|
||||
// to follow std::variant requirements (not references, not array, not void)
|
||||
template <typename T>
|
||||
concept variant_valid_type_c = requires { !std::is_array_v<T> && !std::is_void_v<T> && !std::is_reference_v<T>; };
|
||||
|
||||
// configuration record
|
||||
template <typename T>
|
||||
concept config_record_c = requires(T t) {
|
||||
requires std::same_as<decltype(t.key), std::string_view>; // keyword
|
||||
t.value; // value
|
||||
// method to convert from string to value
|
||||
{ t.cnv_func(std::declval<std::string_view>(), std::declval<decltype(t.value)&>()) } -> std::convertible_to<bool>;
|
||||
requires variant_valid_type_c<decltype(t.value)>; // value
|
||||
};
|
||||
|
||||
// simple minimal-requirement configuration record class
|
||||
template <variant_valid_type_c T>
|
||||
struct simple_config_record_t {
|
||||
std::string_view key;
|
||||
T value;
|
||||
};
|
||||
|
||||
// table of records (std::tuple<config_record_c...>)
|
||||
//
|
||||
// description of config (a std::tuple of "config_record_c"s)
|
||||
template <typename T>
|
||||
concept config_table_c = requires(T t) { []<config_record_c... Ts>(std::tuple<Ts...>) {}(t); };
|
||||
concept config_desc_c = requires(T t) { []<config_record_c... Ts>(std::tuple<Ts...>) {}(t); };
|
||||
|
||||
|
||||
template <config_table_c TabT>
|
||||
class ConfigTable
|
||||
template <config_desc_c DESCR_T>
|
||||
class ConfigHolder
|
||||
{
|
||||
protected:
|
||||
TabT _cfgTable;
|
||||
/* helper definitions */
|
||||
|
||||
std::array<size_t, std::tuple_size_v<TabT>> _keyHashes{};
|
||||
// deduce unique value types of the given config records
|
||||
template <config_desc_c TplT>
|
||||
struct deduce_val_types;
|
||||
|
||||
bool keyExists(size_t hash)
|
||||
template <config_record_c RT>
|
||||
struct deduce_val_types<std::tuple<RT>> {
|
||||
using value_type_t = std::tuple<decltype(RT::value)>;
|
||||
};
|
||||
|
||||
template <config_record_c RT, config_record_c... RTs>
|
||||
struct deduce_val_types<std::tuple<RT, RTs...>> {
|
||||
using value_type_t =
|
||||
std::conditional_t<(std::same_as<RT, RTs> || ...),
|
||||
typename deduce_val_types<std::tuple<RTs...>>::value_type_t,
|
||||
decltype(std::tuple_cat(
|
||||
std::declval<std::tuple<decltype(RT::value)>>(),
|
||||
std::declval<typename deduce_val_types<std::tuple<RTs...>>::value_type_t>()))>;
|
||||
};
|
||||
|
||||
template <config_desc_c TplT>
|
||||
using deduce_val_types_t = typename deduce_val_types<TplT>::value_type_t;
|
||||
|
||||
// deduce std::variant type from std::tuple element types
|
||||
template <mcc::traits::mcc_tuple_c TplT>
|
||||
struct variant_from_tuple;
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
struct variant_from_tuple<std::tuple<T, Ts...>> {
|
||||
using variant_t = std::variant<T, Ts...>;
|
||||
};
|
||||
|
||||
template <mcc::traits::mcc_tuple_c TplT>
|
||||
using variant_from_tuple_t = typename variant_from_tuple<TplT>::variant_t;
|
||||
|
||||
|
||||
public:
|
||||
static constexpr char COMMENT_SYMBOL = '#';
|
||||
static constexpr char KEY_VALUE_DELIM = '=';
|
||||
static constexpr char VALUE_ARRAY_DELIM = ',';
|
||||
|
||||
|
||||
// very simple de-serializer (only numbers and strings)
|
||||
inline static auto defaultDeserializeFunc = [](this auto&& self, std::string_view str, auto& value) {
|
||||
using value_t = std::decay_t<decltype(value)>;
|
||||
|
||||
if constexpr (std::is_arithmetic_v<value_t>) {
|
||||
auto v = mcc::utils::numFromStr<value_t>(str);
|
||||
if (!v.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = v.value();
|
||||
} else if constexpr (mcc::traits::mcc_output_char_range<value_t>) {
|
||||
value_t r;
|
||||
std::ranges::copy(str, std::back_inserter(r));
|
||||
value = r;
|
||||
} else if constexpr (std::ranges::range<value_t>) {
|
||||
using el_t = std::ranges::range_value_t<value_t>;
|
||||
|
||||
if constexpr (std::is_reference_v<el_t> || std::is_const_v<el_t>) { // no reference or constants allowed
|
||||
return false;
|
||||
}
|
||||
|
||||
value_t r;
|
||||
el_t elem;
|
||||
|
||||
auto els = std::views::split(str, VALUE_ARRAY_DELIM);
|
||||
|
||||
for (auto const& el : els) {
|
||||
// if (std::forward<decltype(self)>(self)(std::string_view(el), elem)) {
|
||||
if (std::forward<decltype(self)>(self)(mcc::utils::trimSpaces(el), elem)) {
|
||||
std::back_inserter(r) = elem;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
value = r;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
ConfigHolder(DESCR_T desc)
|
||||
{
|
||||
for (auto& h : _keyHashes) {
|
||||
if (h == hash) {
|
||||
return true;
|
||||
[desc = std::move(desc), this]<size_t... Is>(std::index_sequence<Is...>) {
|
||||
((_configDB[std::get<Is>(desc).key] = std::get<Is>(desc).value), ...);
|
||||
}(std::make_index_sequence<std::tuple_size_v<DESCR_T>>());
|
||||
}
|
||||
|
||||
virtual ~ConfigHolder() = default;
|
||||
|
||||
// deser_func - de-serialization function, i.e., a conversional function
|
||||
// from string-representation to some value
|
||||
//
|
||||
// DeserFuncT is a type of callable with signature:
|
||||
// bool deser_func(std::string_view str, value_type& val)
|
||||
// where
|
||||
// str - input (serialized) string-representation of configuration
|
||||
// item value
|
||||
// 'value_type' - must take into account all possible value types
|
||||
// in the input configuration description (see constructor)
|
||||
//
|
||||
// most suitable implementation is a generic lambda function, i.e.:
|
||||
// auto deser_func(std::string_view str, auto& cnv_val)->bool {
|
||||
// ...
|
||||
// };
|
||||
template <std::ranges::contiguous_range R, typename DeserFuncT>
|
||||
std::error_code parse(const R& buffer, DeserFuncT&& deser_func)
|
||||
requires std::same_as<std::remove_cvref_t<std::ranges::range_value_t<R>>, char>
|
||||
{
|
||||
if constexpr (std::is_array_v<std::decay_t<R>>) { // char*, const char*
|
||||
return parse(std::string_view{std::forward<R>(buffer)}, std::forward<DeserFuncT>(deser_func));
|
||||
}
|
||||
|
||||
auto curr_buffer = std::string_view(buffer.begin(), buffer.end());
|
||||
std::string_view key, value;
|
||||
bool buffer_end = false;
|
||||
|
||||
do {
|
||||
auto it = std::ranges::find(curr_buffer, '\n');
|
||||
if (it == curr_buffer.end()) {
|
||||
buffer_end = true;
|
||||
}
|
||||
|
||||
auto sv =
|
||||
mcc::utils::trimSpaces(std::string_view(curr_buffer.begin(), it), mcc::utils::TrimType::TRIM_LEFT);
|
||||
|
||||
curr_buffer = {it + 1, curr_buffer.end()};
|
||||
|
||||
if (sv.size() && (sv[0] != COMMENT_SYMBOL)) {
|
||||
it = std::ranges::find(sv, KEY_VALUE_DELIM);
|
||||
if (it != sv.begin()) { // ignore an empty key
|
||||
key = mcc::utils::trimSpaces(std::string_view(sv.begin(), it), mcc::utils::TrimType::TRIM_RIGHT);
|
||||
auto rec_it = _configDB.find(key);
|
||||
if (rec_it != _configDB.end()) { // ignore key if it is not in description
|
||||
value =
|
||||
mcc::utils::trimSpaces(std::string_view(it + 1, sv.end()), mcc::utils::TrimType::TRIM_BOTH);
|
||||
|
||||
bool ok = forIndex(rec_it->second, value, std::forward<DeserFuncT>(deser_func),
|
||||
rec_it->second.index());
|
||||
|
||||
if (!ok) {
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} while (!buffer_end);
|
||||
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template <std::ranges::contiguous_range R>
|
||||
std::error_code parse(const R& buffer)
|
||||
{
|
||||
return parse(buffer, defaultDeserializeFunc);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::expected<T, std::error_code> value(std::string_view key)
|
||||
{
|
||||
auto it = _configDB.find(key);
|
||||
if (it == _configDB.end()) {
|
||||
return std::unexpected(std::make_error_code(std::errc::argument_out_of_domain));
|
||||
}
|
||||
|
||||
std::expected<T, std::error_code> res;
|
||||
|
||||
std::visit(
|
||||
[&res](auto&& val) {
|
||||
using v_t = std::decay_t<decltype(val)>;
|
||||
|
||||
if constexpr (std::convertible_to<v_t, T>) {
|
||||
res = static_cast<T>(std::forward<decltype(val)>(val));
|
||||
} else if constexpr (std::constructible_from<T, v_t>) {
|
||||
res = T{std::forward<decltype(val)>(val)};
|
||||
} else {
|
||||
res = std::unexpected(std::make_error_code(std::errc::invalid_argument));
|
||||
}
|
||||
},
|
||||
it->second);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unordered_map<std::string_view, variant_from_tuple_t<deduce_val_types_t<DESCR_T>>> _configDB;
|
||||
|
||||
template <size_t I = 0, typename FuncT, typename... Ts>
|
||||
bool forIndex(std::variant<Ts...>& var, std::string_view s, FuncT&& func, size_t idx)
|
||||
{
|
||||
if constexpr (I < sizeof...(Ts)) {
|
||||
if (I == idx) {
|
||||
using v_t = std::tuple_element_t<I, std::tuple<Ts...>>;
|
||||
v_t val;
|
||||
bool ok = std::forward<FuncT>(func)(s, val);
|
||||
if (ok) {
|
||||
var = val;
|
||||
}
|
||||
|
||||
return ok;
|
||||
} else {
|
||||
return forIndex<I + 1>(var, s, std::forward<FuncT>(func), idx);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr char COMMENT_SYMBOL = '#';
|
||||
static constexpr char KEY_VALUE_DELIM = '=';
|
||||
static constexpr char VALUE_VALUE_DELIM = ',';
|
||||
|
||||
ConfigTable(const TabT& init_cfg) : _cfgTable(init_cfg)
|
||||
{
|
||||
_keyHashes = [this]<size_t... Is>(std::index_sequence<Is...>) {
|
||||
return std::array{mcc::utils::FNV1aHash(std::get<Is>(_cfgTable).key)...};
|
||||
}(std::make_index_sequence<std::tuple_size_v<TabT>>());
|
||||
}
|
||||
|
||||
virtual ~ConfigTable() = default;
|
||||
|
||||
|
||||
std::error_code parse(std::ranges::contiguous_range auto const& buffer)
|
||||
requires std::same_as<std::remove_cvref_t<std::ranges::range_value_t<decltype(buffer)>>, char>
|
||||
{
|
||||
std::string_view sv, curr_buffer{buffer.begin(), buffer.end()};
|
||||
|
||||
do {
|
||||
auto r = std::ranges::find(curr_buffer, '\n');
|
||||
|
||||
sv = mcc::utils::trimSpaces(std::string_view(curr_buffer.begin(), r), mcc::utils::TrimType::TRIM_LEFT);
|
||||
|
||||
if (sv.size()) {
|
||||
if (sv[0] == COMMENT_SYMBOL) { // comment string
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = std::ranges::find(sv, KEY_VALUE_DELIM);
|
||||
if (it == sv.begin()) { // empty key! skip!
|
||||
continue;
|
||||
}
|
||||
|
||||
auto key = mcc::utils::trimSpaces(std::string_view{sv.begin(), it}, mcc::utils::TrimType::TRIM_RIGHT);
|
||||
if (!keyExists(mcc::utils::FNV1aHash(key))) {
|
||||
}
|
||||
|
||||
} // only spaces
|
||||
|
||||
curr_buffer = {++r, buffer.end()};
|
||||
} while (!curr_buffer.empty());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
bool value(mcc::traits::mcc_input_char_range auto const& key, T& val)
|
||||
{
|
||||
const auto* kptr = &key;
|
||||
const auto* vptr = &val;
|
||||
|
||||
return [kptr, vptr, this]<size_t... Is>(std::index_sequence<Is...>) {
|
||||
return ([&kptr, vptr, this]() {
|
||||
if constexpr (std::convertible_to<decltype(std::get<Is>(_cfgTable).value), T>) {
|
||||
if (keyExists(mcc::utils::FNV1aHash(*kptr))) {
|
||||
*vptr = std::get<Is>(_cfgTable).value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}() || ...);
|
||||
}(std::make_index_sequence<std::tuple_size_v<TabT>>());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* ASTOROSIB FM700 MOUNT CONFIGURATION CLASS */
|
||||
|
||||
// configuration description and its defaults
|
||||
static auto Asibfm700MountConfigDefaults = std::make_tuple(
|
||||
// main cycle period in millisecs
|
||||
simple_config_record_t{"hardwarePollingPeriod", std::chrono::milliseconds{100}},
|
||||
|
||||
/* geographic coordinates of the observation site */
|
||||
|
||||
// site latitude in degrees
|
||||
simple_config_record_t{"siteLatitude", mcc::MccAngle(43.646711_degs)},
|
||||
|
||||
// site longitude in degrees
|
||||
simple_config_record_t{"siteLongitude", mcc::MccAngle(41.440732_degs)},
|
||||
|
||||
// site elevation in meters
|
||||
simple_config_record_t{"siteElevation", 2070.0},
|
||||
|
||||
/* celestial coordinate transformation */
|
||||
|
||||
// wavelength at which refraction is calculated (in mkm)
|
||||
simple_config_record_t{"refractWavelength", 0.55},
|
||||
|
||||
// an empty filename means default precompiled string
|
||||
simple_config_record_t{"leapSecondFilename", std::string()},
|
||||
|
||||
// an empty filename means default precompiled string
|
||||
simple_config_record_t{"bulletinAFilename", std::string()},
|
||||
|
||||
/* pointing correction model */
|
||||
|
||||
// PCM default type
|
||||
simple_config_record_t{"pcmType", mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY},
|
||||
|
||||
// PCM geometrical coefficients
|
||||
simple_config_record_t{"pcmGeomCoeffs", std::vector<double>{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}},
|
||||
|
||||
// PCM B-spline degrees
|
||||
simple_config_record_t{"pcmBsplineDegree", std::vector<size_t>{3, 3}},
|
||||
|
||||
// PCM B-spline knots along X-axis (HA-angle or azimuth). By default from 0 to 2*PI radians
|
||||
simple_config_record_t{"pcmBsplineXknots",
|
||||
std::vector<double>{0.0, 0.6981317, 1.3962634, 2.0943951, 2.7925268, 3.4906585, 4.1887902,
|
||||
4.88692191, 5.58505361, 6.28318531}},
|
||||
|
||||
// PCM B-spline knots along Y-axis (declination or zenithal distance). By default from -PI/6 to PI/2 radians
|
||||
simple_config_record_t{"pcmBsplineYknots",
|
||||
std::vector<double>{-0.52359878, -0.29088821, -0.05817764, 0.17453293, 0.40724349,
|
||||
0.63995406, 0.87266463, 1.10537519, 1.33808576, 1.57079633}},
|
||||
|
||||
// PCM B-spline coeffs for along X-axis (HA-angle or azimuth)
|
||||
simple_config_record_t{"pcmBsplineXcoeffs", std::vector<double>{}},
|
||||
|
||||
// PCM B-spline coeffs for along Y-axis (declination or zenithal distance)
|
||||
simple_config_record_t{"pcmBsplineYcoeffs", std::vector<double>{}},
|
||||
|
||||
|
||||
/* slewing and tracking parameters */
|
||||
|
||||
// arcseconds per second
|
||||
simple_config_record_t{"sideralRate", 15.0410686},
|
||||
|
||||
// timeout for telemetry updating in milliseconds
|
||||
simple_config_record_t{"telemetryTimeout", std::chrono::milliseconds(3000)},
|
||||
|
||||
// minimal allowed time in seconds to prohibited zone
|
||||
simple_config_record_t{"minTimeToPZone", std::chrono::seconds(10)},
|
||||
|
||||
// a time interval to update prohibited zones related quantities (millisecs)
|
||||
simple_config_record_t{"updatingPZoneInterval", std::chrono::milliseconds(5000)},
|
||||
|
||||
// coordinates difference in arcsecs to stop slewing
|
||||
simple_config_record_t{"slewToleranceRadius", 5.0},
|
||||
|
||||
// target-mount coordinate difference in arcsecs to start adjusting of slewing
|
||||
simple_config_record_t{"adjustCoordDiff", 50.0},
|
||||
|
||||
// minimum time in millisecs between two successive adjustments
|
||||
simple_config_record_t{"adjustCycleInterval", std::chrono::milliseconds(300)},
|
||||
|
||||
// slew process timeout in seconds
|
||||
simple_config_record_t{"slewTimeout", std::chrono::seconds(3600)},
|
||||
|
||||
// a time shift into future to compute target position in future (UT1-scale time duration, millisecs)
|
||||
simple_config_record_t{"timeShiftToTargetPoint", std::chrono::milliseconds(10000)},
|
||||
|
||||
// minimum time in millisecs between two successive tracking corrections
|
||||
simple_config_record_t{"trackingCycleInterval", std::chrono::milliseconds(300)},
|
||||
|
||||
|
||||
/* prohibited zones */
|
||||
|
||||
// minimal altitude
|
||||
simple_config_record_t{"pzMinAltitude", mcc::MccAngle(10.0_degs)},
|
||||
|
||||
// HA-axis limit switch minimal value
|
||||
simple_config_record_t{"pzLimitSwitchHAMin", mcc::MccAngle(-170.0_degs)},
|
||||
|
||||
// HA-axis limit switch maximal value
|
||||
simple_config_record_t{"pzLimitSwitchHAMax", mcc::MccAngle(170.0_degs)},
|
||||
|
||||
// DEC-axis limit switch minimal value
|
||||
simple_config_record_t{"pzLimitSwitchDecMin", mcc::MccAngle(-90.0_degs)},
|
||||
|
||||
// DEC-axis limit switch maximal value
|
||||
simple_config_record_t{"pzLimitSwitchDecMax", mcc::MccAngle(90.0_degs)},
|
||||
|
||||
|
||||
/* hardware-related */
|
||||
|
||||
// maximal moving rate (degrees per second) along HA-axis (Y-axis of Sidereal servo microcontroller)
|
||||
simple_config_record_t{"hwMaxRateHA", mcc::MccAngle(5.0_degs)},
|
||||
|
||||
// maximal moving rate (degrees per second) along DEC-axis (X-axis of Sidereal servo microcontroller)
|
||||
simple_config_record_t{"hwMaxRateDEC", mcc::MccAngle(5.0_degs)}
|
||||
|
||||
);
|
||||
|
||||
|
||||
|
||||
class Asibfm700MountConfig : protected ConfigHolder<decltype(Asibfm700MountConfigDefaults)>
|
||||
{
|
||||
using base_t = ConfigHolder<decltype(Asibfm700MountConfigDefaults)>;
|
||||
|
||||
public:
|
||||
using base_t::value;
|
||||
|
||||
Asibfm700MountConfig() : base_t(Asibfm700MountConfigDefaults) {}
|
||||
|
||||
~Asibfm700MountConfig() = default;
|
||||
|
||||
std::error_code load(const std::filesystem::path& path)
|
||||
{
|
||||
std::string buffer;
|
||||
|
||||
std::error_code ec;
|
||||
auto sz = std::filesystem::file_size(path, ec);
|
||||
|
||||
if (!ec && sz) {
|
||||
std::ifstream fst(path);
|
||||
|
||||
try {
|
||||
buffer.resize(sz);
|
||||
|
||||
fst.read(buffer.data(), sz);
|
||||
} catch (std::ios_base::failure const& ex) {
|
||||
return ex.code();
|
||||
} catch (...) {
|
||||
return std::make_error_code(std::errc::not_enough_memory);
|
||||
}
|
||||
|
||||
fst.close();
|
||||
} else {
|
||||
return ec;
|
||||
}
|
||||
|
||||
return base_t::parse(buffer, deserializer);
|
||||
}
|
||||
|
||||
protected:
|
||||
inline static auto deserializer = [](std::string_view str, auto& value) {
|
||||
using value_t = std::decay_t<decltype(value)>;
|
||||
|
||||
bool ok;
|
||||
|
||||
if constexpr (std::is_arithmetic_v<value_t> || std::ranges::output_range<value_t, char> ||
|
||||
std::ranges::range<value_t>) {
|
||||
return base_t::defaultDeserializeFunc(str, value);
|
||||
} else if constexpr (mcc::traits::mcc_time_duration_c<value_t>) {
|
||||
typename value_t::rep vd;
|
||||
|
||||
ok = base_t::defaultDeserializeFunc(str, vd);
|
||||
if (ok) {
|
||||
value = value_t{vd};
|
||||
}
|
||||
} else if constexpr (std::same_as<value_t, mcc::MccAngle>) { // assume here all angles are in degrees
|
||||
double vd;
|
||||
ok = base_t::defaultDeserializeFunc(str, vd);
|
||||
if (ok) {
|
||||
value = mcc::MccAngle(vd, mcc::MccDegreeTag{});
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ok;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace asibfm700
|
||||
|
||||
@ -92,5 +92,9 @@ if (WITH_TESTS)
|
||||
target_include_directories(${CTTE_TEST_APP} PRIVATE ${ERFA_INCLUDE_DIR})
|
||||
target_link_libraries(${CTTE_TEST_APP} ERFA_LIB bsplines)
|
||||
|
||||
set(CFG_TEST_APP cfg_test)
|
||||
add_executable(${CFG_TEST_APP} tests/cfg_test.cpp)
|
||||
target_link_libraries(${CFG_TEST_APP} PRIVATE mcc)
|
||||
|
||||
enable_testing()
|
||||
endif()
|
||||
|
||||
54
mcc/tests/cfg_test.cpp
Normal file
54
mcc/tests/cfg_test.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "../../asibfm700/asibfm700_configfile.h"
|
||||
|
||||
template <typename VT>
|
||||
struct rec_t {
|
||||
std::string_view key;
|
||||
VT value;
|
||||
};
|
||||
|
||||
static std::string_view cfg_str = R"--(A = 11
|
||||
B=3.3
|
||||
# this is comment
|
||||
C = WWWWWeeeWWWW
|
||||
|
||||
E = 10,20, 40, 32
|
||||
)--";
|
||||
|
||||
int main()
|
||||
{
|
||||
auto desc = std::make_tuple(rec_t{"A", 1}, rec_t{"B", 2.2}, rec_t{"C", std::string("EEE")}, rec_t{"D", 3.3},
|
||||
rec_t{"E", std::vector<int>{1, 2, 3}});
|
||||
|
||||
asibfm700::ConfigHolder ch(desc);
|
||||
|
||||
// auto err = ch.parse(cfg_str, [](auto s, auto &v) {
|
||||
// if constexpr (std::is_arithmetic_v<std::decay_t<decltype(v)>>) {
|
||||
// v = 77;
|
||||
// } else {
|
||||
// v = std::string{s.begin(), s.end()};
|
||||
// }
|
||||
|
||||
// return true;
|
||||
// });
|
||||
|
||||
auto err = ch.parse(cfg_str);
|
||||
|
||||
auto v = ch.value<float>("A");
|
||||
|
||||
std::cout << v.value() << "\n";
|
||||
|
||||
// auto v2 = ch.value<std::string>("D");
|
||||
auto v2 = ch.value<std::string>("C");
|
||||
std::cout << v2.value_or("<no value>") << "\n";
|
||||
|
||||
auto v3 = ch.value<std::vector<int>>("E");
|
||||
std::cout << "[";
|
||||
for (auto& el : v3.value_or({0, 0, 0})) {
|
||||
std::cout << el << " ";
|
||||
}
|
||||
std::cout << "]\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user