Skip to content

Commit f520cdf

Browse files
committed
Doxygen documentation of code
1 parent 3b14693 commit f520cdf

File tree

6 files changed

+194
-86
lines changed

6 files changed

+194
-86
lines changed

include/ur_client_library/control/script_reader.h

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,26 @@ namespace urcl
4141
{
4242
namespace control
4343
{
44+
/*!
45+
* \brief This class handles reading script files parsing special instructions that will get replaced.
46+
*
47+
* When parsing the script code, it is supported to have
48+
* - Variable replacements using `{{ VARIABLE_NAME }}`
49+
* - Including other files using `{% include <filename> %}`.
50+
* The filename has to be relative to the root script file's folder
51+
* - Conditionals using
52+
*
53+
* {% if <condition %}
54+
* ...
55+
* {% elif <condition> %}
56+
* ...
57+
* {% else %}
58+
* ...
59+
* {% endif %}
60+
*
61+
*
62+
* Those directives use Jinja2 notation.
63+
*/
4464
class ScriptReader
4565
{
4666
public:
@@ -49,10 +69,21 @@ class ScriptReader
4969

5070
ScriptReader() = default;
5171

72+
/*!
73+
* \brief Reads a script file and applies variable replacements, includes, and conditionals.
74+
* \param filename Filename (absolute path) of the script to be loaded.
75+
* \param data Data dictionary used for variable replacements and expression evaluation.
76+
* \return The Script code with all replacements, includes and conditionals applied.
77+
*/
5278
std::string readScriptFile(const std::string& filename, const DataDict& data = DataDict());
5379

54-
static bool checkCondition(const std::string& condition, const DataDict& data);
55-
static bool parseBoolean(const std::string& str);
80+
/*!
81+
* \brief Evaluate a boolean expression
82+
* \param expression The boolean expression to be evaluated.
83+
* \param data A data dictionary that will be used when evaluating the expressions
84+
* \return The result of evaluating the boolean expression
85+
*/
86+
static bool evaluateExpression(const std::string& expression, const DataDict& data);
5687

5788
private:
5889
enum BlockType

include/ur_client_library/helpers.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#ifndef UR_CLIENT_LIBRARY_HELPERS_H_INCLUDED
3030
#define UR_CLIENT_LIBRARY_HELPERS_H_INCLUDED
3131

32+
#include <string>
3233
#include <chrono>
3334
#include <functional>
3435
#ifdef _WIN32
@@ -78,5 +79,25 @@ bool setFiFoScheduling(pthread_t& thread, const int priority);
7879
*/
7980
void waitFor(std::function<bool()> condition, const std::chrono::milliseconds timeout,
8081
const std::chrono::milliseconds check_interval = std::chrono::milliseconds(50));
82+
83+
/*!
84+
* \brief Parses a boolean value from a string.
85+
*
86+
* The string can be one of
87+
* - true, True, TRUE
88+
* - on, On, ON
89+
* - yes, Yes, YES
90+
* - 1
91+
* - false, False, FALSE
92+
* - off, Off, OFF
93+
* - no, No, NO
94+
* - 0
95+
*
96+
* \param str string to be parsed
97+
* \throws urcl::UrException If the string doesn't match one of the options
98+
* \return The boolean representation of the string
99+
*/
100+
bool parseBoolean(const std::string& str);
101+
81102
} // namespace urcl
82103
#endif // ifndef UR_CLIENT_LIBRARY_HELPERS_H_INCLUDED

src/control/script_reader.cpp

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
#include <ur_client_library/exceptions.h>
3232
#include <ur_client_library/control/script_reader.h>
33+
#include <ur_client_library/helpers.h>
3334

3435
#include <fstream>
3536
#include <regex>
@@ -40,28 +41,6 @@ namespace urcl
4041
namespace control
4142
{
4243

43-
bool ScriptReader::parseBoolean(const std::string& str)
44-
{
45-
std::string lower = str;
46-
std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); });
47-
48-
if (lower == "true" || lower == "1" || lower == "yes" || lower == "on")
49-
{
50-
return true;
51-
}
52-
else if (lower == "false" || lower == "0" || lower == "no" || lower == "off")
53-
{
54-
return false;
55-
}
56-
else
57-
{
58-
std::stringstream ss;
59-
ss << "Invalid boolean value: '" << str << "'. Expected 'true', 'false', '1', '0', 'yes', 'no', 'on', or 'off'.";
60-
URCL_LOG_ERROR(ss.str().c_str());
61-
throw UrException(ss.str().c_str());
62-
}
63-
}
64-
6544
bool operator<(const ScriptReader::DataVariant& lhs, const ScriptReader::DataVariant& rhs)
6645
{
6746
if (std::holds_alternative<double>(lhs))
@@ -267,7 +246,7 @@ void ScriptReader::replaceConditionals(std::string& script_code, const DataDict&
267246
if (std::regex_search(line, match, if_pattern))
268247
{
269248
std::string condition = match[1];
270-
bool result = checkCondition(condition, data);
249+
bool result = evaluateExpression(condition, data);
271250
bool parent_render = block_stack.empty() ? true : block_stack.top().should_render;
272251
block_stack.push({ IF, result, parent_render && result, parent_render });
273252
}
@@ -326,9 +305,9 @@ void ScriptReader::replaceConditionals(std::string& script_code, const DataDict&
326305
script_code = output.str();
327306
}
328307

329-
bool ScriptReader::checkCondition(const std::string& condition, const DataDict& data)
308+
bool ScriptReader::evaluateExpression(const std::string& expression, const DataDict& data)
330309
{
331-
const std::string trimmed = std::regex_replace(condition, std::regex("^\\s+|\\s+$"), "");
310+
const std::string trimmed = std::regex_replace(expression, std::regex("^\\s+|\\s+$"), "");
332311
const std::vector<std::string> valid_operators = { "==", "!=", "<", ">", "<=", ">=" };
333312
std::regex expression_pattern(R"(([a-zA-Z_][a-zA-Z0-9_]*)\s*([!=<>]=?)\s*(["']?[^'"]*["']?))");
334313

src/helpers.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,26 @@ void waitFor(std::function<bool()> condition, const std::chrono::milliseconds ti
117117
}
118118
throw urcl::TimeoutException("Timeout while waiting for condition to be met", timeout);
119119
}
120-
} // namespace urcl
120+
121+
bool parseBoolean(const std::string& str)
122+
{
123+
std::string lower = str;
124+
std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); });
125+
126+
if (lower == "true" || lower == "1" || lower == "yes" || lower == "on")
127+
{
128+
return true;
129+
}
130+
else if (lower == "false" || lower == "0" || lower == "no" || lower == "off")
131+
{
132+
return false;
133+
}
134+
else
135+
{
136+
std::stringstream ss;
137+
ss << "Invalid boolean value: '" << str << "'. Expected 'true', 'false', '1', '0', 'yes', 'no', 'on', or 'off'.";
138+
URCL_LOG_ERROR(ss.str().c_str());
139+
throw UrException(ss.str().c_str());
140+
}
141+
}
142+
} // namespace urcl

