Skip to content

Commit e1b8aed

Browse files
authored
Automatically create output directories for all LOBSTER tools (#521)
Automatically create output directories for all LOBSTER tools to prevent crashes when paths don't exist Issue: SWF-20884
1 parent a4a99c5 commit e1b8aed

20 files changed

+80
-54
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55

66
### 1.0.3-dev
77

8+
* All tools now automatically create output directories if they don't exist.
9+
Previously, tools would crash with an exception if the specified output
10+
directory path did not exist. This enhancement improves usability and
11+
prevents unexpected failures when working with nested directory structures.
12+
813
* `lobster-codebeamer`:
914
- Improved error messages with detailed troubleshooting information:
1015
- Connection timeout errors now include the URL and suggest increasing timeout parameter

lobster/common/io.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
# License along with this program. If not, see
1818
# <https://www.gnu.org/licenses/>.
1919

20+
import os
2021
import json
2122
from typing import Dict, Optional, Sequence, TextIO, Type, Union, Iterable
2223

@@ -145,3 +146,10 @@ def signal_duplicate_items(
145146
f"{items[item.tag.key()].location.to_string()}",
146147
fatal=(counter == len(duplicate_items)),
147148
)
149+
150+
151+
def ensure_output_directory(file_path: str) -> None:
152+
"""Create parent directories for the output file if they don't exist."""
153+
output_dir = os.path.dirname(file_path)
154+
if output_dir:
155+
os.makedirs(output_dir, exist_ok=True)

lobster/common/multi_file_input_tool.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from typing import Iterable, List, Optional, Type, Union
2222
from lobster.common.errors import Message_Handler
2323
from lobster.common.items import Requirement, Implementation, Activity
24-
from lobster.common.io import lobster_write
24+
from lobster.common.io import lobster_write, ensure_output_directory
2525
from lobster.common.meta_data_tool_base import MetaDataToolBase
2626
from lobster.common.multi_file_input_config import Config
2727
from lobster.common.file_collector import FileCollector
@@ -132,6 +132,7 @@ def _write_output(
132132
out_file: str,
133133
items: List[Union[Activity, Implementation, Requirement]],
134134
):
135+
ensure_output_directory(out_file)
135136
with open(out_file, "w", encoding="UTF-8") as fd:
136137
lobster_write(fd, schema, self.name, items)
137138
print(f"{self.name}: wrote {len(items)} items to {out_file}")

lobster/common/report.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
# You should have received a copy of the GNU Affero General Public
1717
# License along with this program. If not, see
1818
# <https://www.gnu.org/licenses/>.
19-
2019
import json
2120
from collections import OrderedDict
2221
from dataclasses import dataclass
@@ -25,7 +24,7 @@
2524
from lobster.common.items import Tracing_Status, Requirement, Implementation, Activity
2625
from lobster.common.parser import load as load_config
2726
from lobster.common.errors import Message_Handler
28-
from lobster.common.io import lobster_read
27+
from lobster.common.io import lobster_read, ensure_output_directory
2928
from lobster.common.location import File_Reference
3029

3130

@@ -141,6 +140,7 @@ def write_report(self, filename):
141140
"matrix" : [],
142141
}
143142

143+
ensure_output_directory(filename)
144144
with open(filename, "w", encoding="UTF-8") as fd:
145145
json.dump(report, fd, indent=2)
146146
fd.write("\n")

lobster/common/tool.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from lobster.common.errors import Message_Handler
3030
from lobster.common.location import File_Reference
3131
from lobster.common.items import Requirement, Implementation, Activity
32-
from lobster.common.io import lobster_write
32+
from lobster.common.io import lobster_write, ensure_output_directory
3333
from lobster.common.meta_data_tool_base import MetaDataToolBase
3434

3535

@@ -262,6 +262,7 @@ def write_output(
262262
for item in items)
263263

264264
if options.out:
265+
ensure_output_directory(options.out)
265266
with open(options.out, "w", encoding="UTF-8") as fd:
266267
lobster_write(fd, self.schema, self.name, items)
267268
print(f"{self.name}: wrote {len(items)} items to {options.out}")

lobster/tools/codebeamer/codebeamer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
from lobster.common.items import Tracing_Tag, Requirement, Implementation, Activity
5555
from lobster.common.location import Codebeamer_Reference
5656
from lobster.common.errors import Message_Handler, LOBSTER_Error
57-
from lobster.common.io import lobster_read, lobster_write
57+
from lobster.common.io import lobster_read, lobster_write, ensure_output_directory
5858
from lobster.common.meta_data_tool_base import MetaDataToolBase
5959
from lobster.tools.codebeamer.bearer_auth import BearerAuth
6060
from lobster.tools.codebeamer.config import AuthenticationConfig, Config
@@ -657,6 +657,7 @@ def _execute(self, options: argparse.Namespace) -> None:
657657

658658
def _get_out_stream(config_out: Optional[str]) -> TextIO:
659659
if config_out:
660+
ensure_output_directory(config_out)
660661
return open(config_out, "w", encoding="UTF-8")
661662
return sys.stdout
662663

