Line data Source code
1 : /** \file indiCompRuleConfig.hpp
2 : * \brief Configuration of rules for the MagAO-X stateRuleEngine
3 : *
4 : * \ingroup stateRuleEngine_files
5 : */
6 :
7 : #ifndef stateRuleEngine_indiCompRuleConfig_hpp
8 : #define stateRuleEngine_indiCompRuleConfig_hpp
9 :
10 : #include <map>
11 :
12 : #include "indiCompRules.hpp"
13 :
14 : /// Structure to provide management of the rule and property maps
15 : /** This owns all pointers in the rule engine, and `delete`s them on destruction.
16 : */
17 : struct indiRuleMaps
18 : {
19 : typedef std::map<std::string, indiCompRule *> ruleMapT;
20 : typedef std::map<std::string, pcf::IndiProperty *> propMapT;
21 :
22 : ruleMapT rules;
23 : propMapT props;
24 :
25 20 : ~indiRuleMaps()
26 : {
27 20 : auto rit = rules.begin();
28 47 : while( rit != rules.end() )
29 : {
30 27 : delete rit->second;
31 27 : ++rit;
32 : }
33 :
34 20 : auto pit = props.begin();
35 43 : while( pit != props.end() )
36 : {
37 23 : delete pit->second;
38 23 : ++pit;
39 : }
40 20 : }
41 : };
42 :
43 : /* Structure used to hold ruleVal rule keys aside for final processing
44 : ruleVal rules can be created before the rules they link exist, so
45 : we hold the keys aside and set the pointers after all rules are created.
46 : */
47 : struct ruleRuleKeys
48 : {
49 : std::string rule1;
50 : std::string rule2;
51 : };
52 :
53 : /// Extract a property from a rule configuration
54 : /** Reads the property and element, adding the property to the property map if necessary.
55 : *
56 : * \throws mx::err::invalidconfig if the property is already in the map but of a different type
57 : */
58 24 : void extractRuleProp(
59 : pcf::IndiProperty **prop, ///< [out] pointer to the property, newly created or existing, which is in the map.
60 : std::string &element, ///< [out] the element name from the configuration
61 : indiRuleMaps &maps, ///< [in] contains the property map to which the property is added
62 : const std::string §ion, ///< [in] name of the section for this rule
63 : const std::string &propkey, ///< [in] the key for the property name
64 : const std::string &elkey, ///< [in] the key for the element name
65 : const pcf::IndiProperty::Type &type, ///< [in] the type of the property
66 : mx::app::appConfigurator &config ///< [in] the application configuration structure
67 : )
68 : {
69 24 : std::string property;
70 24 : config.configUnused( property, mx::app::iniFile::makeKey( section, propkey ) );
71 :
72 24 : if( maps.props.count( property ) > 0 )
73 : {
74 : // If the property already exists we just check if it's the right type
75 1 : if( maps.props[property]->getType() != type )
76 : {
77 : throw mx::exception( mx::error_t::invalidconfig,
78 0 : "property " + property + " exists but is not correct type" );
79 : }
80 :
81 1 : *prop = maps.props[property];
82 : }
83 : else
84 : {
85 : // Otherwise we create it
86 23 : *prop = new pcf::IndiProperty( type );
87 23 : maps.props.insert( std::pair<std::string, pcf::IndiProperty *>( { property, *prop } ) );
88 :
89 : ///\todo have to split device and propertyName
90 : }
91 :
92 24 : config.configUnused( element, mx::app::iniFile::makeKey( section, elkey ) );
93 24 : }
94 :
95 : /// \cond
96 : // strip leading and trailing whitespace and then opening and closing "". leaves spaces between "".
97 28 : inline void stripQuotesWS( std::string &str )
98 : {
99 28 : if( str.size() == 0 )
100 : {
101 28 : return;
102 : }
103 :
104 0 : if( str[0] != '\"' && str[0] != ' ' && str.back() != ' ' ) // get out fast if we can
105 : {
106 0 : return;
107 : }
108 :
109 : // strip white space at front
110 0 : size_t ns = str.find_first_not_of( " \t\r\n" );
111 0 : if( ns != std::string::npos && ns != 0 )
112 : {
113 0 : str.erase( 0, ns );
114 :
115 0 : if( str.size() == 0 )
116 : {
117 0 : return;
118 : }
119 : }
120 0 : else if( ns == std::string::npos ) // the rare all spaces
121 : {
122 0 : str = "";
123 0 : return;
124 : }
125 :
126 : // strip white space at back
127 0 : ns = str.find_last_not_of( " \t\r\n" );
128 0 : if( ns != std::string::npos && ns != str.size() - 1 )
129 : {
130 0 : str.erase( ns + 1 );
131 :
132 0 : if( str.size() == 0 )
133 : {
134 0 : return;
135 : }
136 : }
137 :
138 0 : if( str[0] == '\"' && str.back() == '\"' )
139 : {
140 0 : if( str.size() == 1 || str.size() == 2 )
141 : {
142 0 : str = "";
143 0 : return;
144 : }
145 0 : str.erase( str.size() - 1, 1 );
146 0 : str.erase( 0, 1 );
147 : }
148 0 : else if( str[0] == '\"' )
149 : {
150 0 : if( str.size() == 1 )
151 : {
152 0 : str = "";
153 0 : return;
154 : }
155 0 : str.erase( 0, 1 );
156 : }
157 0 : else if( str.back() == '\"' )
158 : {
159 0 : if( str.size() == 1 || str.size() == 2 )
160 : {
161 0 : str = "";
162 0 : return;
163 : }
164 0 : str.erase( str.size() - 1, 1 );
165 : }
166 : }
167 : /// \endcond
168 :
169 : /// Load the rule and properties maps for a rule engine from a configuration file
170 : /** ///\todo check for insertion failure
171 : * ///\todo add a constructor that has priority, message, and comparison, to reduce duplication
172 : */
173 19 : void loadRuleConfig( indiRuleMaps &maps, /**< [out] contains the rule and property maps in
174 : which to place the items found in config */
175 : std::map<std::string, ruleRuleKeys> &rrkMap, /**< [out] Holds the ruleVal rule keys aside for
176 : later post-processing*/
177 : mx::app::appConfigurator &config /**< [in] the application configuration structure */
178 : )
179 : {
180 19 : std::vector<std::string> sections;
181 :
182 19 : config.unusedSections( sections );
183 :
184 19 : if( sections.size() == 0 )
185 : {
186 3 : throw mx::exception( mx::error_t::invalidconfig, "no rules found in config" );
187 : }
188 :
189 43 : for( size_t i = 0; i < sections.size(); ++i )
190 : {
191 28 : bool ruleTypeSet = config.isSetUnused( mx::app::iniFile::makeKey( sections[i], "ruleType" ) );
192 :
193 : // If there is no ruleType then this isn't a rule
194 28 : if( !ruleTypeSet )
195 : {
196 0 : continue;
197 : }
198 :
199 : // If the rule already exists this is an error
200 28 : if( maps.rules.count( sections[i] ) != 0 )
201 : {
202 0 : throw mx::exception( mx::error_t::invalidconfig, "duplicate rule: " + sections[i] );
203 : }
204 :
205 28 : std::string ruleType;
206 56 : config.configUnused( ruleType, mx::app::iniFile::makeKey( sections[i], "ruleType" ) );
207 :
208 56 : std::string priostr = "none";
209 28 : config.configUnused( priostr, mx::app::iniFile::makeKey( sections[i], "priority" ) );
210 28 : rulePriority priority = string2priority( priostr );
211 :
212 28 : std::string message;
213 28 : config.configUnused( message, mx::app::iniFile::makeKey( sections[i], "message" ) );
214 28 : stripQuotesWS( message ); // strips "" and any leading/trailing whitespace
215 :
216 56 : std::string compstr = "Eq";
217 28 : config.configUnused( compstr, mx::app::iniFile::makeKey( sections[i], "comp" ) );
218 28 : ruleComparison comparison = string2comp( compstr );
219 :
220 28 : if( ruleType == numValRule::name )
221 : {
222 2 : numValRule *nvr = new numValRule;
223 2 : maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], nvr } ) );
224 :
225 2 : nvr->priority( priority );
226 2 : nvr->message( message );
227 2 : nvr->comparison( comparison );
228 :
229 2 : pcf::IndiProperty *prop = nullptr;
230 2 : std::string element;
231 :
232 8 : extractRuleProp(
233 4 : &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Number, config );
234 2 : nvr->property( prop );
235 2 : nvr->element( element );
236 :
237 2 : double target = nvr->target();
238 2 : config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
239 2 : nvr->target( target );
240 :
241 2 : double tol = nvr->tol();
242 2 : config.configUnused( tol, mx::app::iniFile::makeKey( sections[i], "tol" ) );
243 2 : nvr->tol( tol );
244 2 : }
245 26 : else if( ruleType == txtValRule::name )
246 : {
247 9 : txtValRule *tvr = new txtValRule;
248 9 : maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], tvr } ) );
249 :
250 9 : tvr->priority( priority );
251 9 : tvr->message( message );
252 9 : tvr->comparison( comparison );
253 :
254 9 : pcf::IndiProperty *prop = nullptr;
255 9 : std::string element;
256 :
257 36 : extractRuleProp(
258 18 : &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Text, config );
259 9 : tvr->property( prop );
260 9 : tvr->element( element );
261 :
262 9 : std::string target = tvr->target();
263 9 : config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
264 9 : tvr->target( target );
265 9 : }
266 17 : else if( ruleType == swValRule::name )
267 : {
268 3 : swValRule *svr = new swValRule;
269 3 : maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], svr } ) );
270 :
271 3 : svr->priority( priority );
272 3 : svr->message( message );
273 3 : svr->comparison( comparison );
274 :
275 3 : pcf::IndiProperty *prop = nullptr;
276 3 : std::string element;
277 :
278 12 : extractRuleProp(
279 6 : &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Switch, config );
280 3 : svr->property( prop );
281 3 : svr->element( element );
282 :
283 6 : std::string target = "On";
284 3 : config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
285 3 : svr->target( target );
286 3 : }
287 14 : else if( ruleType == timeDiffRule::name )
288 : {
289 2 : timeDiffRule *nvr = new timeDiffRule;
290 2 : maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], nvr } ) );
291 :
292 2 : nvr->priority( priority );
293 2 : nvr->message( message );
294 2 : nvr->comparison( comparison );
295 :
296 2 : pcf::IndiProperty *prop = nullptr;
297 2 : std::string element;
298 :
299 8 : extractRuleProp(
300 4 : &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Number, config );
301 2 : nvr->property( prop );
302 2 : nvr->element( element );
303 :
304 2 : double target = nvr->target();
305 2 : config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
306 2 : nvr->target( target );
307 :
308 2 : double tol = nvr->tol();
309 2 : config.configUnused( tol, mx::app::iniFile::makeKey( sections[i], "tol" ) );
310 2 : nvr->tol( tol );
311 2 : }
312 12 : else if( ruleType == elCompNumRule::name )
313 : {
314 1 : elCompNumRule *nvr = new elCompNumRule;
315 1 : maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], nvr } ) );
316 :
317 1 : nvr->priority( priority );
318 1 : nvr->message( message );
319 1 : nvr->comparison( comparison );
320 :
321 : pcf::IndiProperty *prop1;
322 1 : std::string element1;
323 :
324 4 : extractRuleProp(
325 2 : &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Number, config );
326 1 : nvr->property1( prop1 );
327 1 : nvr->element1( element1 );
328 :
329 : pcf::IndiProperty *prop2;
330 1 : std::string element2;
331 :
332 4 : extractRuleProp(
333 2 : &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Number, config );
334 1 : nvr->property2( prop2 );
335 1 : nvr->element2( element2 );
336 1 : }
337 11 : else if( ruleType == elCompTxtRule::name )
338 : {
339 1 : elCompTxtRule *tvr = new elCompTxtRule;
340 1 : maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], tvr } ) );
341 :
342 1 : tvr->priority( priority );
343 1 : tvr->message( message );
344 1 : tvr->comparison( comparison );
345 :
346 : pcf::IndiProperty *prop1;
347 1 : std::string element1;
348 :
349 4 : extractRuleProp(
350 2 : &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Text, config );
351 1 : tvr->property1( prop1 );
352 1 : tvr->element1( element1 );
353 :
354 : pcf::IndiProperty *prop2;
355 1 : std::string element2;
356 :
357 4 : extractRuleProp(
358 2 : &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Text, config );
359 1 : tvr->property2( prop2 );
360 1 : tvr->element2( element2 );
361 1 : }
362 10 : else if( ruleType == elCompSwRule::name )
363 : {
364 2 : elCompSwRule *svr = new elCompSwRule;
365 2 : maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], svr } ) );
366 :
367 2 : svr->priority( priority );
368 2 : svr->message( message );
369 2 : svr->comparison( comparison );
370 :
371 : pcf::IndiProperty *prop1;
372 2 : std::string element1;
373 :
374 8 : extractRuleProp(
375 4 : &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Switch, config );
376 2 : svr->property1( prop1 );
377 2 : svr->element1( element1 );
378 :
379 : pcf::IndiProperty *prop2;
380 2 : std::string element2;
381 :
382 8 : extractRuleProp(
383 4 : &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Switch, config );
384 2 : svr->property2( prop2 );
385 2 : svr->element2( element2 );
386 2 : }
387 8 : else if( ruleType == ruleCompRule::name )
388 : {
389 : // Here we have to hold the ruleVal keys separately for later processing after all the rules are created.
390 :
391 7 : if( rrkMap.count( sections[i] ) > 0 )
392 : {
393 : // This probably should be impossible, since we already checked maps.rules above...
394 0 : throw mx::exception( mx::error_t::invalidconfig, "duplicate ruleRule: " + sections[i] );
395 : }
396 :
397 7 : ruleCompRule *rcr = new ruleCompRule;
398 7 : maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], rcr } ) );
399 :
400 7 : rcr->priority( priority );
401 7 : rcr->message( message );
402 7 : rcr->comparison( comparison );
403 :
404 7 : ruleRuleKeys rrk;
405 :
406 7 : config.configUnused( rrk.rule1, mx::app::iniFile::makeKey( sections[i], "rule1" ) );
407 7 : if( rrk.rule1 == "" )
408 : {
409 : throw mx::exception( mx::error_t::invalidconfig,
410 0 : "rule1 for ruleVal rule " + sections[i] + " not found" );
411 : }
412 7 : if( rrk.rule1 == sections[i] )
413 : {
414 : throw mx::exception( mx::error_t::invalidconfig,
415 1 : "rule1 for ruleVal rule " + sections[i] + " can't equal rule name" );
416 : }
417 :
418 6 : config.configUnused( rrk.rule2, mx::app::iniFile::makeKey( sections[i], "rule2" ) );
419 6 : if( rrk.rule2 == "" )
420 : {
421 : throw mx::exception( mx::error_t::invalidconfig,
422 0 : "rule2 for ruleVal rule " + sections[i] + " not found" );
423 : }
424 6 : if( rrk.rule2 == sections[i] )
425 : {
426 : throw mx::exception( mx::error_t::invalidconfig,
427 1 : "rule2 for ruleVal rule " + sections[i] + " can't equal rule name" );
428 : }
429 :
430 5 : rrkMap.insert( std::pair<std::string, ruleRuleKeys>( sections[i], rrk ) );
431 7 : }
432 : else
433 : {
434 : throw mx::exception( mx::error_t::notimpl,
435 1 : std::format( "unknown rule type {} in {}", ruleType, sections[i] ) );
436 : }
437 37 : }
438 19 : }
439 :
440 : /// Finalize ruleVal rules
441 : /** ///\todo check for insertion failure
442 : * ///\todo add a constructor that has priority, message, and comparison, to reduce duplication
443 : */
444 4 : void finalizeRuleValRules(
445 : indiRuleMaps &maps, /**< [in/out] contains the rule and property maps with rules ot finalize */
446 : std::map<std::string, ruleRuleKeys> &rrkMap ///< [out] Holds the ruleVal rule keys aside for later post-processing
447 : )
448 : {
449 : // Now set the rule pointers for any ruleVal rules
450 4 : auto it = rrkMap.begin();
451 7 : while( it != rrkMap.end() )
452 : {
453 5 : if( maps.rules.count( it->first ) == 0 )
454 : {
455 0 : throw mx::exception( mx::error_t::invalidconfig, std::format( "rule parsing error for {}", it->first ) );
456 : }
457 :
458 5 : if( maps.rules.count( it->second.rule1 ) == 0 )
459 : {
460 : throw mx::exception( mx::error_t::invalidconfig,
461 1 : std::format( "rule1 {} not found "
462 : "for ruleVal rule {}",
463 1 : it->second.rule1,
464 3 : it->first ) );
465 : }
466 :
467 4 : if( maps.rules.count( it->second.rule2 ) == 0 )
468 : {
469 : throw mx::exception( mx::error_t::invalidconfig,
470 1 : std::format( "rule2 {} not found "
471 : "for ruleVal rule {}",
472 1 : it->second.rule2,
473 3 : it->first ) );
474 : }
475 :
476 3 : ruleCompRule *rcr = nullptr;
477 :
478 : try
479 : {
480 3 : rcr = dynamic_cast<ruleCompRule *>( maps.rules[it->first] );
481 : }
482 0 : catch( const std::exception &e )
483 : {
484 0 : std::throw_with_nested(
485 0 : mx::exception( mx::error_t::invalidconfig, std::format( "error casting {}", it->first ) ) );
486 0 : }
487 :
488 3 : if( rcr == nullptr )
489 : {
490 : throw mx::exception( mx::error_t::invalidconfig,
491 0 : std::format( "{} is not a ruleVal rule but has rules", it->first ) );
492 : }
493 :
494 3 : rcr->rule1( maps.rules[it->second.rule1] );
495 3 : rcr->rule2( maps.rules[it->second.rule2] );
496 :
497 3 : ++it;
498 : }
499 2 : }
500 :
501 : #endif // stateRuleEngine_indiCompRuleConfig_hpp
|