tests/test_helpers.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// -- BEGIN LICENSE BLOCK ----------------------------------------------
2+
// Copyright 2025 Universal Robots A/S
3+
//
4+
// Redistribution and use in source and binary forms, with or without
5+
// modification, are permitted provided that the following conditions are met:
6+
//
7+
// * Redistributions of source code must retain the above copyright
8+
// notice, this list of conditions and the following disclaimer.
9+
//
10+
// * Redistributions in binary form must reproduce the above copyright
11+
// notice, this list of conditions and the following disclaimer in the
12+
// documentation and/or other materials provided with the distribution.
13+
//
14+
// * Neither the name of the {copyright_holder} nor the names of its
15+
// contributors may be used to endorse or promote products derived from
16+
// this software without specific prior written permission.
17+
//
18+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
// POSSIBILITY OF SUCH DAMAGE.
29+
// -- END LICENSE BLOCK ------------------------------------------------
30+
31+
#include <gtest/gtest.h>
32+
33+
#include <ur_client_library/helpers.h>
34+
#include <ur_client_library/exceptions.h>
35+
36+
using namespace urcl;
37+
38+
TEST(TestHelpers, test_parse_boolean)
39+
{
40+
EXPECT_TRUE(parseBoolean("true"));
41+
EXPECT_TRUE(parseBoolean("True"));
42+
EXPECT_TRUE(parseBoolean("TRUE"));
43+
EXPECT_TRUE(parseBoolean("on"));
44+
EXPECT_TRUE(parseBoolean("On"));
45+
EXPECT_TRUE(parseBoolean("ON"));
46+
EXPECT_TRUE(parseBoolean("1"));
47+
EXPECT_FALSE(parseBoolean("false"));
48+
EXPECT_FALSE(parseBoolean("False"));
49+
EXPECT_FALSE(parseBoolean("FALSE"));
50+
EXPECT_FALSE(parseBoolean("off"));
51+
EXPECT_FALSE(parseBoolean("Off"));
52+
EXPECT_FALSE(parseBoolean("OFF"));
53+
EXPECT_FALSE(parseBoolean("0"));
54+
EXPECT_THROW(parseBoolean("notabool"), urcl::UrException);
55+
}

