AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
create_node_reference_markdown.cpp
1// Copyright 2025 Robin Müller
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 <filesystem>
16#include <fstream>
17#include <iostream>
18#include <sstream>
19
20#include "auto_apms_behavior_tree_core/tree/tree_document.hpp"
21#include "auto_apms_util/container.hpp"
22#include "auto_apms_util/string.hpp"
23#include "behaviortree_cpp/basic_types.h"
24
25using namespace auto_apms_behavior_tree;
26
27int main(int argc, char ** argv)
28{
29 bool print_help = false;
30 if (argc > 1) {
31 const std::string arg(argv[1]);
32 print_help = "-h" == arg || "--help" == arg;
33 }
34 if (print_help || argc < 2) {
35 std::cerr << "create_node_reference_markdown: The program accepts: \n\t1.) Path to the markdown file to write "
36 "to.\n\t2.) Node manifest identities of installed resources used to "
37 "create the documentation. All "
38 "arguments after the first one are interpreted as resource identities.\n";
39 std::cerr << "Usage: create_node_reference_markdown <output_file> [<node_manifest_identity> ...]\n";
40 return EXIT_FAILURE;
41 }
42
43 try {
44 const std::filesystem::path output_file = std::filesystem::absolute(auto_apms_util::trimWhitespaces(argv[1]));
45 if (output_file.empty()) {
46 throw std::runtime_error("Argument output_file must not be empty.");
47 }
48
49 // Ensure correct extensions
50 if (output_file.extension() != ".md") {
51 throw std::runtime_error("Output file '" + output_file.string() + "' has wrong extension. Must be '.md'.");
52 }
53
54 core::NodeManifest node_manifest;
55 std::vector<std::string> input_packages;
56 for (int i = 2; i < argc; ++i) {
57 const std::string package_name = auto_apms_util::splitString(argv[i], "::")[0];
58 input_packages.push_back(package_name);
59 if (package_name == "include_native") continue;
60
61 // Throws if there are registration names which exist multiple times
63 }
64 const bool include_native = auto_apms_util::contains(input_packages, std::string("include_native"));
65 const std::string native_package_name = "auto_apms_behavior_tree (BehaviorTree.CPP)";
66 for (std::string & ele : input_packages) {
67 if (ele == "include_native") ele = native_package_name;
68 }
69
70 // Search all packages
71 core::NodeRegistrationLoader::SharedPtr node_loader_ptr = core::NodeRegistrationLoader::make_shared();
72 core::TreeDocument doc(core::TreeDocument::BTCPP_FORMAT_DEFAULT_VERSION, node_loader_ptr);
73 doc.registerNodes(node_manifest);
74 std::map<std::string, std::string> package_for_class = node_loader_ptr->getClassPackageMap();
75 const NodeModelMap model_map = doc.getNodeModel(include_native);
76 core::NodeRegistrationOptions native_node_options;
77 native_node_options.class_name = "None";
78
79 // Verify that package information is available
80 const std::set<std::string> native_node_names = BT::BehaviorTreeFactory().builtinNodes();
81 for (const auto & [registration_name, _] : model_map) {
82 if (!node_manifest.contains(registration_name)) {
83 if (native_node_names.find(registration_name) != native_node_names.end()) {
84 node_manifest.add(registration_name, native_node_options);
85 package_for_class[node_manifest[registration_name].class_name] = native_package_name;
86 } else {
87 throw std::runtime_error("Package for node '" + registration_name + "' is unknown.");
88 }
89 }
90 }
91
92 auto lowerize = [](const std::string & str) {
93 std::string ret(str);
94 std::transform(ret.begin(), ret.end(), ret.begin(), [](unsigned char c) { return std::tolower(c); });
95 return ret;
96 };
97
98 auto escapePipes = [](const std::string & str) {
99 std::string ret;
100 ret.reserve(str.size());
101 for (char c : str) {
102 if (c == '|')
103 ret += "\\|";
104 else
105 ret += c;
106 }
107 return ret;
108 };
109
110 std::ostringstream content;
111 content << R"(<!-- markdownlint-disable MD024 MD041 MD060 -->
112| Registration Name | Class Name | Package |
113| :--- | :---: | :---: |)";
114 for (const std::string & package_name : input_packages) {
115 for (const auto & [registration_name, model] : model_map) {
116 const core::NodeRegistrationOptions & options = node_manifest[registration_name];
117 if (package_name != package_for_class[options.class_name]) continue;
118 content << "\n| [" << registration_name << "](#" << lowerize(registration_name) << ") | `" << options.class_name
119 << "` | " << package_for_class[options.class_name] << " |";
120 }
121 }
122 content << "\n";
123 for (const std::string & package_name : input_packages) {
124 content << "\n## " << package_name << "\n";
125 for (const auto & [registration_name, model] : model_map) {
126 const core::NodeRegistrationOptions & options = node_manifest[registration_name];
127 if (package_name != package_for_class[options.class_name]) continue;
128 // clang-format off
129 content << R"(
130)" << "### " << registration_name << R"(
131)";
132 if (options.class_name != "None") {
133 content << R"(
134**Plugin Class:** `)" << options.class_name << R"(`
135)";
136 }
137 content << R"(
138**C++ Model:** `)" << auto_apms_util::splitString(package_name, " ")[0] << "::" << registration_name << R"(`
139
140**Node Type:** `)" << BT::toStr(model.type) << R"(`
141
142**Description:** )" << (options.description.empty() ? "No description available." : options.description) << R"(
143)";
144 if (model.port_infos.empty()) {
145 content << "\n*This node doesn't have any ports.*\n";
146 continue;
147 }
148 std::vector<NodePortInfo> inputs;
149 std::vector<NodePortInfo> outputs;
150 std::vector<NodePortInfo> inouts;
151 for (const NodePortInfo & port_info : model.port_infos) {
152 switch (port_info.port_direction) {
153 case BT::PortDirection::INPUT:
154 inputs.push_back(port_info);
155 break;
156 case BT::PortDirection::OUTPUT:
157 outputs.push_back(port_info);
158 break;
159 case BT::PortDirection::INOUT:
160 inouts.push_back(port_info);
161 break;
162 }
163 }
164 if (!inputs.empty()) {
165 content << R"(
166#### Input Ports
167
168| Input Name | Type | Default Value | Description |
169| :--- | :---: | :---: | :--- |
170)";
171 for (const NodePortInfo & port_info : inputs) {
172 content << "| **" << port_info.port_name << "** | `" << port_info.port_type << "` | " << (port_info.port_has_default ? escapePipes(port_info.port_default) : "❌") << " | " << escapePipes(port_info.port_description) << " |\n";
173 }
174 }
175 if (!outputs.empty()) {
176 content << R"(
177#### Output Ports
178
179| Output Name | Type | Default Value | Description |
180| :--- | :---: | :---: | :--- |
181)";
182 for (const NodePortInfo & port_info : outputs) {
183 content << "| **" << port_info.port_name << "** | `" << port_info.port_type << "` | " << (port_info.port_has_default ? escapePipes(port_info.port_default) : "❌") << " | " << escapePipes(port_info.port_description) << " |\n";
184 }
185 }
186 if (!inouts.empty()) {
187 content << R"(
188#### Bidirectional Ports
189
190| Port Name | Type | Default Value | Description |
191| :--- | :---: | :---: | :--- |
192)";
193 for (const NodePortInfo & port_info : inouts) {
194 content << "| **" << port_info.port_name << "** | `" << port_info.port_type << "` | " << (port_info.port_has_default ? escapePipes(port_info.port_default) : "❌") << " | " << escapePipes(port_info.port_description) << " |\n";
195 }
196 }
197 // clang-format on
198 }
199 }
200
201 std::ofstream out_stream(output_file);
202 if (out_stream.is_open()) {
203 out_stream << content.str();
204 out_stream.close();
205 } else {
206 throw std::runtime_error("Error opening markdown output file '" + output_file.string() + "'");
207 }
208 } catch (const std::exception & e) {
209 std::cerr << "ERROR (create_node_reference_markdown): " << e.what() << "\n";
210 return EXIT_FAILURE;
211 }
212
213 return EXIT_SUCCESS;
214}
Data structure for information about which behavior tree node plugin to load and how to configure the...
NodeManifest & add(const std::string &node_name, const RegistrationOptions &opt)
Add registration options for a behavior tree node to the manifest.
bool contains(const std::string &node_name) const
Determine if a behavior tree node has been added to the manifest.
NodeManifest & merge(const NodeManifest &other, bool replace=false)
Merges another NodeManifest with this one.
static NodeManifest fromResource(const NodeManifestResourceIdentity &search_identity)
Create a node manifest from an installed resource.
Document Object Model (DOM) for the behavior tree XML schema. This class offers a programmatic approa...
std::string trimWhitespaces(const std::string &str)
Trim whitespaces from both ends of a string.
Definition string.cpp:84
bool contains(const ContainerT< ValueT, AllocatorT > &c, const ValueT &val)
Check whether a particular container structure contains a value.
Definition container.hpp:36
std::vector< std::string > splitString(const std::string &str, const std::string &delimiter, bool remove_empty=true)
Split a string into multiple tokens using a specific delimiter string (Delimiter may consist of multi...
Definition string.cpp:24
Models for all available behavior tree nodes.
Powerful tooling for incorporating behavior trees for task development.
Definition behavior.hpp:32
std::map< std::string, NodeModel > NodeModelMap
Mapping of node registration names and their implementation details.
Implementation details of a single data port.
std::string port_default
Default value of the port encoded as string.
bool port_has_default
Flag whether the port implements a default value or not.
BT::PortDirection port_direction
Direction of the port.
std::string port_type
String representation of the C++ type given to the port.
std::string port_name
Name of the port.
Parameters for loading and registering a behavior tree node class from a shared library using e....
std::string description
Short description of the behavior tree node's purpose and use-case.
std::string class_name
Fully qualified name of the behavior tree node plugin class.