Skip to content

Commit f7ba590

Browse files
Add update verb (#48)
* Add update.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * initial document parser * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * rename type to data_type * add update verb to entry_points * initial update documentation * update parameters * from dict to lists * create Node class * get running node interface * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Pump version to 1.1.0 * WIP * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add update.py * initial document parser * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * rename type to data_type * add update verb to entry_points * initial update documentation * update parameters * from dict to lists * create Node class * get running node interface * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Pump version to 1.1.0 * WIP * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove unused code * Update version to 1.2.0 * Remove unused Node class * add node_name as an argument * Implement DocParser * fix bug with description * get nodes and interfaces * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * style changes * style changes * WIP: update doc method * handle same params but different types and describtions * remove white space * WIP: doc updater * Delet the removed APIs * Up the version to 2.2.0 * Update README.md * Update README.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 236c17c commit f7ba590

File tree

5 files changed

+190
-17
lines changed

5 files changed

+190
-17
lines changed

README.md

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,19 @@
66

77
## Overview
88

9-
The `ros2autodoc` package provides a ROS2 command line interface tool to automatically generate documentation for ROS2 nodes.
10-
The tool outputs an initial documentation file detailing the interface (parameters, publishers, subscribers, services and actions) for a running ROS2 node. The package was tested with ROS2 Foxy, Humble and Iron.
9+
The `ros2autodoc` package extends the ROS 2 CLI tools to generate API documentation for ROS2 nodes, update previously generated documentation or check if a node is documented for use in CI/CD jobs. The tool outputs a markdown file in the style of ROS Wiki.
1110

1211
## Installation
1312

14-
1. Install a recent ROS2 version.
15-
2. Make sure that `colcon` is installed:
16-
17-
```shell
18-
sudo apt install python3-colcon-common-extensions
19-
```
20-
21-
3. Clone this repo into your workspace:
13+
1. Clone this repo into your workspace:
2214

2315
```shell
2416
mkdir -p ~/ros2_ws/src
2517
cd ~/ros2_ws
2618
git clone https://github.com/3473f/ros2autodoc src/ros2autodoc
2719
```
2820

29-
4. Build the workspace:
21+
2. Build the workspace:
3022

3123
```shell
3224
colcon build
@@ -40,7 +32,9 @@ source install/setup.bash
4032
```shell
4133
$ ros2 autodoc generate --help
4234

43-
usage: ros2 autodoc generate [-h] [--nodes [node ...]] [--executables [executables ...]] [--output-dir] [--seperate-files] package_name
35+
usage: ros2 autodoc generate [-h] [--nodes [node ...]] [--executables [executables ...]] [--launch-file launch_file]
36+
[--output-dir] [--seperate-files]
37+
package_name
4438

4539
Automatically generate documentation for a ROS2 node
4640

@@ -52,8 +46,12 @@ options:
5246
--nodes [node ...] name of the nodes to be documented.
5347
--executables [executables ...]
5448
name of the executables for the nodes to be documented.
55-
--output-dir the directory where documentation should be written. If not specified, the file will be saved to the current directory.
56-
--seperate-files when this option is set, the node documentation will be written to separate files and no package documentation will be generated
49+
--launch-file launch_file
50+
name of the launch file to start the nodes.
51+
--output-dir the directory where documentation should be written. If not specified, the file will be saved
52+
to the current directory.
53+
--seperate-files when this option is set, the node documentation will be written to separate files and no
54+
package documentation will be generated.
5755

5856
```
5957
@@ -72,7 +70,7 @@ This should output the following [README.md](https://github.com/3473f/ros2autodo
7270
```shell
7371
$ ros2 autodoc check --help
7472

75-
usage: ros2 autodoc check [-h] [--nodes [node ...]] [--executables [executables ...]] package_name input_file
73+
usage: ros2 autodoc check [-h] [--launch-file launch_file] [--nodes [node ...]] [--executables [executables ...]] package_name input_file
7674

7775
Check if a ROS2 node API is documented
7876

@@ -82,6 +80,8 @@ positional arguments:
8280

8381
options:
8482
-h, --help show this help message and exit
83+
--launch-file launch_file
84+
name of the launch file to start the nodes.
8585
--nodes [node ...] name of the nodes to be documented.
8686
--executables [executables ...]
8787
name of the executables for the nodes to be documented.
@@ -100,3 +100,18 @@ This should output the following and exit
100100
```shell
101101
Node 'turtlesim' interfaces are correctly listed.
102102
```
103+
104+
### Update
105+
106+
```shell
107+
usage: ros2 autodoc update [-h] [node ...] input_file
108+
109+
Update the documentation of a ROS2 node
110+
111+
positional arguments:
112+
node name of the nodes to be documented. If not specified, all running nodes from the package will be documented.
113+
input_file absolute path of the README.md file to be updated.
114+
115+
options:
116+
-h, --help show this help message and exit
117+
```

package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0"?>
22
<package format="3">
33
<name>ros2autodoc</name>
4-
<version>2.1.2</version>
4+
<version>2.2.0</version>
55
<description>
66
The ROS2 autodoc project provides a CLI command to automatically generate ROS-Wiki-style documentation template nodes in markdown syntax.
77
</description>

ros2autodoc/api/__init__.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
# ROS2 pkg API
99
from ros2pkg.api import get_executable_paths, get_package_names
1010

11+
from ros2autodoc.api.doc_parser import DocParser
1112
from ros2autodoc.api.doc_parser import DocParser
1213
from ros2autodoc.api.doc_writer import DocWriter
1314
from ros2autodoc.api.node_interface_collector import NodeInterfaceCollector
1415

16+
TODO = "TODO: description"
17+
1518

1619
def check_for_package(package_name):
1720
"""Check if the given package is available."""
@@ -116,3 +119,123 @@ def check_interface(interface_type, doc_interface, node_interface):
116119
print(f"Type of '{doc_item}' {interface_type} doesn't match.")
117120
return False
118121
return True
122+
123+
124+
def update_documentation(node, node_name, file_path):
125+
"""Update the documentation for the given node."""
126+
# Parse the document
127+
parser = DocParser(file_path)
128+
parser.parse()
129+
doc_interface = parser.get_node_interface(node_name)
130+
if not doc_interface:
131+
print(f"Node '{node_name}' was not found in the documentation.")
132+
return
133+
134+
# Get the current node interface
135+
interface_collector = NodeInterfaceCollector(node, node_name)
136+
node_interface = interface_collector.get_interfaces()
137+
138+
# Check if parameter names are the same
139+
for node_param in node_interface["parameters"]:
140+
if node_param in doc_interface["parameters"]:
141+
# Update type if different
142+
if node_interface["parameters"][node_param]["type"] != doc_interface["parameters"][node_param]["type"]:
143+
print(f"Updating parameter type for {node_param}")
144+
doc_interface["parameters"][node_param]["type"] = node_interface["parameters"][node_param]["type"]
145+
# Update description if different and not TODO
146+
if node_interface["parameters"][node_param]["description"] != doc_interface["parameters"][node_param]["description"] and node_interface["parameters"][node_param]["description"] != TODO:
147+
print(f"Updating parameter description for {node_param}")
148+
doc_interface["parameters"][node_param]["description"] = node_interface["parameters"][node_param]["description"]
149+
else:
150+
print(f"Adding missing parameter: {node_param}")
151+
doc_interface["parameters"][node_param] = node_interface["parameters"][node_param]
152+
153+
# Check if subscriber names are the same
154+
for node_sub in node_interface["subscribers"]:
155+
if node_sub in doc_interface["subscribers"]:
156+
# Update type if different
157+
if node_interface["subscribers"][node_sub]["type"] != doc_interface["subscribers"][node_sub]["type"]:
158+
print(f"Updating subscriber type for {node_sub}")
159+
doc_interface["subscribers"][node_sub]["type"] = node_interface["subscribers"][node_sub]["type"]
160+
# Update description if different and not TODO
161+
if node_interface["subscribers"][node_sub]["description"] != doc_interface["subscribers"][node_sub]["description"] and node_interface["subscribers"][node_sub]["description"] != TODO:
162+
print(f"Updating subscriber description for {node_sub}")
163+
doc_interface["subscribers"][node_sub]["description"] = node_interface["subscribers"][node_sub]["description"]
164+
else:
165+
print(f"Adding missing subscriber: {node_sub}")
166+
doc_interface["subscribers"][node_sub] = node_interface["subscribers"][node_sub]
167+
168+
# Check if publisher names are the same
169+
for node_pub in node_interface["publishers"]:
170+
if node_pub in doc_interface["publishers"]:
171+
# Update type if different
172+
if node_interface["publishers"][node_pub]["type"] != doc_interface["publishers"][node_pub]["type"]:
173+
print(f"Updating publisher type for {node_pub}")
174+
doc_interface["publishers"][node_pub]["type"] = node_interface["publishers"][node_pub]["type"]
175+
# Update description if different and not TODO
176+
if node_interface["publishers"][node_pub]["description"] != doc_interface["publishers"][node_pub]["description"] and node_interface["publishers"][node_pub]["description"] != TODO:
177+
print(f"Updating publisher description for {node_pub}")
178+
doc_interface["publishers"][node_pub]["description"] = node_interface["publishers"][node_pub]["description"]
179+
else:
180+
print(f"Adding missing publisher: {node_pub}")
181+
doc_interface["publishers"][node_pub] = node_interface["publishers"][node_pub]
182+
183+
# Check if service names are the same
184+
for node_srv in node_interface["services"]:
185+
if node_srv in doc_interface["services"]:
186+
# Update type if different
187+
if node_interface["services"][node_srv]["type"] != doc_interface["services"][node_srv]["type"]:
188+
print(f"Updating service type for {node_srv}")
189+
doc_interface["services"][node_srv]["type"] = node_interface["services"][node_srv]["type"]
190+
# Update description if different and not TODO
191+
if node_interface["services"][node_srv]["description"] != doc_interface["services"][node_srv]["description"] and node_interface["services"][node_srv]["description"] != TODO:
192+
print(f"Updating service description for {node_srv}")
193+
doc_interface["services"][node_srv]["description"] = node_interface["services"][node_srv]["description"]
194+
else:
195+
print(f"Adding missing service: {node_srv}")
196+
doc_interface["services"][node_srv] = node_interface["services"][node_srv]
197+
198+
# Check if action names are the same
199+
for node_action in node_interface["actions"]:
200+
if node_action in doc_interface["actions"]:
201+
# Update type if different
202+
if node_interface["actions"][node_action]["type"] != doc_interface["actions"][node_action]["type"]:
203+
print(f"Updating action type for {node_action}")
204+
doc_interface["actions"][node_action]["type"] = node_interface["actions"][node_action]["type"]
205+
# Update description if different and not TODO
206+
if node_interface["actions"][node_action]["description"] != doc_interface["actions"][node_action]["description"] and node_interface["actions"][node_action]["description"] != TODO:
207+
print(f"Updating action description for {node_action}")
208+
doc_interface["actions"][node_action]["description"] = node_interface["actions"][node_action]["description"]
209+
else:
210+
print(f"Adding missing action: {node_action}")
211+
doc_interface["actions"][node_action] = node_interface["actions"][node_action]
212+
213+
# Now that we added all missing elements,
214+
# check if there are any elements in the doc that are not in the node
215+
for doc_param in doc_interface["parameters"].copy():
216+
if doc_param not in node_interface["parameters"]:
217+
print(f"Removing parameter: {doc_param}")
218+
del doc_interface["parameters"][doc_param]
219+
for doc_sub in doc_interface["subscribers"].copy():
220+
if doc_sub not in node_interface["subscribers"]:
221+
print(f"Removing subscriber: {doc_sub}")
222+
del doc_interface["subscribers"][doc_sub]
223+
for doc_pub in doc_interface["publishers"].copy():
224+
if doc_pub not in node_interface["publishers"]:
225+
print(f"Removing publisher: {doc_pub}")
226+
del doc_interface["publishers"][doc_pub]
227+
for doc_srv in doc_interface["services"].copy():
228+
if doc_srv not in node_interface["services"]:
229+
print(f"Removing service: {doc_srv}")
230+
del doc_interface["services"][doc_srv]
231+
for doc_action in doc_interface["actions"].copy():
232+
if doc_action not in node_interface["actions"]:
233+
print(f"Removing action: {doc_action}")
234+
del doc_interface["actions"][doc_action]
235+
236+
# Write to the document
237+
# TODO: remove the old documentation and write a new one
238+
writer = DocWriter(None, node_name, doc_interface)
239+
writer.write(file_path)
240+
241+

ros2autodoc/verb/update.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from os.path import isfile
2+
3+
from ros2cli.node.strategy import NodeStrategy
4+
from ros2cli.verb import VerbExtension
5+
6+
from ros2autodoc.api import check_for_node, update_documentation
7+
8+
9+
class UpdateVerb(VerbExtension):
10+
"""Update the documentation of a ROS2 node."""
11+
12+
def add_arguments(self, parser, cli_name):
13+
parser.add_argument(
14+
"nodes",
15+
metavar="node",
16+
nargs="*",
17+
help="name of the nodes to be documented. If not specified, "
18+
"all running nodes from the package will be documented.",
19+
)
20+
parser.add_argument(
21+
"input_file",
22+
help="absolute path of the README.md file to be updated.",
23+
)
24+
25+
def main(self, *, args):
26+
if not isfile(args.input_file):
27+
return f"File: '{args.input_file}' could not be found."
28+
29+
with NodeStrategy(args) as node:
30+
for node_name in args.nodes:
31+
if not check_for_node(node, f"/{node_name}"):
32+
print(f"Node '{node_name}' is not running and will be ignored.")
33+
continue
34+
update_documentation(node, node_name, args.input_file)

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
setup(
66
name=PACKAGE_NAME,
7-
version="2.1.2",
7+
version="2.2.0",
88
packages=find_packages(exclude=["test"]),
99
data_files=[
1010
("share/" + PACKAGE_NAME, ["package.xml"]),
@@ -32,6 +32,7 @@
3232
],
3333
"ros2autodoc.verb": [
3434
"generate = ros2autodoc.verb.generate:GenerateVerb",
35+
"update = ros2autodoc.verb.update:UpdateVerb",
3536
"check = ros2autodoc.verb.check:CheckVerb",
3637
],
3738
},

0 commit comments

Comments
 (0)