tests/test_script_reader.cpp

Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -250,77 +250,77 @@ TEST_F(ScriptReaderTest, CheckCondition)
250250
data["T"] = "world";
251251

252252
// True/False
253-
EXPECT_TRUE(reader.checkCondition(" A", data));
254-
EXPECT_FALSE(reader.checkCondition("B", data));
253+
EXPECT_TRUE(reader.evaluateExpression(" A", data));
254+
EXPECT_FALSE(reader.evaluateExpression("B", data));
255255

256256
// Equality
257-
EXPECT_TRUE(reader.checkCondition("X == 5", data));
258-
EXPECT_FALSE(reader.checkCondition("X == 6", data));
259-
EXPECT_TRUE(reader.checkCondition("S == \"hello\"", data));
260-
EXPECT_TRUE(reader.checkCondition("S == 'hello'", data));
261-
EXPECT_FALSE(reader.checkCondition("S == \"world\"", data));
262-
EXPECT_FALSE(reader.checkCondition("S == 'world'", data));
263-
EXPECT_FALSE(reader.checkCondition("S == T", data));
264-
EXPECT_TRUE(reader.checkCondition("S == S", data));
265-
EXPECT_TRUE(reader.checkCondition("A == true", data));
266-
EXPECT_TRUE(reader.checkCondition("A == True", data));
267-
EXPECT_TRUE(reader.checkCondition("A == TRUE", data));
268-
EXPECT_TRUE(reader.checkCondition("A == 1", data));
269-
EXPECT_TRUE(reader.checkCondition("A == on", data));
270-
EXPECT_TRUE(reader.checkCondition("A == On", data));
271-
EXPECT_TRUE(reader.checkCondition("A == ON", data));
272-
EXPECT_FALSE(reader.checkCondition("B == true", data));
273-
EXPECT_FALSE(reader.checkCondition("B == True", data));
274-
EXPECT_FALSE(reader.checkCondition("B == TRUE", data));
275-
EXPECT_FALSE(reader.checkCondition("B == 1", data));
276-
EXPECT_FALSE(reader.checkCondition("B == on", data));
277-
EXPECT_FALSE(reader.checkCondition("B == On", data));
278-
EXPECT_FALSE(reader.checkCondition("B == ON", data));
279-
EXPECT_FALSE(reader.checkCondition("A == B", data));
257+
EXPECT_TRUE(reader.evaluateExpression("X == 5", data));
258+
EXPECT_FALSE(reader.evaluateExpression("X == 6", data));
259+
EXPECT_TRUE(reader.evaluateExpression("S == \"hello\"", data));
260+
EXPECT_TRUE(reader.evaluateExpression("S == 'hello'", data));
261+
EXPECT_FALSE(reader.evaluateExpression("S == \"world\"", data));
262+
EXPECT_FALSE(reader.evaluateExpression("S == 'world'", data));
263+
EXPECT_FALSE(reader.evaluateExpression("S == T", data));
264+
EXPECT_TRUE(reader.evaluateExpression("S == S", data));
265+
EXPECT_TRUE(reader.evaluateExpression("A == true", data));
266+
EXPECT_TRUE(reader.evaluateExpression("A == True", data));
267+
EXPECT_TRUE(reader.evaluateExpression("A == TRUE", data));
268+
EXPECT_TRUE(reader.evaluateExpression("A == 1", data));
269+
EXPECT_TRUE(reader.evaluateExpression("A == on", data));
270+
EXPECT_TRUE(reader.evaluateExpression("A == On", data));
271+
EXPECT_TRUE(reader.evaluateExpression("A == ON", data));
272+
EXPECT_FALSE(reader.evaluateExpression("B == true", data));
273+
EXPECT_FALSE(reader.evaluateExpression("B == True", data));
274+
EXPECT_FALSE(reader.evaluateExpression("B == TRUE", data));
275+
EXPECT_FALSE(reader.evaluateExpression("B == 1", data));
276+
EXPECT_FALSE(reader.evaluateExpression("B == on", data));
277+
EXPECT_FALSE(reader.evaluateExpression("B == On", data));
278+
EXPECT_FALSE(reader.evaluateExpression("B == ON", data));
279+
EXPECT_FALSE(reader.evaluateExpression("A == B", data));
280280

