Skip to content

Commit b24fb94

Browse files
authored
Implement Stdin Event Handler (#45)
* Implement Stdin Event Handler Signed-off-by: jparisu <javierparis@eprosima.com> * Add code documentation Signed-off-by: jparisu <javierparis@eprosima.com> * apply suggestions Signed-off-by: jparisu <javierparis@eprosima.com> * uncrustify Signed-off-by: jparisu <javierparis@eprosima.com> --------- Signed-off-by: jparisu <javierparis@eprosima.com>
1 parent b3aa70d commit b24fb94

10 files changed

Lines changed: 682 additions & 10 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <atomic>
18+
#include <functional>
19+
#include <thread>
20+
21+
#include <cpp_utils/event/EventHandler.hpp>
22+
#include <cpp_utils/library/library_dll.h>
23+
#include <cpp_utils/time/time_utils.hpp>
24+
#include <cpp_utils/wait/CounterWaitHandler.hpp>
25+
26+
namespace eprosima {
27+
namespace utils {
28+
namespace event {
29+
30+
/**
31+
* This object allows to read from a \c istream , commonly from \c std::cin .
32+
* Every time there is a message written in std::cin AND this object is enabled to read it, the string input
33+
* is given in callback call.
34+
*
35+
* @warning This EventHandler is different than others in the way that it is obligated to give the number of inputs
36+
* that it must read (and not read until stop). This is because there is no easy way to stop a thread that
37+
* is waiting in std::cin in a multi-platform way.
38+
* This also implies that once the handler is waiting for stdin input, there is no way to stop it.
39+
*/
40+
class StdinEventHandler : public EventHandler<std::string>
41+
{
42+
public:
43+
44+
/**
45+
* @brief Construct a new Stdin Event Handler with specific callback
46+
*
47+
* @param callback : callback to call when there is new data in stdin
48+
* @param read_lines : whether to read whole lines or read separated in spaces
49+
* @param lines_to_read : number of lines that this EventHandler must expect. Could be incremented by
50+
* \c read_one_more_line .
51+
* @param source : source \c istream to get data from.
52+
*
53+
* @note \c source is very useful for testing purpose.
54+
* However, it does not make much sense in any other scenario, and it is dangerous to use something different
55+
* than std::cin, as the object stores a reference, and this referenced istream must survive this object.
56+
*/
57+
CPP_UTILS_DllAPI
58+
StdinEventHandler(
59+
std::function<void(std::string)> callback,
60+
const bool read_lines = true,
61+
const int lines_to_read = 0,
62+
std::istream& source = std::cin);
63+
64+
/**
65+
* @brief Destroy the StdinEventHandler object
66+
*
67+
* Calls \c unset_callback
68+
*/
69+
CPP_UTILS_DllAPI
70+
~StdinEventHandler();
71+
72+
/**
73+
* @brief In order to read more than the lines given by ctor argument, use this method to increase value by 1.
74+
*
75+
* There is no easy way to stop a thread reading in a istream.
76+
* Thus the handler must know how much times it should read.
77+
* This method increase in 1 the number of times to read.
78+
*
79+
* The standard use of this class is to call this method each time a stdin input is expected.
80+
*/
81+
CPP_UTILS_DllAPI
82+
void read_one_more_line();
83+
84+
protected:
85+
86+
/**
87+
* @brief Internal thread to read from \c source_ .
88+
*
89+
* This thread waits first to this object to give it permission to start waiting for data in \c source_ by
90+
* waiter \c activation_times_ .
91+
* Every time allowed, it waits for 1 new input and give it by calling callback.
92+
*
93+
* @warning callback is called from this method, so until the
94+
* callback does not finish, the object cannot start reading again.
95+
*/
96+
CPP_UTILS_DllAPI
97+
void stdin_listener_thread_routine_() noexcept;
98+
99+
/**
100+
* @brief Override \c callback_set_ from \c EventHandler .
101+
*
102+
* It starts reading thread.
103+
*
104+
* It is already guarded by \c event_mutex_ .
105+
*/
106+
virtual void callback_set_nts_() noexcept override;
107+
108+
/**
109+
* @brief Override \c callback_set_ from \c EventHandler .
110+
*
111+
* It stops reading thread.
112+
*
113+
* It is already guarded by \c event_mutex_ .
114+
*/
115+
virtual void callback_unset_nts_() noexcept override;
116+
117+
//! Reading thread
118+
std::thread stdin_listener_thread_;
119+
120+
//! Counter that contains the number of times the thread is allowed to start waiting for data from source_.
121+
CounterWaitHandler activation_times_;
122+
123+
/**
124+
* @brief istream source from where to read.
125+
*
126+
* This is very useful for testing.
127+
* However it is dangerous to have a reference here to an external object.
128+
* Commonly this will be std::cin that does not die till the end of process.
129+
*/
130+
std::istream& source_;
131+
132+
//! Whether to read whole lines or stop reading in a space.
133+
const bool read_lines_;
134+
};
135+
136+
} /* namespace event */
137+
} /* namespace utils */
138+
} /* namespace eprosima */

cpp_utils/include/cpp_utils/utils.hpp

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ FileAccessMode operator &(
6363
FileAccessMode mode_b);
6464

6565
//! Perform the wildcard matching using file comparison method
66-
CPP_UTILS_DllAPI bool match_pattern(
66+
CPP_UTILS_DllAPI
67+
bool match_pattern(
6768
const std::string& pattern,
6869
const std::string& str) noexcept;
6970

@@ -74,7 +75,8 @@ CPP_UTILS_DllAPI bool match_pattern(
7475
*
7576
* @param [in,out] st : string to modify
7677
*/
77-
CPP_UTILS_DllAPI void to_lowercase(
78+
CPP_UTILS_DllAPI
79+
void to_lowercase(
7880
std::string& st) noexcept;
7981

8082
/**
@@ -135,7 +137,8 @@ bool are_set_of_ptr_equal(
135137
*
136138
* @param formatter msg of the unexpected case.
137139
*/
138-
CPP_UTILS_DllAPI void tsnh(
140+
CPP_UTILS_DllAPI
141+
void tsnh(
139142
const Formatter& formatter);
140143

141144
/**
@@ -165,7 +168,8 @@ std::set<std::shared_ptr<Parent>> convert_set_to_shared(
165168
* @return true if file is accessible regarding the permissions given
166169
* @return false otherwise
167170
*/
168-
CPP_UTILS_DllAPI bool is_file_accessible(
171+
CPP_UTILS_DllAPI
172+
bool is_file_accessible(
169173
const char* file_path,
170174
FileAccessMode access_mode = FileAccessMode::exist) noexcept;
171175

@@ -179,20 +183,24 @@ CPP_UTILS_DllAPI bool is_file_accessible(
179183
* @return to string convertion of the element
180184
*/
181185
template <typename T>
182-
CPP_UTILS_DllAPI std::string generic_to_string(
186+
CPP_UTILS_DllAPI
187+
std::string generic_to_string(
183188
const T& element);
184189

185190
template <typename T>
186-
CPP_UTILS_DllAPI void* copy_to_void_ptr(
191+
CPP_UTILS_DllAPI
192+
void* copy_to_void_ptr(
187193
const T* source,
188194
size_t size = sizeof(T));
189195

190-
CPP_UTILS_DllAPI bool replace_first(
196+
CPP_UTILS_DllAPI
197+
bool replace_first(
191198
std::string& st,
192199
std::string const& to_replace,
193200
std::string const& replace_by);
194201

195-
CPP_UTILS_DllAPI unsigned int replace_all(
202+
CPP_UTILS_DllAPI
203+
unsigned int replace_all(
196204
std::string& st,
197205
std::string const& to_replace,
198206
std::string const& replace_by);
@@ -209,16 +217,61 @@ CPP_UTILS_DllAPI unsigned int replace_all(
209217
*
210218
* @return number of strings removed.
211219
*/
212-
CPP_UTILS_DllAPI unsigned int strip_str(
220+
CPP_UTILS_DllAPI
221+
unsigned int strip_str(
213222
std::string& to_strip,
214223
const std::string& replace_by = "",
215224
const std::set<std::string>& undesired_strings = {"\n", "\r"});
216225

217-
CPP_UTILS_DllAPI std::string number_trailing_zeros_format(
226+
CPP_UTILS_DllAPI
227+
std::string number_trailing_zeros_format(
218228
int value_to_print,
219229
unsigned int n_chars,
220230
bool allow_more_chars = true);
221231

232+
/**
233+
* @brief Split string \c source by every delimiter in \c delimiters .
234+
*
235+
* This method uses split_string(const std::vector<std::string>&, const std::string&) .
236+
* For more information, please check that documentation.
237+
*/
238+
CPP_UTILS_DllAPI
239+
std::vector<std::string> split_string(
240+
const std::string& source,
241+
const std::set<std::string>& delimiters);
242+
243+
/**
244+
* @brief Split each string in \c source by \c delimiter .
245+
*
246+
* This method uses split_string(const std::string&, const std::string&) .
247+
* For more information, please check that documentation.
248+
*/
249+
CPP_UTILS_DllAPI
250+
std::vector<std::string> split_string(
251+
const std::vector<std::string>& source,
252+
const std::string& delimiter);
253+
254+
/**
255+
* @brief Split a string \c source by \c delimiter .
256+
*
257+
* The delimiter will no longer exist in any of the result strings.
258+
*
259+
* @example source:"Some String" delimiter:" " result:["Some", "String"]
260+
* @example source:" " delimiter:" " result:["", ""]
261+
*
262+
* @param source string to divide in tokens
263+
* @param delimiter string to look for in source and divide it with
264+
*
265+
* @return vector with strings
266+
*
267+
* @warning Some results could be empty strings if the delimiter is at the start, end or repeated along source.
268+
*
269+
* @post There will be always at least 1 element in the result vector
270+
*/
271+
CPP_UTILS_DllAPI
272+
std::vector<std::string> split_string(
273+
const std::string& source,
274+
const std::string& delimiter);
222275

223276
} /* namespace utils */
224277
} /* namespace eprosima */
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <cpp_utils/Log.hpp>
16+
#include <cpp_utils/event/StdinEventHandler.hpp>
17+
#include <cpp_utils/exception/InitializationException.hpp>
18+
19+
namespace eprosima {
20+
namespace utils {
21+
namespace event {
22+
23+
StdinEventHandler::StdinEventHandler(
24+
std::function<void(std::string)> callback,
25+
const bool read_lines /* = true */,
26+
const int lines_to_read /* = 0 */,
27+
std::istream& source /* = std::cin */)
28+
: activation_times_(0, lines_to_read, false)
29+
, source_(source)
30+
, read_lines_(read_lines)
31+
{
32+
set_callback(callback);
33+
}
34+
35+
StdinEventHandler::~StdinEventHandler()
36+
{
37+
unset_callback();
38+
}
39+
40+
void StdinEventHandler::read_one_more_line()
41+
{
42+
++activation_times_;
43+
}
44+
45+
void StdinEventHandler::stdin_listener_thread_routine_() noexcept
46+
{
47+
auto awake_reason = activation_times_.wait_and_decrement();
48+
while (awake_reason == AwakeReason::condition_met)
49+
{
50+
std::string read_str;
51+
52+
// Read lines or separated by spaces
53+
if (read_lines_)
54+
{
55+
getline(source_, read_str);
56+
}
57+
else
58+
{
59+
source_ >> read_str;
60+
}
61+
event_occurred_(read_str);
62+
63+
awake_reason = activation_times_.wait_and_decrement();
64+
}
65+
}
66+
67+
void StdinEventHandler::callback_set_nts_() noexcept
68+
{
69+
activation_times_.enable();
70+
stdin_listener_thread_ = std::thread(&StdinEventHandler::stdin_listener_thread_routine_, this);
71+
}
72+
73+
void StdinEventHandler::callback_unset_nts_() noexcept
74+
{
75+
activation_times_.disable();
76+
stdin_listener_thread_.join();
77+
}
78+
79+
} /* namespace event */
80+
} /* namespace utils */
81+
} /* namespace eprosima */

0 commit comments

Comments
 (0)