From 5a5349129c7188f5ffea73e1e3d25927b2a9ff91 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Thu, 20 Feb 2020 14:00:21 -0800 Subject: [PATCH 01/13] Complete launch file name for launch files not arguments Signed-off-by: Shane Loretz --- ros2launch/ros2launch/command/launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ros2launch/ros2launch/command/launch.py b/ros2launch/ros2launch/command/launch.py index 5b800e76..b5e88b4c 100644 --- a/ros2launch/ros2launch/command/launch.py +++ b/ros2launch/ros2launch/command/launch.py @@ -55,11 +55,11 @@ def add_arguments(self, parser, cli_name): # TODO(wjwwood) make this not optional when full launch path is supported. nargs='?', help='Name of the launch file') + arg.completer = LaunchFileNameCompleter() arg = parser.add_argument( 'launch_arguments', nargs='*', help="Arguments to the launch file; ':=' (for duplicates, last one wins)") - arg.completer = LaunchFileNameCompleter() def main(self, *, parser, args): """Entry point for CLI program.""" From 29a6503b44d51ccae76a43563c94e8ba1c1d6371 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Thu, 20 Feb 2020 16:53:32 -0800 Subject: [PATCH 02/13] Expose get_launch_file_paths Signed-off-by: Shane Loretz --- ros2launch/ros2launch/api/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ros2launch/ros2launch/api/__init__.py b/ros2launch/ros2launch/api/__init__.py index 2240859a..aa1c3552 100644 --- a/ros2launch/ros2launch/api/__init__.py +++ b/ros2launch/ros2launch/api/__init__.py @@ -17,6 +17,7 @@ from launch.launch_description_sources import InvalidLaunchFileError from launch.launch_description_sources import InvalidPythonLaunchFileError +from .api import get_launch_file_paths from .api import get_share_file_path_from_package from .api import launch_a_launch_file from .api import LaunchFileNameCompleter @@ -27,6 +28,7 @@ __all__ = [ + 'get_launch_file_paths', 'get_share_file_path_from_package', 'InvalidLaunchFileError', 'InvalidPythonLaunchFileError', From 380bdc9dcdbeffd72b4cb871e705751c1ba5754a Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Thu, 20 Feb 2020 16:57:54 -0800 Subject: [PATCH 03/13] Suppress invalid file completions Signed-off-by: Shane Loretz --- ros2launch/ros2launch/command/launch.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ros2launch/ros2launch/command/launch.py b/ros2launch/ros2launch/command/launch.py index b5e88b4c..ce4194d2 100644 --- a/ros2launch/ros2launch/command/launch.py +++ b/ros2launch/ros2launch/command/launch.py @@ -16,6 +16,7 @@ from ament_index_python.packages import get_package_prefix from ament_index_python.packages import PackageNotFoundError +from argcomplete.completers import SuppressCompleter from ros2cli.command import CommandExtension from ros2launch.api import get_share_file_path_from_package from ros2launch.api import launch_a_launch_file @@ -26,6 +27,13 @@ from ros2pkg.api import package_name_completer +class SuppressCompleterWorkaround(SuppressCompleter): + """Workaround https://github.com/kislyuk/argcomplete/pull/289 .""" + + def __call__(self, *args, **kwargs): + return tuple() + + class LaunchCommand(CommandExtension): """Run a launch file.""" @@ -60,6 +68,7 @@ def add_arguments(self, parser, cli_name): 'launch_arguments', nargs='*', help="Arguments to the launch file; ':=' (for duplicates, last one wins)") + arg.completer = SuppressCompleterWorkaround() def main(self, *, parser, args): """Entry point for CLI program.""" From a6564083591a53ff74065c554ea4e05973f549bc Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Thu, 20 Feb 2020 16:58:51 -0800 Subject: [PATCH 04/13] Complete launch files in first argument Signed-off-by: Shane Loretz --- ros2launch/ros2launch/command/launch.py | 39 ++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/ros2launch/ros2launch/command/launch.py b/ros2launch/ros2launch/command/launch.py index ce4194d2..73d0a3c2 100644 --- a/ros2launch/ros2launch/command/launch.py +++ b/ros2launch/ros2launch/command/launch.py @@ -18,6 +18,7 @@ from ament_index_python.packages import PackageNotFoundError from argcomplete.completers import SuppressCompleter from ros2cli.command import CommandExtension +from ros2launch.api import get_launch_file_paths from ros2launch.api import get_share_file_path_from_package from ros2launch.api import launch_a_launch_file from ros2launch.api import LaunchFileNameCompleter @@ -34,6 +35,42 @@ def __call__(self, *args, **kwargs): return tuple() +def package_name_or_launch_file_completer(prefix, parsed_args, **kwargs): + # Complete package names + completions = [n for n in package_name_completer(prefix=prefix, **kwargs)] + + # list of 2-tuples: (directory, part of prefix to prepend) + dirs_to_check = [] + + if os.path.isdir(prefix) and not prefix.endswith(os.sep): + # if prefix is directory 'foo' then suggest 'foo/' + completions.append(prefix + os.sep) + if os.path.isdir(prefix) and prefix.endswith(os.sep): + # if prefix is 'foo/' then check 'foo/' for launch files + dirs_to_check.append((prefix, prefix)) + if not prefix.endswith(os.sep) and os.path.isdir(os.path.dirname(prefix)): + # if prefix is 'foo/bar' and then check 'foo' for launch files + dirname = os.path.dirname(prefix) + basename = os.path.basename(prefix) + prepend = prefix[:-len(basename)] + dirs_to_check.append((dirname, prepend)) + if os.sep not in prefix: + # if prefix is 'foo' then check current directory for files starting with 'foo' + dirs_to_check.append((os.curdir, '')) + + for dirname, prepend in dirs_to_check: + # complete launch files in a directory + for launch_file in get_launch_file_paths(path=dirname): + completions.append(prepend + os.path.basename(launch_file)) + + # complete directories since they may contain launch files + for path in os.listdir(path=dirname): + if os.path.isdir(os.path.join(dirname, path)): + completions.append(prepend + path + os.sep) + + return completions + + class LaunchCommand(CommandExtension): """Run a launch file.""" @@ -57,7 +94,7 @@ def add_arguments(self, parser, cli_name): arg = parser.add_argument( 'package_name', help='Name of the ROS package which contains the launch file') - arg.completer = package_name_completer + arg.completer = package_name_or_launch_file_completer arg = parser.add_argument( 'launch_file_name', # TODO(wjwwood) make this not optional when full launch path is supported. From 8c04d21fc98520d402b7ab568af5edfe43c1dad8 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Thu, 20 Feb 2020 17:34:33 -0800 Subject: [PATCH 05/13] Don't complete recursive launch files Signed-off-by: Shane Loretz --- ros2launch/ros2launch/command/launch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ros2launch/ros2launch/command/launch.py b/ros2launch/ros2launch/command/launch.py index 73d0a3c2..c138e739 100644 --- a/ros2launch/ros2launch/command/launch.py +++ b/ros2launch/ros2launch/command/launch.py @@ -61,7 +61,9 @@ def package_name_or_launch_file_completer(prefix, parsed_args, **kwargs): for dirname, prepend in dirs_to_check: # complete launch files in a directory for launch_file in get_launch_file_paths(path=dirname): - completions.append(prepend + os.path.basename(launch_file)) + if os.path.normpath(os.path.dirname(launch_file)) == os.path.normpath(dirname): + # Get launch_file_paths is recursive; complete only launch files in first level + completions.append(prepend + os.path.basename(launch_file)) # complete directories since they may contain launch files for path in os.listdir(path=dirname): From b81028ab06ebb40b1b169637700f5d0ad07f4e59 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Thu, 20 Feb 2020 17:43:55 -0800 Subject: [PATCH 06/13] Typo in comment Signed-off-by: Shane Loretz --- ros2launch/ros2launch/command/launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ros2launch/ros2launch/command/launch.py b/ros2launch/ros2launch/command/launch.py index c138e739..a2b6665d 100644 --- a/ros2launch/ros2launch/command/launch.py +++ b/ros2launch/ros2launch/command/launch.py @@ -62,7 +62,7 @@ def package_name_or_launch_file_completer(prefix, parsed_args, **kwargs): # complete launch files in a directory for launch_file in get_launch_file_paths(path=dirname): if os.path.normpath(os.path.dirname(launch_file)) == os.path.normpath(dirname): - # Get launch_file_paths is recursive; complete only launch files in first level + # get_launch_file_paths() is recursive; complete only launch files in first level completions.append(prepend + os.path.basename(launch_file)) # complete directories since they may contain launch files From 00f8903978f47238d5530bf65b11d798aa48d8c6 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Mon, 24 Feb 2020 10:44:10 -0800 Subject: [PATCH 07/13] style Signed-off-by: Shane Loretz --- ros2launch/ros2launch/command/launch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ros2launch/ros2launch/command/launch.py b/ros2launch/ros2launch/command/launch.py index a2b6665d..c8273964 100644 --- a/ros2launch/ros2launch/command/launch.py +++ b/ros2launch/ros2launch/command/launch.py @@ -32,12 +32,13 @@ class SuppressCompleterWorkaround(SuppressCompleter): """Workaround https://github.com/kislyuk/argcomplete/pull/289 .""" def __call__(self, *args, **kwargs): - return tuple() + """Make SupressCompleter callable by returning no completions.""" + return () def package_name_or_launch_file_completer(prefix, parsed_args, **kwargs): # Complete package names - completions = [n for n in package_name_completer(prefix=prefix, **kwargs)] + completions = list(package_name_completer(prefix=prefix, **kwargs)) # list of 2-tuples: (directory, part of prefix to prepend) dirs_to_check = [] From 753d630bdad8a47a18e0bb640005ffa73d9e10c1 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Tue, 25 Feb 2020 08:42:09 -0800 Subject: [PATCH 08/13] Add is_launch_file and expose it Signed-off-by: Shane Loretz --- ros2launch/ros2launch/api/__init__.py | 4 ++-- ros2launch/ros2launch/api/api.py | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ros2launch/ros2launch/api/__init__.py b/ros2launch/ros2launch/api/__init__.py index aa1c3552..788b0b21 100644 --- a/ros2launch/ros2launch/api/__init__.py +++ b/ros2launch/ros2launch/api/__init__.py @@ -17,8 +17,8 @@ from launch.launch_description_sources import InvalidLaunchFileError from launch.launch_description_sources import InvalidPythonLaunchFileError -from .api import get_launch_file_paths from .api import get_share_file_path_from_package +from .api import is_launch_file from .api import launch_a_launch_file from .api import LaunchFileNameCompleter from .api import MultipleLaunchFilesError @@ -28,8 +28,8 @@ __all__ = [ - 'get_launch_file_paths', 'get_share_file_path_from_package', + 'is_launch_file', 'InvalidLaunchFileError', 'InvalidPythonLaunchFileError', 'LaunchFileNameCompleter', diff --git a/ros2launch/ros2launch/api/api.py b/ros2launch/ros2launch/api/api.py index bcdf4915..d911d7d0 100644 --- a/ros2launch/ros2launch/api/api.py +++ b/ros2launch/ros2launch/api/api.py @@ -73,16 +73,22 @@ def get_launch_file_paths(*, path): launch_file_paths = [] for root, dirs, files in os.walk(path): for file_name in files: - if file_name.endswith(get_launch_file_paths.extensions): - launch_file_paths.append(os.path.join(root, file_name)) + file_path = os.path.join(root, file_name) + if is_launch_file(path=file_path): + launch_file_paths.append(file_path) return launch_file_paths -get_launch_file_paths.extensions = [ +def is_launch_file(*, path): + """Return True if the path is a launch file.""" + return os.path.basename(path).endswith(is_launch_file.extensions) and os.path.isfile(path) + + +is_launch_file.extensions = [ 'launch.' + extension for extension in Parser.get_available_extensions() ] -get_launch_file_paths.extensions.append('launch.py') -get_launch_file_paths.extensions = tuple(get_launch_file_paths.extensions) +is_launch_file.extensions.append('launch.py') +is_launch_file.extensions = tuple(is_launch_file.extensions) def print_a_launch_file(*, launch_file_path): From 71ae581d2988aaa2d2d7bd5c3737ed917f8eca59 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Tue, 25 Feb 2020 08:42:32 -0800 Subject: [PATCH 09/13] Reuse argcomplete machinery for paths Signed-off-by: Shane Loretz --- ros2launch/ros2launch/command/launch.py | 47 ++++++++----------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/ros2launch/ros2launch/command/launch.py b/ros2launch/ros2launch/command/launch.py index c8273964..beaaa7e3 100644 --- a/ros2launch/ros2launch/command/launch.py +++ b/ros2launch/ros2launch/command/launch.py @@ -16,10 +16,11 @@ from ament_index_python.packages import get_package_prefix from ament_index_python.packages import PackageNotFoundError +from argcomplete.completers import FilesCompleter from argcomplete.completers import SuppressCompleter from ros2cli.command import CommandExtension -from ros2launch.api import get_launch_file_paths from ros2launch.api import get_share_file_path_from_package +from ros2launch.api import is_launch_file from ros2launch.api import launch_a_launch_file from ros2launch.api import LaunchFileNameCompleter from ros2launch.api import MultipleLaunchFilesError @@ -37,39 +38,19 @@ def __call__(self, *args, **kwargs): def package_name_or_launch_file_completer(prefix, parsed_args, **kwargs): + """Complete package names or paths to launch files.""" + pass_through_kwargs = {k: v for k, v in kwargs.items()} + pass_through_kwargs['prefix'] = prefix + pass_through_kwargs['parsed_args'] = parsed_args + # Complete package names - completions = list(package_name_completer(prefix=prefix, **kwargs)) - - # list of 2-tuples: (directory, part of prefix to prepend) - dirs_to_check = [] - - if os.path.isdir(prefix) and not prefix.endswith(os.sep): - # if prefix is directory 'foo' then suggest 'foo/' - completions.append(prefix + os.sep) - if os.path.isdir(prefix) and prefix.endswith(os.sep): - # if prefix is 'foo/' then check 'foo/' for launch files - dirs_to_check.append((prefix, prefix)) - if not prefix.endswith(os.sep) and os.path.isdir(os.path.dirname(prefix)): - # if prefix is 'foo/bar' and then check 'foo' for launch files - dirname = os.path.dirname(prefix) - basename = os.path.basename(prefix) - prepend = prefix[:-len(basename)] - dirs_to_check.append((dirname, prepend)) - if os.sep not in prefix: - # if prefix is 'foo' then check current directory for files starting with 'foo' - dirs_to_check.append((os.curdir, '')) - - for dirname, prepend in dirs_to_check: - # complete launch files in a directory - for launch_file in get_launch_file_paths(path=dirname): - if os.path.normpath(os.path.dirname(launch_file)) == os.path.normpath(dirname): - # get_launch_file_paths() is recursive; complete only launch files in first level - completions.append(prepend + os.path.basename(launch_file)) - - # complete directories since they may contain launch files - for path in os.listdir(path=dirname): - if os.path.isdir(os.path.join(dirname, path)): - completions.append(prepend + path + os.sep) + completions = list(package_name_completer(**pass_through_kwargs)) + + def is_launch_file_or_dir(path): + return is_launch_file(path=path) or os.path.isdir(path) + + # Complete paths to launch files + completions.extend(filter(is_launch_file_or_dir, FilesCompleter()(**pass_through_kwargs))) return completions From 31df98fc6242b0ae9dc5506d8a71d1149abb8881 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Tue, 25 Feb 2020 09:17:12 -0800 Subject: [PATCH 10/13] dict() instead of dictionary comprehension Signed-off-by: Shane Loretz --- ros2launch/ros2launch/command/launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ros2launch/ros2launch/command/launch.py b/ros2launch/ros2launch/command/launch.py index beaaa7e3..34290a17 100644 --- a/ros2launch/ros2launch/command/launch.py +++ b/ros2launch/ros2launch/command/launch.py @@ -39,7 +39,7 @@ def __call__(self, *args, **kwargs): def package_name_or_launch_file_completer(prefix, parsed_args, **kwargs): """Complete package names or paths to launch files.""" - pass_through_kwargs = {k: v for k, v in kwargs.items()} + pass_through_kwargs = dict(kwargs) pass_through_kwargs['prefix'] = prefix pass_through_kwargs['parsed_args'] = parsed_args From 96bd7f8287cef64c2ce36efc7c3279757a4402d9 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Tue, 25 Feb 2020 09:17:58 -0800 Subject: [PATCH 11/13] Remove unnecessary basename() call Signed-off-by: Shane Loretz --- ros2launch/ros2launch/api/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ros2launch/ros2launch/api/api.py b/ros2launch/ros2launch/api/api.py index d911d7d0..5efbf4b3 100644 --- a/ros2launch/ros2launch/api/api.py +++ b/ros2launch/ros2launch/api/api.py @@ -81,7 +81,7 @@ def get_launch_file_paths(*, path): def is_launch_file(*, path): """Return True if the path is a launch file.""" - return os.path.basename(path).endswith(is_launch_file.extensions) and os.path.isfile(path) + return path.endswith(is_launch_file.extensions) and os.path.isfile(path) is_launch_file.extensions = [ From 84b564e73c6ef48292830bd140b8c3b8d3834586 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Tue, 25 Feb 2020 11:03:00 -0800 Subject: [PATCH 12/13] Compatibility with argcomplete==1.8.1 Signed-off-by: Shane Loretz --- ros2launch/ros2launch/command/launch.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ros2launch/ros2launch/command/launch.py b/ros2launch/ros2launch/command/launch.py index 34290a17..28bc2d28 100644 --- a/ros2launch/ros2launch/command/launch.py +++ b/ros2launch/ros2launch/command/launch.py @@ -17,7 +17,11 @@ from ament_index_python.packages import get_package_prefix from ament_index_python.packages import PackageNotFoundError from argcomplete.completers import FilesCompleter -from argcomplete.completers import SuppressCompleter +try: + from argcomplete.completers import SuppressCompleter +except ImportError: + # argcomplete < 1.9.0 + SuppressCompleter = object from ros2cli.command import CommandExtension from ros2launch.api import get_share_file_path_from_package from ros2launch.api import is_launch_file From 7b64ba9a7a8db5b43279eb7d4e2f87eb99019513 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Tue, 25 Feb 2020 15:45:42 -0800 Subject: [PATCH 13/13] is_launch_file takes normal arg Signed-off-by: Shane Loretz --- ros2launch/ros2launch/api/api.py | 2 +- ros2launch/ros2launch/command/launch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ros2launch/ros2launch/api/api.py b/ros2launch/ros2launch/api/api.py index 5efbf4b3..72b491c2 100644 --- a/ros2launch/ros2launch/api/api.py +++ b/ros2launch/ros2launch/api/api.py @@ -79,7 +79,7 @@ def get_launch_file_paths(*, path): return launch_file_paths -def is_launch_file(*, path): +def is_launch_file(path): """Return True if the path is a launch file.""" return path.endswith(is_launch_file.extensions) and os.path.isfile(path) diff --git a/ros2launch/ros2launch/command/launch.py b/ros2launch/ros2launch/command/launch.py index 28bc2d28..0e39dc9e 100644 --- a/ros2launch/ros2launch/command/launch.py +++ b/ros2launch/ros2launch/command/launch.py @@ -51,7 +51,7 @@ def package_name_or_launch_file_completer(prefix, parsed_args, **kwargs): completions = list(package_name_completer(**pass_through_kwargs)) def is_launch_file_or_dir(path): - return is_launch_file(path=path) or os.path.isdir(path) + return is_launch_file(path) or os.path.isdir(path) # Complete paths to launch files completions.extend(filter(is_launch_file_or_dir, FilesCompleter()(**pass_through_kwargs)))