281281
// Inequality
282-
EXPECT_TRUE(reader.checkCondition("X != 6", data));
283-
EXPECT_FALSE(reader.checkCondition("X != 5", data));
284-
EXPECT_TRUE(reader.checkCondition("A != B", data));
282+
EXPECT_TRUE(reader.evaluateExpression("X != 6", data));
283+
EXPECT_FALSE(reader.evaluateExpression("X != 5", data));
284+
EXPECT_TRUE(reader.evaluateExpression("A != B", data));
285285

286286
// Greater/Less
287-
EXPECT_TRUE(reader.checkCondition("Y > X", data));
288-
EXPECT_FALSE(reader.checkCondition("X > Y", data));
289-
EXPECT_TRUE(reader.checkCondition("X < Y", data));
290-
EXPECT_FALSE(reader.checkCondition("Y < X", data));
291-
EXPECT_TRUE(reader.checkCondition("PI > 3", data));
292-
EXPECT_FALSE(reader.checkCondition("PI < 3", data));
293-
EXPECT_TRUE(reader.checkCondition("PI >= 3.14159", data));
294-
EXPECT_FALSE(reader.checkCondition("PI < 3.14159", data));
295-
EXPECT_TRUE(reader.checkCondition("PI < X", data));
287+
EXPECT_TRUE(reader.evaluateExpression("Y > X", data));
288+
EXPECT_FALSE(reader.evaluateExpression("X > Y", data));
289+
EXPECT_TRUE(reader.evaluateExpression("X < Y", data));
290+
EXPECT_FALSE(reader.evaluateExpression("Y < X", data));
291+
EXPECT_TRUE(reader.evaluateExpression("PI > 3", data));
292+
EXPECT_FALSE(reader.evaluateExpression("PI < 3", data));
293+
EXPECT_TRUE(reader.evaluateExpression("PI >= 3.14159", data));
294+
EXPECT_FALSE(reader.evaluateExpression("PI < 3.14159", data));
295+
EXPECT_TRUE(reader.evaluateExpression("PI < X", data));
296296

297297
// String not empty
298-
EXPECT_TRUE(reader.checkCondition("S != ''", data));
299-
EXPECT_TRUE(reader.checkCondition("S != \"\"", data));
300-
EXPECT_FALSE(reader.checkCondition("S == \"\"", data));
298+
EXPECT_TRUE(reader.evaluateExpression("S != ''", data));
299+
EXPECT_TRUE(reader.evaluateExpression("S != \"\"", data));
300+
EXPECT_FALSE(reader.evaluateExpression("S == \"\"", data));
301301