@@ -672,6 +673,7 @@ def lobster_codebeamer(config: Config, out_file: str) -> None:
672673
"""
673674
# This is an API function.
674675
items = get_query(config, config.import_query)
676+
ensure_output_directory(out_file)
675677
with open(out_file, "w", encoding="UTF-8") as fd:
676678
_cb_items_to_lobster(items, config, fd)
677679

lobster/tools/core/html_report/html_report.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from lobster.common.version import LOBSTER_VERSION
3131
from lobster.htmldoc import htmldoc
3232
from lobster.common.report import Report
33+
from lobster.common.io import ensure_output_directory
3334
from lobster.common.location import (Void_Reference,
3435
File_Reference,
3536
Github_Reference,
@@ -286,6 +287,14 @@ def generate_custom_data(report) -> str:
286287
return "".join(content)
287288

288289

290+
def write_html_to_file(html_content: str, output_path: str) -> None:
291+
"""Write HTML content to file, creating parent directories if needed."""
292+
ensure_output_directory(output_path)
293+
with open(output_path, "w", encoding="UTF-8") as fd:
294+
fd.write(html_content)
295+
fd.write("\n")
296+
297+
289298
def write_html(report, dot, high_contrast, render_md) -> str:
290299
assert isinstance(report, Report)
291300

@@ -613,9 +622,7 @@ def _run_impl(self, options: argparse.Namespace) -> int:
613622
high_contrast = options.high_contrast,
614623
render_md = options.render_md,
615624
)
616-
with open(options.out, "w", encoding="UTF-8") as fd:
617-
fd.write(html_content)
618-
fd.write("\n")
625+
write_html_to_file(html_content, options.out)
619626
print(f"LOBSTER HTML report written to {options.out}")
620627

621628
return 0
@@ -646,9 +653,7 @@ def lobster_html_report(
646653
high_contrast=high_contrast,
647654
render_md=render_md,
648655
)
649-
with open(output_html_path, "w", encoding="UTF-8") as fd:
650-
fd.write(html_content)
651-
fd.write("\n")
656+
write_html_to_file(html_content, output_html_path)
652657

653658

654659
def main(args: Optional[Sequence[str]] = None) -> int:

lobster/tools/cpptest/cpptest.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import sys
2121
from argparse import Namespace
22-
import os.path
22+
import os
2323
from copy import copy
2424
from dataclasses import dataclass, field
2525
from typing import List, Optional, Sequence, Union
@@ -29,7 +29,7 @@
2929
from lobster.common.exceptions import LOBSTER_Exception
3030
from lobster.common.items import Tracing_Tag, Activity
3131
from lobster.common.location import File_Reference
32-
from lobster.common.io import lobster_write
32+
from lobster.common.io import lobster_write, ensure_output_directory
3333
from lobster.common.file_tag_generator import FileTagGenerator
3434
from lobster.tools.cpptest.constants import Constants
3535
from lobster.tools.cpptest.requirements_parser import \
@@ -303,16 +303,16 @@ def write_lobster_items_output_dict(lobster_items_output_dict: dict):
303303
lobster_items_dict.update(orphan_test_items)
304304
item_count = len(lobster_items_dict)
305305

306-
if output_file_name:
307-
with open(output_file_name, "w", encoding="UTF-8") as output_file:
308-
lobster_write(
309-
output_file,
310-
Activity,
311-
lobster_generator,
312-
lobster_items_dict.values()
313-
)
314-
print(f'Written {item_count} lobster items to '
315-
f'"{output_file_name}".')
306+
ensure_output_directory(output_file_name)
307+
with open(output_file_name, "w", encoding="UTF-8") as output_file:
308+
lobster_write(
309+
output_file,
310+
Activity,
311+
lobster_generator,
312+
lobster_items_dict.values()
313+
)
314+
print(f'Written {item_count} lobster items to '
315+
f'"{output_file_name}".')
316316

317317

318318
def run_lobster_cpptest(config: Config):

lobster/tools/gtest/gtest.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919

2020
from argparse import Namespace
2121
import sys
22-
import os.path
22+
import os
2323
from typing import Optional, Sequence
2424
import xml.etree.ElementTree as ET
2525

2626
from lobster.common.items import Tracing_Tag, Activity
2727
from lobster.common.location import Void_Reference, File_Reference
28-
from lobster.common.io import lobster_write
28+
from lobster.common.io import lobster_write, ensure_output_directory
2929
from lobster.common.meta_data_tool_base import MetaDataToolBase
3030

3131

@@ -141,6 +141,7 @@ def _run_impl(self, options: Namespace) -> int:
141141
items.append(item)
142142

143143
if options.out:
144+
ensure_output_directory(options.out)
144145
with open(options.out, "w", encoding="UTF-8") as fd:
145146
lobster_write(fd, Activity, "lobster_gtest", items)
146147
print(f"Written output for {len(items)} items to {options.out}")

lobster/tools/python/python.py

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

3131
from lobster.common.items import Tracing_Tag, Implementation, Activity
3232
from lobster.common.location import File_Reference
33-
from lobster.common.io import lobster_write
33+
from lobster.common.io import lobster_write, ensure_output_directory
3434
from lobster.common.meta_data_tool_base import MetaDataToolBase
3535

3636
LOBSTER_TRACE_PREFIX = "# lobster-trace: "
@@ -534,6 +534,7 @@ def _run_impl(self, options: Namespace) -> int:
534534
schema = Implementation
535535

536536
if options.out:
537+
ensure_output_directory(options.out)
537538
with open(options.out, "w", encoding="UTF-8") as fd:
538539
lobster_write(fd, schema, "lobster_python", items)
539540
print(f"Written output for {len(items)} items to {options.out}")

0 commit comments

Comments
 (0)