Robowflex  v0.1
Making MoveIt Easy
robowflex_library/src/io.cpp
Go to the documentation of this file.
1 /* Author: Zachary Kingston */
2 
3 #include <array> // for std::array
4 #include <cstdlib> // for std::getenv
5 #include <memory> // for std::shared_ptr
6 #include <regex> // for std::regex
7 #include <thread>
8 
9 #include <boost/asio/ip/host_name.hpp> // for hostname
10 #include <boost/interprocess/detail/os_thread_functions.hpp> // for process / thread IDs
11 #include <boost/filesystem.hpp> // for filesystem paths
12 #include <boost/lexical_cast.hpp>
13 #include <boost/uuid/uuid.hpp> // for UUID generation
14 #include <boost/uuid/uuid_generators.hpp> // for UUID generation
15 #include <boost/uuid/uuid_io.hpp> // for UUID generation
16 #include <ros/package.h> // for package resolving
17 
18 #include <robowflex_library/io.h>
21 #include <robowflex_library/log.h>
23 #include <robowflex_library/util.h>
24 
25 using namespace robowflex;
26 
27 namespace
28 {
29  boost::filesystem::path expandHome(const boost::filesystem::path &in)
30  {
31  const char *home = std::getenv("HOME");
32  if (home == nullptr)
33  {
34  RBX_WARN("HOME Environment variable is not set! Cannot resolve ~ in path.");
35  return in;
36  }
37 
38  boost::filesystem::path out;
39  for (const auto &p : in)
40  out /= (p.string() == "~") ? home : p;
41 
42  return out;
43  }
44 
45  boost::filesystem::path expandSymlinks(const boost::filesystem::path &in)
46  {
47  // Check if the path has a symlink before expansion to avoid error.
48  boost::filesystem::path out;
49  for (const auto &p : in)
50  {
51  auto tmp = out / p;
52  if (boost::filesystem::is_symlink(tmp))
53  return boost::filesystem::canonical(in);
54  }
55 
56  return in;
57  }
58 
59  boost::filesystem::path expandPath(const boost::filesystem::path &in)
60  {
61  boost::filesystem::path out = in;
62  out = expandHome(out);
63  out = expandSymlinks(out);
64 
65  return boost::filesystem::absolute(out);
66  }
67 
68  // is lhs a prefix of rhs?
69  bool isPrefix(const std::string &lhs, const std::string &rhs)
70  {
71  return std::equal(lhs.begin(), lhs.begin() + std::min(lhs.size(), rhs.size()), rhs.begin());
72  }
73 
74  // is lhs a suffix? of rhs?
75  bool isSuffix(const std::string &lhs, const std::string &rhs)
76  {
77  return std::equal(lhs.rbegin(), lhs.rbegin() + std::min(lhs.size(), rhs.size()), rhs.rbegin());
78  }
79 
80  bool isExtension(const std::string &path_string, const std::string &extension)
81  {
82  boost::filesystem::path path(path_string);
83  const std::string last = boost::filesystem::extension(path);
84  return isSuffix(extension, last);
85  }
86 } // namespace
87 
89 {
90  if (path.empty())
91  return "";
92 
93  const std::string prefix = "package://";
94 
95  boost::filesystem::path file;
96  if (isPrefix(prefix, path))
97  {
98  boost::filesystem::path subpath(path.substr(prefix.length(), path.length() - 1));
99  const std::string package_name = (*subpath.begin()).string();
100 
101  const std::string package = ros::package::getPath(package_name);
102  if (package.empty())
103  {
104  RBX_WARN("Package `%s` does not exist.", package_name);
105  return "";
106  }
107 
108  file = package;
109  for (auto it = ++subpath.begin(); it != subpath.end(); ++it)
110  file /= *it;
111  }
112  else
113  file = path;
114 
115  return expandPath(file).string();
116 }
117 
119 {
120  const std::regex re(R"(((package):?\/)\/?([^:\/\s]+)((\/\w+)*\/)([\w\-\.]+[^#?\s]+)?)");
121 
122  std::set<std::string> packages;
123 
124  auto begin = std::sregex_iterator(string.begin(), string.end(), re);
125  auto end = std::sregex_iterator();
126 
127  for (auto it = begin; it != end; ++it)
128  {
129  std::smatch sm = *it;
130  std::string smstr = sm.str(3);
131  packages.emplace(smstr);
132  }
133 
134  return packages;
135 }
136 
138 {
139  boost::filesystem::path file = resolvePackage(path);
140 
141  if (!boost::filesystem::exists(file))
142  {
143  RBX_WARN("File `%s` does not exist.", path);
144  return "";
145  }
146 
147  return boost::filesystem::canonical(boost::filesystem::absolute(file)).string();
148 }
149 
151 {
152  boost::filesystem::path file = resolvePackage(path);
153  return file.parent_path().string();
154 }
155 
156 std::string IO::makeFilepath(const std::string &directory, const std::string &filename)
157 {
158  boost::filesystem::path dirpath = resolveParent(directory);
159  dirpath /= filename;
160 
161  return dirpath.string();
162 }
163 
165 {
166  const std::string full_path = resolvePath(path);
167  if (full_path.empty())
168  return "";
169 
170  std::ifstream ifs(full_path.c_str(), std::ios::in | std::ios::binary | std::ios::ate);
171 
172  std::ifstream::pos_type size = ifs.tellg();
173  ifs.seekg(0, std::ios::beg);
174 
175  std::vector<char> bytes(size);
176  ifs.read(bytes.data(), size);
177 
178  return std::string(bytes.data(), size);
179 }
180 
182 {
183  std::array<char, 128> buffer;
184  std::string result;
185  std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
186  if (!pipe)
187  {
188  RBX_ERROR("Failed to run command `%s`!", cmd);
189  return "";
190  }
191 
192  while (!feof(pipe.get()))
193  {
194  if (fgets(buffer.data(), 128, pipe.get()) != nullptr)
195  result += buffer.data();
196  }
197 
198  return result;
199 }
200 
202 {
203  const std::string full_path = resolvePath(path);
204  if (full_path.empty())
205  return "";
206 
207  std::string cmd = "rosrun xacro xacro ";
208 
209 #if ROBOWFLEX_AT_LEAST_MELODIC
210 #else
211  cmd += "--inorder ";
212 #endif
213 
214  cmd += full_path;
215  return runCommand(cmd);
216 }
217 
219 {
220  const std::string full_path = resolvePath(path);
221  if (full_path.empty())
222  return "";
223 
224  if (isExtension(full_path, "xacro"))
225  return loadXacroToString(full_path);
226 
227  return loadFileToString(full_path);
228 }
229 
231 {
232  YAML::Node file;
233  const std::string full_path = resolvePath(path);
234  if (full_path.empty())
235  return std::make_pair(false, file);
236 
237  if (!isExtension(full_path, "yml") && !isExtension(full_path, "yaml"))
238  return std::make_pair(false, file);
239 
240  try
241  {
242  return std::make_pair(true, YAML::LoadFile(full_path));
243  }
244  catch (std::exception &e)
245  {
246  return std::make_pair(false, file);
247  }
248 }
249 
251 {
253  const std::string full_path = resolvePath(path);
254  if (full_path.empty())
255  return std::make_pair(false, file);
256 
257  if (!isExtension(full_path, "yml") && !isExtension(full_path, "yaml"))
258  return std::make_pair(false, file);
259 
260  try
261  {
262  return std::make_pair(true, YAML::LoadAllFromFile(full_path));
263  }
264  catch (std::exception &e)
265  {
266  return std::make_pair(false, file);
267  }
268 }
269 
270 bool IO::YAMLToFile(const YAML::Node &node, const std::string &file)
271 {
272  YAML::Emitter out;
273  out << node;
274 
275  std::ofstream fout;
276  IO::createFile(fout, file);
277 
278  fout << out.c_str();
279  fout.close();
280 
281  return true;
282 }
283 
285 {
286  boost::uuids::random_generator gen;
287  boost::uuids::uuid u = gen();
288 
289  std::string s = boost::lexical_cast<std::string>(u);
290 
291  std::replace(s.begin(), s.end(), '-', '_');
292 
293  return s;
294 }
295 
296 void IO::createFile(std::ofstream &out, const std::string &file)
297 {
298  boost::filesystem::path path(file);
299  path = expandHome(path);
300  path = expandSymlinks(path);
301 
302  const auto parent = path.parent_path().string();
303 
304  if (!parent.empty())
305  boost::filesystem::create_directories(parent);
306 
307  out.open(path.string(), std::ofstream::out | std::ofstream::trunc);
308 }
309 
311 {
312  auto temp = boost::filesystem::unique_path();
313  auto filename = "/tmp/" + temp.string();
314  createFile(out, filename);
315 
316  return filename;
317 }
318 
319 void IO::deleteFile(const std::string &file)
320 {
321  boost::filesystem::path path(file);
322  path = expandHome(path);
323  path = expandSymlinks(path);
324 
325  boost::filesystem::remove(path);
326 }
327 
329 {
330  std::vector<std::string> contents;
331 
332  const std::string full_path = resolvePath(directory);
333  if (full_path.empty())
334  return std::make_pair(false, contents);
335 
336  boost::filesystem::path path(full_path);
337  if (!boost::filesystem::is_directory(path))
338  return std::make_pair(false, contents);
339 
340  for (auto it = boost::filesystem::directory_iterator(path); it != boost::filesystem::directory_iterator();
341  ++it)
342  contents.emplace_back(expandPath(it->path()).string());
343 
344  return std::make_pair(true, contents);
345 }
346 
348 {
349  return boost::asio::ip::host_name();
350 }
351 
353 {
354  return boost::interprocess::ipcdetail::get_current_process_id();
355 }
356 
358 {
359  return boost::interprocess::ipcdetail::get_current_thread_id();
360 }
361 
362 boost::posix_time::ptime IO::getDate()
363 {
364  return boost::posix_time::microsec_clock::local_time();
365 }
366 
367 double IO::getSeconds(boost::posix_time::ptime start, boost::posix_time::ptime finish)
368 {
369  auto duration = finish - start;
370  return duration.total_microseconds() / 1000000.;
371 }
372 
373 void IO::threadSleep(double seconds)
374 {
375  std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<long int>(seconds * 1000)));
376 }
377 
378 template <typename T>
380 {
381  boost::char_separator<char> seps(separators.c_str());
382  boost::tokenizer<boost::char_separator<char>> tokenizer(s, seps);
383 
384  std::vector<T> values;
385  std::transform(tokenizer.begin(), tokenizer.end(), std::back_inserter(values),
386  [](const std::string &s) { return boost::lexical_cast<T>(s); });
387 
388  return std::vector<T>();
389 }
390 
392 template std::vector<double> IO::tokenize(const std::string &, const std::string &);
393 
394 ///
395 /// IO::Bag
396 ///
397 
398 IO::Bag::Bag(const std::string &file, Mode mode)
399  : mode_(mode)
400  , file_((mode_ == WRITE) ? file : IO::resolvePath(file))
401  , bag_(file_, (mode_ == WRITE) ? rosbag::bagmode::Write : rosbag::bagmode::Read)
402 {
403 }
404 
406 {
407  bag_.close();
408 }
409 
410 ///
411 /// IO::Handler
412 ///
413 
414 // Static ID for all handlers
416 
417 namespace
418 {
419  class XmlRpcValueCreator : public XmlRpc::XmlRpcValue
420  {
421  public:
422  static XmlRpcValueCreator createArray(const std::vector<XmlRpcValue> &values)
423  {
424  XmlRpcValueCreator ret;
425  ret._type = TypeArray;
426  ret._value.asArray = new ValueArray(values);
427 
428  return ret;
429  }
430 
431  static XmlRpcValueCreator createStruct(const std::map<std::string, XmlRpcValue> &members)
432  {
433  XmlRpcValueCreator ret;
434  ret._type = TypeStruct;
435  ret._value.asStruct = new std::map<std::string, XmlRpcValue>(members);
436  return ret;
437  }
438  };
439 
440  XmlRpc::XmlRpcValue YAMLToXmlRpc(const YAML::Node &node)
441  {
442  if (node.Type() != YAML::NodeType::Scalar)
443  {
444  switch (node.Type())
445  {
446  case YAML::NodeType::Map:
447  {
449  for (YAML::const_iterator it = node.begin(); it != node.end(); ++it)
450  members[it->first.as<std::string>()] = YAMLToXmlRpc(it->second);
451 
452  return XmlRpcValueCreator::createStruct(members);
453  }
454  case YAML::NodeType::Sequence:
455  {
457  for (YAML::const_iterator it = node.begin(); it != node.end(); ++it)
458  values.push_back(YAMLToXmlRpc(*it));
459 
460  return XmlRpcValueCreator::createArray(values);
461  }
462  default:
463  throw Exception(1, "Unknown non-scalar node type in YAML");
464  }
465  }
466 
467  if (node.Tag() == "!!int")
468  return XmlRpc::XmlRpcValue(node.as<int>());
469 
470  if (node.Tag() == "!!float")
471  return XmlRpc::XmlRpcValue(node.as<double>());
472 
473  if (node.Tag() == "!!bool")
474  return XmlRpc::XmlRpcValue(node.as<bool>());
475 
476  try
477  {
478  return XmlRpc::XmlRpcValue(node.as<bool>());
479  }
480  catch (YAML::Exception &)
481  {
482  }
483 
484  try
485  {
486  return XmlRpc::XmlRpcValue(node.as<int>());
487  }
488  catch (YAML::Exception &)
489  {
490  }
491 
492  try
493  {
494  return XmlRpc::XmlRpcValue(node.as<double>());
495  }
496  catch (YAML::Exception &)
497  {
498  }
499 
500  try
501  {
502  return XmlRpc::XmlRpcValue(node.as<std::string>());
503  }
504  catch (YAML::Exception &)
505  {
506  }
507 
508  throw Exception(1, "Unknown node value in YAML");
509  }
510 } // namespace
511 
513  : name_(name), namespace_("robowflex_" + UUID + "/" + name_), nh_(namespace_)
514 {
515 }
516 
517 IO::Handler::Handler(const IO::Handler &handler, const std::string &name)
518  : name_(handler.getName()), namespace_(handler.getNamespace()), nh_(handler.getHandle(), name)
519 {
520 }
521 
523 {
524  for (const auto &key : params_)
525  nh_.deleteParam(key);
526 }
527 
528 void IO::Handler::loadYAMLtoROS(const YAML::Node &node, const std::string &prefix)
529 {
530  switch (node.Type())
531  {
532  case YAML::NodeType::Map:
533  {
534  const std::string fixed_prefix = (prefix.empty()) ? "" : (prefix + "/");
535  for (YAML::const_iterator it = node.begin(); it != node.end(); ++it)
536  loadYAMLtoROS(it->second, fixed_prefix + it->first.as<std::string>());
537 
538  break;
539  }
540  case YAML::NodeType::Sequence:
541  case YAML::NodeType::Scalar:
542  {
543  setParam(prefix, YAMLToXmlRpc(node));
544  break;
545  }
546  default:
547  throw Exception(1, "Unknown node type in YAML");
548  }
549 }
550 
551 bool IO::Handler::hasParam(const std::string &key) const
552 {
553  return nh_.hasParam(key);
554 }
555 
556 const ros::NodeHandle &IO::Handler::getHandle() const
557 {
558  return nh_;
559 }
560 
562 {
563  return name_;
564 }
565 
567 {
568  return namespace_;
569 }
T back_inserter(T... args)
T begin(T... args)
T c_str(T... args)
Exception that contains a message and an error code.
Definition: util.h:15
~Bag()
Destructor. Closes opened bag.
Mode
File modes.
Definition: bag.h:28
Bag(const std::string &file, Mode mode=WRITE)
Constructor.
ROS parameter server handler to handle namespacing and automatic parameter deletion.
Definition: handler.h:19
const std::string & getName() const
Gets the name of the handler.
~Handler()
Destructor. Deletes all parameters created through this handler.
bool hasParam(const std::string &key) const
Checks if the parameter server has key.
const ros::NodeHandle & getHandle() const
Gets the node handle.
void loadYAMLtoROS(const YAML::Node &node, const std::string &prefix="")
Loads the contents of a YAML node to the parameter server under a prefix.
const std::string & getNamespace() const
Gets the namespace of the handler.
Handler(const std::string &name)
Constructor.
static const std::string UUID
UUID of handler.
Definition: handler.h:92
T close(T... args)
T data(T... args)
T emplace_back(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T equal(T... args)
T get(T... args)
T getenv(T... args)
#define RBX_WARN(fmt,...)
Output a warning logging message.
Definition: log.h:110
#define RBX_ERROR(fmt,...)
Output a error logging message.
Definition: log.h:102
T make_pair(T... args)
T min(T... args)
bool YAMLToFile(const YAML::Node &node, const std::string &file)
Write the contents of a YAML node out to a potentially new file.
double getSeconds(boost::posix_time::ptime start, boost::posix_time::ptime finish)
Get a duration in seconds from two times.
std::string resolveParent(const std::string &path)
Resolves package:// URLs to get the directory this path is in.
std::set< std::string > findPackageURIs(const std::string &string)
Finds all package URIs within a string.
boost::posix_time::ptime getDate()
Get the current time (up to milliseconds)
std::string resolvePackage(const std::string &path)
Resolves package:// URLs to their canonical form. The path does not need to exist,...
void deleteFile(const std::string &file)
Deletes a file.
std::size_t getProcessID()
Get the process ID of this process.
std::pair< bool, std::vector< YAML::Node > > loadAllFromFileToYAML(const std::string &path)
Loads a file with multiple documents to a vector of YAML nodes.
std::pair< bool, YAML::Node > loadFileToYAML(const std::string &path)
Loads a file to a YAML node.
std::string makeFilepath(const std::string &directory, const std::string &filename)
Concatenates two elements of a path, a directory and a filename.
std::string runCommand(const std::string &cmd)
Runs a command cmd and returns stdout as a string.
std::string getHostname()
Get the hostname of the system.
std::pair< bool, std::vector< std::string > > listDirectory(const std::string &directory)
Lists of the contents of a directory.
std::vector< T > tokenize(const std::string &string, const std::string &separators=" ")
Separates a string into casted tokens, based upon separators.
std::string loadFileToString(const std::string &path)
Loads a file to a string.
std::string generateUUID()
Generates a UUID.
std::size_t getThreadID()
Get the thread ID of the current thread.
std::string createTempFile(std::ofstream &out)
Creates a temporary file and opens an output stream.
std::string resolvePath(const std::string &path)
Resolves package:// URLs and relative file paths to their canonical form.
std::string loadXMLToString(const std::string &path)
Loads an XML or .xacro file to a string.
void createFile(std::ofstream &out, const std::string &file)
Creates a file and opens an output stream. Creates directories if they do not exist.
std::string loadXacroToString(const std::string &path)
Loads a .xacro file to a string.
void threadSleep(double seconds)
Put the current thread to sleep for a desired amount of seconds.
Main namespace. Contains all library classes and functions.
Definition: scene.cpp:25
T open(T... args)
T push_back(T... args)
T rbegin(T... args)
T read(T... args)
T replace(T... args)
T seekg(T... args)
T size(T... args)
T sleep_for(T... args)
T str(T... args)
T substr(T... args)
T tellg(T... args)
T transform(T... args)