302302
// Provoke errors
303303
// Non-existing operator
304-
EXPECT_THROW(reader.checkCondition("X ~= 5", data), std::runtime_error);
305-
EXPECT_THROW(reader.checkCondition("This is not an expression at all", data), std::runtime_error);
306-
EXPECT_TRUE(reader.checkCondition("S != \"This is not an expression at all\"", data));
304+
EXPECT_THROW(reader.evaluateExpression("X ~= 5", data), std::runtime_error);
305+
EXPECT_THROW(reader.evaluateExpression("This is not an expression at all", data), std::runtime_error);
306+
EXPECT_TRUE(reader.evaluateExpression("S != \"This is not an expression at all\"", data));
307307
// Non-existing variable
308-
EXPECT_THROW(reader.checkCondition("non_existing == 5", data), urcl::UnknownVariable);
309-
EXPECT_THROW(reader.checkCondition("A == non_existing", data), urcl::UnknownVariable);
310-
EXPECT_THROW(reader.checkCondition("IDONTEXIST", data), urcl::UnknownVariable);
308+
EXPECT_THROW(reader.evaluateExpression("non_existing == 5", data), urcl::UnknownVariable);
309+
EXPECT_THROW(reader.evaluateExpression("A == non_existing", data), urcl::UnknownVariable);
310+
EXPECT_THROW(reader.evaluateExpression("IDONTEXIST", data), urcl::UnknownVariable);
311311
// <, >, <=, >= is only available for numeric types
312-
EXPECT_THROW(reader.checkCondition("A < 5", data), std::invalid_argument);
313-
EXPECT_THROW(reader.checkCondition("S < T", data), std::invalid_argument);
314-
EXPECT_THROW(reader.checkCondition("X < True", data), std::invalid_argument);
315-
EXPECT_THROW(reader.checkCondition("A > 5", data), std::invalid_argument);
316-
EXPECT_THROW(reader.checkCondition("S > T", data), std::invalid_argument);
317-
EXPECT_THROW(reader.checkCondition("X > True", data), std::invalid_argument);
318-
EXPECT_THROW(reader.checkCondition("A <= 5", data), std::invalid_argument);
319-
EXPECT_THROW(reader.checkCondition("S <= T", data), std::invalid_argument);
320-
EXPECT_THROW(reader.checkCondition("X <= True", data), std::invalid_argument);
321-
EXPECT_THROW(reader.checkCondition("A >= 5", data), std::invalid_argument);
322-
EXPECT_THROW(reader.checkCondition("S >= T", data), std::invalid_argument);
323-
EXPECT_THROW(reader.checkCondition("X >= True", data), std::invalid_argument);
312+
EXPECT_THROW(reader.evaluateExpression("A < 5", data), std::invalid_argument);
313+
EXPECT_THROW(reader.evaluateExpression("S < T", data), std::invalid_argument);
314+
EXPECT_THROW(reader.evaluateExpression("X < True", data), std::invalid_argument);
315+
EXPECT_THROW(reader.evaluateExpression("A > 5", data), std::invalid_argument);
316+
EXPECT_THROW(reader.evaluateExpression("S > T", data), std::invalid_argument);
317+
EXPECT_THROW(reader.evaluateExpression("X > True", data), std::invalid_argument);
318+
EXPECT_THROW(reader.evaluateExpression("A <= 5", data), std::invalid_argument);
319+
EXPECT_THROW(reader.evaluateExpression("S <= T", data), std::invalid_argument);
320+
EXPECT_THROW(reader.evaluateExpression("X <= True", data), std::invalid_argument);
321+
EXPECT_THROW(reader.evaluateExpression("A >= 5", data), std::invalid_argument);
322+
EXPECT_THROW(reader.evaluateExpression("S >= T", data), std::invalid_argument);
323+
EXPECT_THROW(reader.evaluateExpression("X >= True", data), std::invalid_argument);
324324
}
325325

326326
TEST_F(ScriptReaderTest, ParseBoolean)

0 commit comments

Comments
 (0)