diff --git a/launch/launch/actions/__init__.py b/launch/launch/actions/__init__.py index 0038d5bb5..d4bc80ca2 100644 --- a/launch/launch/actions/__init__.py +++ b/launch/launch/actions/__init__.py @@ -14,6 +14,7 @@ """actions Module.""" +from .declare_boolean_launch_argument import DeclareBooleanLaunchArgument from .declare_launch_argument import DeclareLaunchArgument from .append_environment_variable import AppendEnvironmentVariable # noqa: I100 from .emit_event import EmitEvent @@ -49,6 +50,7 @@ __all__ = [ 'AppendEnvironmentVariable', 'DeclareLaunchArgument', + 'DeclareBooleanLaunchArgument', 'EmitEvent', 'ExecuteLocal', 'ExecuteProcess', diff --git a/launch/launch/actions/declare_boolean_launch_argument.py b/launch/launch/actions/declare_boolean_launch_argument.py new file mode 100644 index 000000000..d446edd95 --- /dev/null +++ b/launch/launch/actions/declare_boolean_launch_argument.py @@ -0,0 +1,84 @@ +# Copyright 2026 Metro Robots +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module for the DeclareBooleanLaunchArgument action.""" + +from typing import Any +from typing import Dict +from typing import Optional +from typing import Text +from typing import Tuple +from typing import Type + +from .declare_launch_argument import DeclareLaunchArgument +from ..frontend import Entity +from ..frontend import expose_action +from ..frontend import Parser # noqa: F401 +from ..some_substitutions_type import SomeSubstitutionsType + + +@expose_action('bool_arg') +class DeclareBooleanLaunchArgument(DeclareLaunchArgument): + """ + Action that declares a new boolean launch argument. + + This is a bit of syntactic sugar for + :py:class:`launch.actions.DeclareLaunchArgument` + that sets the choices to be either true or false (caps-insensitive) + + .. doctest:: + + >>> ld = LaunchDescription([ + ... DeclareBooleanLaunchArgument('simple_argument'), # default value is False + ... DeclareBooleanLaunchArgument('with_default_value', default_value=True), + ... # other actions here, ... + ... ]) + + .. code-block:: xml + + + + + + """ + + def __init__( + self, + name: Text, + *, + default_value: Optional[SomeSubstitutionsType | bool] = None, + **kwargs: Any + ) -> None: + """Create a DeclareBooleanLaunchArgument action.""" + super().__init__( + name=name, + default_value=(default_value if not isinstance(default_value, bool) + else str(default_value)), + choices=['true', 'false', 'True', 'False'], + **kwargs + ) + + @classmethod + def parse( + cls, + entity: Entity, + parser: 'Parser' + ) -> Tuple[Type['DeclareBooleanLaunchArgument'], Dict[str, Any]]: + """Parse `bool_arg` tag.""" + _, kwargs = super().parse(entity, parser) + + if 'choices' in kwargs: + raise ValueError('Cannot specify choices for bool_arg') + + return cls, kwargs diff --git a/launch/test/launch/actions/test_declare_boolean_launch_argument.py b/launch/test/launch/actions/test_declare_boolean_launch_argument.py new file mode 100644 index 000000000..d6feead65 --- /dev/null +++ b/launch/test/launch/actions/test_declare_boolean_launch_argument.py @@ -0,0 +1,46 @@ +# Copyright 2026 Metro Robots +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the DeclareBooleanLaunchArgument action class.""" + +# from launch import LaunchContext +from launch.actions import DeclareBooleanLaunchArgument + +import pytest + + +def test_declare_launch_argument_constructors(): + """Test the constructors for DeclareLaunchArgument class.""" + DeclareBooleanLaunchArgument('name') + + # All possible default values + DeclareBooleanLaunchArgument('name', default_value='True') + DeclareBooleanLaunchArgument('name', default_value='true') + DeclareBooleanLaunchArgument('name', default_value='False') + DeclareBooleanLaunchArgument('name', default_value='false') + DeclareBooleanLaunchArgument('name', default_value=True) + DeclareBooleanLaunchArgument('name', default_value=False) + + # With description + DeclareBooleanLaunchArgument('name', description='description') + + # With invalid default value + with pytest.raises(RuntimeError) as excinfo: + DeclareBooleanLaunchArgument('name', default_value='invalid') + assert 'not in provided choices' in str(excinfo.value) + + # With invalid choices override + with pytest.raises(TypeError) as excinfo: + DeclareBooleanLaunchArgument('name', choices=['verdad', 'no verdad']) + assert "multiple values for keyword argument 'choices'" in str(excinfo.value)