AutoAPMS
Resilient Robot Mission Management
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// https://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 // Throw if there a registration name exists 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 core::TreeDocument::NodeModelMap model_map = doc.getNodeModel(include_native);
76 core::NodeRegistrationOptions native_node_options;
77 native_node_options.class_name = "❌";
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 unkown.");
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 std::ostringstream content;
99 content << R"(<!-- markdownlint-disable MD024 MD041 -->
100## Overview
101
102| Registration Name | Class Name | Package |
103| :--- | :---: | :---: |)";
104 for (const std::string & package_name : input_packages) {
105 for (const auto & [registration_name, model] : model_map) {
106 const core::NodeRegistrationOptions & options = node_manifest[registration_name];
107 if (package_name != package_for_class[options.class_name]) continue;
108 content << "\n| [" << registration_name << "](#" << lowerize(registration_name) << ") | `" << options.class_name
109 << "` | " << package_for_class[options.class_name] << " |";
110 }
111 }
112 content << "\n";
113 for (const std::string & package_name : input_packages) {
114 content << "\n## " << package_name << "\n";
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 // clang-format off
119 content << R"(
120)" << "### " << registration_name << R"(
121
122**Class:** `)" << options.class_name << R"(`
123
124**Node Type:** `)" << BT::toStr(model.type) << R"(`
125)";
126 if (model.port_infos.empty()) {
127 content << "\n*This node doesn't have any ports.*\n";
128 continue;
129 }
130 std::vector<core::TreeDocument::NodePortInfo> inputs;
131 std::vector<core::TreeDocument::NodePortInfo> outputs;
132 std::vector<core::TreeDocument::NodePortInfo> inouts;
133 for (const core::TreeDocument::NodePortInfo & port_info : model.port_infos) {
134 switch (port_info.port_direction) {
135 case BT::PortDirection::INPUT:
136 inputs.push_back(port_info);
137 break;
138 case BT::PortDirection::OUTPUT:
139 outputs.push_back(port_info);
140 break;
141 case BT::PortDirection::INOUT:
142 inouts.push_back(port_info);
143 break;
144 }
145 }
146 if (!inputs.empty()) {
147 content << R"(
148#### Input Ports
149
150| Input Name | Type | Default Value | Description |
151| :--- | :---: | :---: | :--- |
152)";
153 for (const core::TreeDocument::NodePortInfo & port_info : inputs) {
154 content << "| **" << port_info.port_name << "** | `" << port_info.port_type << "` | " << (port_info.port_has_default ? port_info.port_default : "❌") << " | " << port_info.port_description << " |\n";
155 }
156 }
157 if (!outputs.empty()) {
158 content << R"(
159#### Output Ports
160
161| Output Name | Type | Default Value | Description |
162| :--- | :---: | :---: | :--- |
163)";
164 for (const core::TreeDocument::NodePortInfo & port_info : outputs) {
165 content << "| **" << port_info.port_name << "** | `" << port_info.port_type << "` | " << (port_info.port_has_default ? port_info.port_default : "❌") << " | " << port_info.port_description << " |\n";
166 }
167 }
168 if (!inouts.empty()) {
169 content << R"(
170#### Bidirectional Ports
171
172| Port Name | Type | Default Value | Description |
173| :--- | :---: | :---: | :--- |
174)";
175 for (const core::TreeDocument::NodePortInfo & port_info : inouts) {
176 content << "| **" << port_info.port_name << "** | `" << port_info.port_type << "` | " << (port_info.port_has_default ? port_info.port_default : "❌") << " | " << port_info.port_description << " |\n";
177 }
178 }
179 // clang-format on
180 }
181 }
182
183 std::ofstream out_stream(output_file);
184 if (out_stream.is_open()) {
185 out_stream << content.str();
186 out_stream.close();
187 } else {
188 throw std::runtime_error("Error opening markdown output file '" + output_file.string() + "'");
189 }
190 } catch (const std::exception & e) {
191 std::cerr << "ERROR (create_node_reference_markdown): " << e.what() << "\n";
192 return EXIT_FAILURE;
193 }
194
195 return EXIT_SUCCESS;
196}
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 fromResourceIdentity(const std::string &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::map< std::string, NodeModel > NodeModelMap
Mapping of node registration names and their implementation details.
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.
Useful tooling for incorporating behavior trees for task development.
Definition builder.hpp:30
Parameters for loading and registering a behavior tree node class from a shared library using e....
std::string class_name
Fully qualified name of the behavior tree node plugin class.
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.