diff --git a/.github/workflows/tests-robocode-python-ls-core.yml b/.github/workflows/tests-robocode-python-ls-core.yml index efc2756dfe..4999d811f3 100644 --- a/.github/workflows/tests-robocode-python-ls-core.yml +++ b/.github/workflows/tests-robocode-python-ls-core.yml @@ -21,19 +21,19 @@ jobs: matrix: name: [ "windows-py37", - "ubuntu-py38", - "macos-py37" + "ubuntu-py39", + "macos-py38" ] include: - name: "windows-py37" python: "3.7" os: windows-latest - - name: "ubuntu-py38" - python: "3.8" + - name: "ubuntu-py39" + python: "3.9" os: ubuntu-latest - - name: "macos-py37" - python: "3.7" + - name: "macos-py38" + python: "3.8" os: macos-latest steps: diff --git a/robocorp-python-ls-core/.settings/org.eclipse.core.resources.prefs b/robocorp-python-ls-core/.settings/org.eclipse.core.resources.prefs index 63da72ffc7..15a00199a5 100644 --- a/robocorp-python-ls-core/.settings/org.eclipse.core.resources.prefs +++ b/robocorp-python-ls-core/.settings/org.eclipse.core.resources.prefs @@ -2,5 +2,3 @@ eclipse.preferences.version=1 encoding//.settings/org.python.pydev.analysis.yaml=UTF-8 encoding//.settings/org.python.pydev.yaml=UTF-8 encoding//src/robocorp_ls_core/debug_adapter_core/dap/dap_schema.py=utf-8 -encoding//src/robocorp_ls_core/libs/watchdog_lib/watchdog/events.py=utf-8 -encoding//src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/api.py=utf-8 diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/fsnotify_lib/fsnotify/__init__.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/fsnotify_lib/fsnotify/__init__.py index 7c7fe7597c..031b1ef1fe 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/fsnotify_lib/fsnotify/__init__.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/fsnotify_lib/fsnotify/__init__.py @@ -1,4 +1,4 @@ -''' +""" Sample usage to track changes in a thread. import threading @@ -36,10 +36,10 @@ def start_watching(): # Called from thread Note: changes are only reported for files (added/modified/deleted), not directories. -''' +""" import threading import sys -from typing import Tuple +from typing import Tuple, List try: from os import scandir @@ -58,13 +58,14 @@ def start_watching(): # Called from thread class IntEnum(object): pass + from collections import deque import time -__author__ = 'Fabio Zadrozny' -__email__ = 'fabiofz@gmail.com' -__version__ = '0.2.1' # Version here and in setup.py +__author__ = "Fabio Zadrozny" +__email__ = "fabiofz@gmail.com" +__version__ = "0.2.1" # Version here and in setup.py PRINT_SINGLE_POLL_TIME = False @@ -76,7 +77,6 @@ class Change(IntEnum): class _SingleVisitInfo(object): - def __init__(self): self.count = 0 self.visited_dirs = set() @@ -86,7 +86,7 @@ def __init__(self): class TrackedPath(object): - __slots__ = ['path', 'recursive'] + __slots__ = ["path", "recursive"] def __init__(self, path, recursive): self.path = path @@ -94,18 +94,27 @@ def __init__(self, path, recursive): class _PathWatcher(object): - ''' + """ Helper to watch a single path. - ''' - - def __init__(self, root_path, accept_directory, accept_file, single_visit_info, max_recursion_level, sleep_time=.0, recursive=True): - ''' + """ + + def __init__( + self, + root_path, + accept_directory, + accept_file, + single_visit_info, + max_recursion_level, + sleep_time=0.0, + recursive=True, + ): + """ :type root_path: str :type accept_directory: Callback[str, bool] :type accept_file: Callback[str, bool] :type max_recursion_level: int :type sleep_time: float - ''' + """ self.accept_directory = accept_directory self.accept_file = accept_file self._max_recursion_level = max_recursion_level @@ -117,7 +126,7 @@ def __init__(self, root_path, accept_directory, accept_file, single_visit_info, # Watcher.target_time_for_single_scan. self.sleep_time = sleep_time - self.sleep_at_elapsed = 1. / 30. + self.sleep_at_elapsed = 1.0 / 30.0 # When created, do the initial snapshot right away! old_file_to_mtime = {} @@ -135,9 +144,14 @@ def __ne__(self, o): def __hash__(self): return hash(self._root_path) - def _check_dir(self, dir_path, single_visit_info, append_change, old_file_to_mtime, level): + def _check_dir( + self, dir_path, single_visit_info, append_change, old_file_to_mtime, level + ): # This is the actual poll loop - if dir_path in single_visit_info.visited_dirs or level > self._max_recursion_level: + if ( + dir_path in single_visit_info.visited_dirs + or level > self._max_recursion_level + ): return single_visit_info.visited_dirs.add(dir_path) try: @@ -146,7 +160,7 @@ def _check_dir(self, dir_path, single_visit_info, append_change, old_file_to_mti dir_path = dir_path.decode(sys.getfilesystemencoding()) except UnicodeDecodeError: try: - dir_path = dir_path.decode('utf-8') + dir_path = dir_path.decode("utf-8") except UnicodeDecodeError: return # Ignore if we can't deal with the path. @@ -168,7 +182,13 @@ def _check_dir(self, dir_path, single_visit_info, append_change, old_file_to_mti if entry.is_dir(): if self.accept_directory(entry.path): if self._recursive: - self._check_dir(entry.path, single_visit_info, append_change, old_file_to_mtime, level + 1) + self._check_dir( + entry.path, + single_visit_info, + append_change, + old_file_to_mtime, + level + 1, + ) elif self.accept_file(entry.path): stat = entry.stat() @@ -186,14 +206,16 @@ def _check_dir(self, dir_path, single_visit_info, append_change, old_file_to_mti pass # Directory was removed in the meanwhile. def _check(self, single_visit_info, append_change, old_file_to_mtime): - self._check_dir(self._root_path, single_visit_info, append_change, old_file_to_mtime, 0) + self._check_dir( + self._root_path, single_visit_info, append_change, old_file_to_mtime, 0 + ) class Watcher(object): # By default (if accept_directory is not specified), these will be the # ignored directories. - ignored_dirs = {u'.git', u'__pycache__', u'.idea', u'node_modules', u'.metadata'} + ignored_dirs = {u".git", u"__pycache__", u".idea", u"node_modules", u".metadata"} # By default (if accept_file is not specified), these will be the # accepted files. @@ -218,7 +240,7 @@ class Watcher(object): max_recursion_level = 10 def __init__(self, accept_directory=None, accept_file=None): - ''' + """ :param Callable[str, bool] accept_directory: Callable that returns whether a directory should be watched. Note: if passed it'll override the `ignored_dirs` @@ -226,7 +248,7 @@ def __init__(self, accept_directory=None, accept_file=None): :param Callable[str, bool] accept_file: Callable that returns whether a file should be watched. Note: if passed it'll override the `accepted_file_extensions`. - ''' + """ self._lock = threading.Lock() self._path_watchers = set() @@ -234,10 +256,15 @@ def __init__(self, accept_directory=None, accept_file=None): if accept_directory is None: from os.path import basename - accept_directory = lambda dir_path: basename(dir_path) not in self.ignored_dirs + + accept_directory = ( + lambda dir_path: basename(dir_path) not in self.ignored_dirs + ) if accept_file is None: - accept_file = lambda path_name: \ - not self.accepted_file_extensions or path_name.endswith(self.accepted_file_extensions) + accept_file = ( + lambda path_name: not self.accepted_file_extensions + or path_name.endswith(self.accepted_file_extensions) + ) self.accept_file = accept_file self.accept_directory = accept_directory self._single_visit_info = _SingleVisitInfo() @@ -277,29 +304,48 @@ def set_tracked_paths(self, paths): if not isinstance(paths, (list, tuple, set)): paths = (paths,) - def key(path_or_str): - if isinstance(path_or_str, TrackedPath): - return -len(path_or_str.path) - return -len(path_or_str) + new_paths = [] + path_to_recursive = {} + for p in paths: + if isinstance(p, str): + p = TrackedPath(p, True) + new_paths.append(p) + curr = path_to_recursive.get(p.path) + + if curr is None: + path_to_recursive[p.path] = p.recursive + else: + if p.recursive: + path_to_recursive[p.path] = True + else: + if curr: + pass # Already recursive, don't override with non-recursive. + else: + # Set to not recursive. + path_to_recursive[p.path] = False + + def key(path): + return -len(path) # Sort by the path len so that the bigger paths come first (so, # if there's any nesting we want the nested paths to be visited # before the parent paths so that the max_recursion_level is correct). - paths = sorted(set(paths), key=key) + paths: List[str] = sorted(set(p.path for p in paths), key=key) path_watchers = set() single_visit_info = _SingleVisitInfo() + path: str for path in paths: - sleep_time = 0. # When collecting the first time, sleep_time should be 0! + sleep_time = 0.0 # When collecting the first time, sleep_time should be 0! path_watcher = _PathWatcher( - path.path if isinstance(path, TrackedPath) else path, + path, self.accept_directory, self.accept_file, single_visit_info, max_recursion_level=self.max_recursion_level, sleep_time=sleep_time, - recursive=path.recursive if isinstance(path, TrackedPath) else path + recursive=path_to_recursive.get(path, True), ) path_watchers.add(path_watcher) @@ -309,13 +355,13 @@ def key(path_or_str): self._path_watchers = path_watchers def iter_changes(self): - ''' + """ Continuously provides changes (until dispose() is called). Changes provided are tuples with the Change enum and filesystem path. :rtype: Iterable[Tuple[Change, str]] - ''' + """ while not self._disposed.is_set(): with self._lock: @@ -338,9 +384,9 @@ def iter_changes(self): for change in changes: yield change - actual_time = (time.time() - initial_time) + actual_time = time.time() - initial_time if self.print_poll_time: - print('--- Total poll time: %.3fs' % actual_time) + print("--- Total poll time: %.3fs" % actual_time) if actual_time > 0: if self.target_time_for_single_scan <= 0.0: @@ -353,8 +399,8 @@ def iter_changes(self): # direction). # (to prevent from cases where the user puts the machine on sleep and # values become too skewed). - if perc > 2.: - perc = 2. + if perc > 2.0: + perc = 2.0 elif perc < 0.5: perc = 0.5 @@ -368,7 +414,13 @@ def iter_changes(self): # (to prevent from cases where the user puts the machine on sleep and # values become too skewed). diff_sleep_time = new_sleep_time - path_watcher.sleep_time - path_watcher.sleep_time += (diff_sleep_time / (3.0 * len(self._path_watchers))) + path_watchers_len = len(self._path_watchers) + if not path_watchers_len: + continue + + path_watcher.sleep_time += diff_sleep_time / ( + 3.0 * path_watchers_len + ) if actual_time > 0: self._disposed.wait(actual_time) @@ -379,6 +431,5 @@ def iter_changes(self): # print('new sleep time: %s' % path_watcher.sleep_time) diff = self.target_time_for_notification - actual_time - if diff > 0.: + if diff > 0.0: self._disposed.wait(diff) - diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/README.md b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/README.md index 4a0875505b..0c3b7d8085 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/README.md +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/README.md @@ -1,13 +1,14 @@ The files in this directory contain watchdog and the needed deps. -To update, erase it and run (note: using 0.10.3 due to https://github.com/gorakhargosh/watchdog/issues/706 in 0.10.4): +To update, erase it and run: -pip install watchdog==0.10.3 --target . +pip install watchdog --target . Then: - Remove bin - commit the code - Verify if the license brought in from the libraries downloaded are OK +- Make sure that there's a `watchdog_lib/__init__.py` (so that it's added during setup). Then, go on to: @@ -16,8 +17,8 @@ the `_watchdog_fsevents.xxx.so` to the watchdog_lib. Then update the permissions of the file: -git update-index --add --chmod=+x _watchdog_fsevents.cpython-37m-darwin.so git update-index --add --chmod=+x _watchdog_fsevents.cpython-38-darwin.so git update-index --add --chmod=+x _watchdog_fsevents.cpython-39-darwin.so +git update-index --add --chmod=+x _watchdog_fsevents.cpython-310-darwin.so This folder should be automatically added to the PYTHONPATH when needed. \ No newline at end of file diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-310-darwin.so b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-310-darwin.so new file mode 100755 index 0000000000..bc7e04b9c4 Binary files /dev/null and b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-310-darwin.so differ diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-37m-darwin.so b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-37m-darwin.so deleted file mode 100755 index 5c793467ef..0000000000 Binary files a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-37m-darwin.so and /dev/null differ diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-38-darwin.so b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-38-darwin.so index e0fe99d9f7..9feb5dda67 100755 Binary files a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-38-darwin.so and b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-38-darwin.so differ diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-39-darwin.so b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-39-darwin.so index f74bb127f6..708ab127de 100755 Binary files a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-39-darwin.so and b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/_watchdog_fsevents.cpython-39-darwin.so differ diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/AUTHORS b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/AUTHORS deleted file mode 100644 index e14de1185f..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/AUTHORS +++ /dev/null @@ -1,2 +0,0 @@ -Yesudeep Mangalapilly -Martin Kreichgauer diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/LICENSE b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/LICENSE deleted file mode 100644 index 5e6adc9dad..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (C) 2010 by Yesudeep Mangalapilly - -MIT License ------------ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/METADATA b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/METADATA deleted file mode 100644 index 4b8892914a..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/METADATA +++ /dev/null @@ -1,27 +0,0 @@ -Metadata-Version: 2.1 -Name: pathtools -Version: 0.1.2 -Summary: File system general utilities -Home-page: http://github.com/gorakhargosh/pathtools -Author: Yesudeep Mangalapilly -Author-email: yesudeep@gmail.com -License: MIT License -Platform: UNKNOWN -Classifier: Development Status :: 3 - Alpha -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Natural Language :: English -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: System :: Filesystems -Classifier: Topic :: Utilities - -pathtools -========= - -Pattern matching and various utilities for file systems paths. - - - - diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/RECORD b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/RECORD deleted file mode 100644 index c924960fc4..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/RECORD +++ /dev/null @@ -1,15 +0,0 @@ -pathtools-0.1.2.dist-info/AUTHORS,sha256=mFRQoJyYaNMKGmBPpYM-0Xjs1-00TVZyYebS_gwmejc,109 -pathtools-0.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -pathtools-0.1.2.dist-info/LICENSE,sha256=tS2D-6CPYb0eYD_uG0TLx-rZ_LUSpc64xT_skLpBWsQ,1113 -pathtools-0.1.2.dist-info/METADATA,sha256=kfRLQmjTpm7dY4mOnJ7CexgkFRrVfVYF1sJifm5D_Bc,733 -pathtools-0.1.2.dist-info/RECORD,, -pathtools-0.1.2.dist-info/WHEEL,sha256=wukoMpUSXSjEgkuGJ4rBwSQD_WUudXigt4qI353o1f8,98 -pathtools-0.1.2.dist-info/top_level.txt,sha256=aGOU9N02R1oQrWs2b6kfI2RRAoilwseXzgWQoWpBdl8,10 -pathtools/__init__.py,sha256=WzraoQXBsXz4HiSy2PnQSMBI-0d4LkIKWeAXd0k24Ik,1182 -pathtools/__pycache__/__init__.cpython-36.pyc,, -pathtools/__pycache__/path.cpython-36.pyc,, -pathtools/__pycache__/patterns.cpython-36.pyc,, -pathtools/__pycache__/version.cpython-36.pyc,, -pathtools/path.py,sha256=wnqgP_y9q4kiQFsZhsnlm8eUddwsmQHvu71Wwp8R2mA,6614 -pathtools/patterns.py,sha256=xWatHsYukVTIwVOw8O4BB0-_-ajx2z2vgGCA_NUfyss,10698 -pathtools/version.py,sha256=U-qWHg5rAkhn3-8ER9GYAMMgqLj6wU9Qa95S8jwcHyU,1469 diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/WHEEL b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/WHEEL deleted file mode 100644 index a6dd4f1c9b..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.33.4) -Root-Is-Purelib: true -Tag: cp36-none-any - diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/top_level.txt b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/top_level.txt deleted file mode 100644 index fd4be62a24..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -pathtools diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/__init__.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/__init__.py deleted file mode 100644 index c9c373fc07..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# pathtools: File system path tools. -# Copyright (C) 2010 Yesudeep Mangalapilly -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/path.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/path.py deleted file mode 100644 index 20013599aa..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/path.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# path.py: Path functions. -# -# Copyright (C) 2010 Yesudeep Mangalapilly -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -""" -:module: pathtools.path -:synopsis: Directory walking, listing, and path sanitizing functions. -:author: Yesudeep Mangalapilly - -Functions ---------- -.. autofunction:: get_dir_walker -.. autofunction:: walk -.. autofunction:: listdir -.. autofunction:: list_directories -.. autofunction:: list_files -.. autofunction:: absolute_path -.. autofunction:: real_absolute_path -.. autofunction:: parent_dir_path -""" - -import os.path -import os.path -from functools import partial - - -__all__ = [ - 'get_dir_walker', - 'walk', - 'listdir', - 'list_directories', - 'list_files', - 'absolute_path', - 'real_absolute_path', - 'parent_dir_path', -] - - -def get_dir_walker(recursive, topdown=True, followlinks=False): - """ - Returns a recursive or a non-recursive directory walker. - - :param recursive: - ``True`` produces a recursive walker; ``False`` produces a non-recursive - walker. - :returns: - A walker function. - """ - if recursive: - walk = partial(os.walk, topdown=topdown, followlinks=followlinks) - else: - def walk(path, topdown=topdown, followlinks=followlinks): - try: - yield next(os.walk(path, topdown=topdown, followlinks=followlinks)) - except NameError: - yield os.walk(path, topdown=topdown, followlinks=followlinks).next() #IGNORE:E1101 - return walk - - -def walk(dir_pathname, recursive=True, topdown=True, followlinks=False): - """ - Walks a directory tree optionally recursively. Works exactly like - :func:`os.walk` only adding the `recursive` argument. - - :param dir_pathname: - The directory to traverse. - :param recursive: - ``True`` for walking recursively through the directory tree; - ``False`` otherwise. - :param topdown: - Please see the documentation for :func:`os.walk` - :param followlinks: - Please see the documentation for :func:`os.walk` - """ - walk_func = get_dir_walker(recursive, topdown, followlinks) - for root, dirnames, filenames in walk_func(dir_pathname): - yield (root, dirnames, filenames) - - -def listdir(dir_pathname, - recursive=True, - topdown=True, - followlinks=False): - """ - Enlists all items using their absolute paths in a directory, optionally - recursively. - - :param dir_pathname: - The directory to traverse. - :param recursive: - ``True`` for walking recursively through the directory tree; - ``False`` otherwise. - :param topdown: - Please see the documentation for :func:`os.walk` - :param followlinks: - Please see the documentation for :func:`os.walk` - """ - for root, dirnames, filenames\ - in walk(dir_pathname, recursive, topdown, followlinks): - for dirname in dirnames: - yield absolute_path(os.path.join(root, dirname)) - for filename in filenames: - yield absolute_path(os.path.join(root, filename)) - - -def list_directories(dir_pathname, - recursive=True, - topdown=True, - followlinks=False): - """ - Enlists all the directories using their absolute paths within the specified - directory, optionally recursively. - - :param dir_pathname: - The directory to traverse. - :param recursive: - ``True`` for walking recursively through the directory tree; - ``False`` otherwise. - :param topdown: - Please see the documentation for :func:`os.walk` - :param followlinks: - Please see the documentation for :func:`os.walk` - """ - for root, dirnames, filenames\ - in walk(dir_pathname, recursive, topdown, followlinks): - for dirname in dirnames: - yield absolute_path(os.path.join(root, dirname)) - - -def list_files(dir_pathname, - recursive=True, - topdown=True, - followlinks=False): - """ - Enlists all the files using their absolute paths within the specified - directory, optionally recursively. - - :param dir_pathname: - The directory to traverse. - :param recursive: - ``True`` for walking recursively through the directory tree; - ``False`` otherwise. - :param topdown: - Please see the documentation for :func:`os.walk` - :param followlinks: - Please see the documentation for :func:`os.walk` - """ - for root, dirnames, filenames\ - in walk(dir_pathname, recursive, topdown, followlinks): - for filename in filenames: - yield absolute_path(os.path.join(root, filename)) - - -def absolute_path(path): - """ - Returns the absolute path for the given path and normalizes the path. - - :param path: - Path for which the absolute normalized path will be found. - :returns: - Absolute normalized path. - """ - return os.path.abspath(os.path.normpath(path)) - - -def real_absolute_path(path): - """ - Returns the real absolute normalized path for the given path. - - :param path: - Path for which the real absolute normalized path will be found. - :returns: - Real absolute normalized path. - """ - return os.path.realpath(absolute_path(path)) - - -def parent_dir_path(path): - """ - Returns the parent directory path. - - :param path: - Path for which the parent directory will be obtained. - :returns: - Parent directory path. - """ - return absolute_path(os.path.dirname(path)) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/patterns.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/patterns.py deleted file mode 100644 index 4ecd853745..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/patterns.py +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# patterns.py: Common wildcard searching/filtering functionality for files. -# -# Copyright (C) 2010 Yesudeep Mangalapilly -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -""" -:module: pathtools.patterns -:synopsis: Wildcard pattern matching and filtering functions for paths. -:author: Yesudeep Mangalapilly - -Functions ---------- -.. autofunction:: match_path -.. autofunction:: match_path_against -.. autofunction:: filter_paths -""" - -from fnmatch import fnmatch, fnmatchcase - -__all__ = ['match_path', - 'match_path_against', - 'match_any_paths', - 'filter_paths'] - - -def _string_lower(s): - """ - Convenience function to lowercase a string (the :mod:`string` module is - deprecated/removed in Python 3.0). - - :param s: - The string which will be lowercased. - :returns: - Lowercased copy of string s. - """ - return s.lower() - - -def match_path_against(pathname, patterns, case_sensitive=True): - """ - Determines whether the pathname matches any of the given wildcard patterns, - optionally ignoring the case of the pathname and patterns. - - :param pathname: - A path name that will be matched against a wildcard pattern. - :param patterns: - A list of wildcard patterns to match_path the filename against. - :param case_sensitive: - ``True`` if the matching should be case-sensitive; ``False`` otherwise. - :returns: - ``True`` if the pattern matches; ``False`` otherwise. - - Doctests:: - >>> match_path_against("/home/username/foobar/blah.py", ["*.py", "*.txt"], False) - True - >>> match_path_against("/home/username/foobar/blah.py", ["*.PY", "*.txt"], True) - False - >>> match_path_against("/home/username/foobar/blah.py", ["*.PY", "*.txt"], False) - True - >>> match_path_against("C:\\windows\\blah\\BLAH.PY", ["*.py", "*.txt"], True) - False - >>> match_path_against("C:\\windows\\blah\\BLAH.PY", ["*.py", "*.txt"], False) - True - """ - if case_sensitive: - match_func = fnmatchcase - pattern_transform_func = (lambda w: w) - else: - match_func = fnmatch - pathname = pathname.lower() - pattern_transform_func = _string_lower - for pattern in set(patterns): - pattern = pattern_transform_func(pattern) - if match_func(pathname, pattern): - return True - return False - - -def _match_path(pathname, - included_patterns, - excluded_patterns, - case_sensitive=True): - """Internal function same as :func:`match_path` but does not check arguments. - - Doctests:: - >>> _match_path("/users/gorakhargosh/foobar.py", ["*.py"], ["*.PY"], True) - True - >>> _match_path("/users/gorakhargosh/FOOBAR.PY", ["*.py"], ["*.PY"], True) - False - >>> _match_path("/users/gorakhargosh/foobar/", ["*.py"], ["*.txt"], False) - False - >>> _match_path("/users/gorakhargosh/FOOBAR.PY", ["*.py"], ["*.PY"], False) - Traceback (most recent call last): - ... - ValueError: conflicting patterns `set(['*.py'])` included and excluded - """ - if not case_sensitive: - included_patterns = set(map(_string_lower, included_patterns)) - excluded_patterns = set(map(_string_lower, excluded_patterns)) - else: - included_patterns = set(included_patterns) - excluded_patterns = set(excluded_patterns) - common_patterns = included_patterns & excluded_patterns - if common_patterns: - raise ValueError('conflicting patterns `%s` included and excluded'\ - % common_patterns) - return (match_path_against(pathname, included_patterns, case_sensitive)\ - and not match_path_against(pathname, excluded_patterns, - case_sensitive)) - - -def match_path(pathname, - included_patterns=None, - excluded_patterns=None, - case_sensitive=True): - """ - Matches a pathname against a set of acceptable and ignored patterns. - - :param pathname: - A pathname which will be matched against a pattern. - :param included_patterns: - Allow filenames matching wildcard patterns specified in this list. - If no pattern is specified, the function treats the pathname as - a match_path. - :param excluded_patterns: - Ignores filenames matching wildcard patterns specified in this list. - If no pattern is specified, the function treats the pathname as - a match_path. - :param case_sensitive: - ``True`` if matching should be case-sensitive; ``False`` otherwise. - :returns: - ``True`` if the pathname matches; ``False`` otherwise. - :raises: - ValueError if included patterns and excluded patterns contain the - same pattern. - - Doctests:: - >>> match_path("/Users/gorakhargosh/foobar.py") - True - >>> match_path("/Users/gorakhargosh/foobar.py", case_sensitive=False) - True - >>> match_path("/users/gorakhargosh/foobar.py", ["*.py"], ["*.PY"], True) - True - >>> match_path("/users/gorakhargosh/FOOBAR.PY", ["*.py"], ["*.PY"], True) - False - >>> match_path("/users/gorakhargosh/foobar/", ["*.py"], ["*.txt"], False) - False - >>> match_path("/users/gorakhargosh/FOOBAR.PY", ["*.py"], ["*.PY"], False) - Traceback (most recent call last): - ... - ValueError: conflicting patterns `set(['*.py'])` included and excluded - """ - included = ["*"] if included_patterns is None else included_patterns - excluded = [] if excluded_patterns is None else excluded_patterns - return _match_path(pathname, included, excluded, case_sensitive) - - -def filter_paths(pathnames, - included_patterns=None, - excluded_patterns=None, - case_sensitive=True): - """ - Filters from a set of paths based on acceptable patterns and - ignorable patterns. - - :param pathnames: - A list of path names that will be filtered based on matching and - ignored patterns. - :param included_patterns: - Allow filenames matching wildcard patterns specified in this list. - If no pattern list is specified, ["*"] is used as the default pattern, - which matches all files. - :param excluded_patterns: - Ignores filenames matching wildcard patterns specified in this list. - If no pattern list is specified, no files are ignored. - :param case_sensitive: - ``True`` if matching should be case-sensitive; ``False`` otherwise. - :returns: - A list of pathnames that matched the allowable patterns and passed - through the ignored patterns. - - Doctests:: - >>> pathnames = set(["/users/gorakhargosh/foobar.py", "/var/cache/pdnsd.status", "/etc/pdnsd.conf", "/usr/local/bin/python"]) - >>> set(filter_paths(pathnames)) == pathnames - True - >>> set(filter_paths(pathnames, case_sensitive=False)) == pathnames - True - >>> set(filter_paths(pathnames, ["*.py", "*.conf"], ["*.status"], case_sensitive=True)) == set(["/users/gorakhargosh/foobar.py", "/etc/pdnsd.conf"]) - True - """ - included = ["*"] if included_patterns is None else included_patterns - excluded = [] if excluded_patterns is None else excluded_patterns - - for pathname in pathnames: - # We don't call the public match_path because it checks arguments - # and sets default values if none are found. We're already doing that - # above. - if _match_path(pathname, included, excluded, case_sensitive): - yield pathname - -def match_any_paths(pathnames, - included_patterns=None, - excluded_patterns=None, - case_sensitive=True): - """ - Matches from a set of paths based on acceptable patterns and - ignorable patterns. - - :param pathnames: - A list of path names that will be filtered based on matching and - ignored patterns. - :param included_patterns: - Allow filenames matching wildcard patterns specified in this list. - If no pattern list is specified, ["*"] is used as the default pattern, - which matches all files. - :param excluded_patterns: - Ignores filenames matching wildcard patterns specified in this list. - If no pattern list is specified, no files are ignored. - :param case_sensitive: - ``True`` if matching should be case-sensitive; ``False`` otherwise. - :returns: - ``True`` if any of the paths matches; ``False`` otherwise. - - Doctests:: - >>> pathnames = set(["/users/gorakhargosh/foobar.py", "/var/cache/pdnsd.status", "/etc/pdnsd.conf", "/usr/local/bin/python"]) - >>> match_any_paths(pathnames) - True - >>> match_any_paths(pathnames, case_sensitive=False) - True - >>> match_any_paths(pathnames, ["*.py", "*.conf"], ["*.status"], case_sensitive=True) - True - >>> match_any_paths(pathnames, ["*.txt"], case_sensitive=False) - False - >>> match_any_paths(pathnames, ["*.txt"], case_sensitive=True) - False - """ - included = ["*"] if included_patterns is None else included_patterns - excluded = [] if excluded_patterns is None else excluded_patterns - - for pathname in pathnames: - # We don't call the public match_path because it checks arguments - # and sets default values if none are found. We're already doing that - # above. - if _match_path(pathname, included, excluded, case_sensitive): - return True - return False diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/version.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/version.py deleted file mode 100644 index e277237035..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools/version.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# version.py: Version information. -# Copyright (C) 2010 Yesudeep Mangalapilly -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -# When updating this version number, please update the -# ``docs/source/global.rst.inc`` file as well. -VERSION_MAJOR = 0 -VERSION_MINOR = 1 -VERSION_BUILD = 2 -VERSION_INFO = (VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD) -VERSION_STRING = "%d.%d.%d" % VERSION_INFO - -__version__ = VERSION_INFO diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/INSTALLER b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/INSTALLER deleted file mode 100644 index a1b589e38a..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/RECORD b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/RECORD deleted file mode 100644 index 5c53db7e15..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/RECORD +++ /dev/null @@ -1,60 +0,0 @@ -../../bin/watchmedo.exe,sha256=2Dnlx7Fr5wO_TaBeKixUzjzuFId4aQqN1E_qYhV2Tec,102753 -watchdog-0.10.3.dist-info/AUTHORS,sha256=43ua7PwDlQuuNk3wzZjB8mJObkrCOoTgcajQ9y4FE4s,2770 -watchdog-0.10.3.dist-info/COPYING,sha256=FOY2-vWIkTihu3qEmMy7Amau4x0txHUgGhBEjlbtPZY,610 -watchdog-0.10.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -watchdog-0.10.3.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 -watchdog-0.10.3.dist-info/METADATA,sha256=EUmLL26O4ErN80PF0Znt91XgIG1Sxh-eorzbOHMQeZk,20752 -watchdog-0.10.3.dist-info/RECORD,, -watchdog-0.10.3.dist-info/WHEEL,sha256=wukoMpUSXSjEgkuGJ4rBwSQD_WUudXigt4qI353o1f8,98 -watchdog-0.10.3.dist-info/entry_points.txt,sha256=buJ62u_LUWeXJA0P16fH90a7E2JSUWitCUyGuPIOmqs,67 -watchdog-0.10.3.dist-info/top_level.txt,sha256=OVdR7GkPGZako8sRtVuM0Nis-ZIElx3he3hKFPYnTGg,9 -watchdog/__init__.py,sha256=-eu2NcfsKwZHudYM1DwZob-gagx9DhVVvJOvmIGWo8o,684 -watchdog/__pycache__/__init__.cpython-36.pyc,, -watchdog/__pycache__/events.cpython-36.pyc,, -watchdog/__pycache__/version.cpython-36.pyc,, -watchdog/__pycache__/watchmedo.cpython-36.pyc,, -watchdog/events.py,sha256=RLBA544snEHSSGaEMhcBsgJTza0135M7NprLZ7vKwXw,18427 -watchdog/observers/__init__.py,sha256=Lve5pH69hINkXqEEkm-8iwguThmujIUk-GIYNkmcBm4,3528 -watchdog/observers/__pycache__/__init__.cpython-36.pyc,, -watchdog/observers/__pycache__/api.cpython-36.pyc,, -watchdog/observers/__pycache__/fsevents.cpython-36.pyc,, -watchdog/observers/__pycache__/fsevents2.cpython-36.pyc,, -watchdog/observers/__pycache__/inotify.cpython-36.pyc,, -watchdog/observers/__pycache__/inotify_buffer.cpython-36.pyc,, -watchdog/observers/__pycache__/inotify_c.cpython-36.pyc,, -watchdog/observers/__pycache__/kqueue.cpython-36.pyc,, -watchdog/observers/__pycache__/polling.cpython-36.pyc,, -watchdog/observers/__pycache__/read_directory_changes.cpython-36.pyc,, -watchdog/observers/__pycache__/winapi.cpython-36.pyc,, -watchdog/observers/api.py,sha256=kmnJVlgJQPe-aLJ98CFFtEFU_eVhLBjL--yboIGR4XE,11992 -watchdog/observers/fsevents.py,sha256=H6842R1bnX7BZTSiuE5cvVbU67Fd8CTk6lX_cNTl6CU,6607 -watchdog/observers/fsevents2.py,sha256=Ez-Ifjegou7I3Gc3767ubwtNo7kZsUkAdYv1ZptB50w,9142 -watchdog/observers/inotify.py,sha256=zUGQyZvu1_z3RdNyjm9pp6zKYjZ02dXm4wwCH6tFQsc,8525 -watchdog/observers/inotify_buffer.py,sha256=jwQEVMZ5LTbxolvo_VhukphiVIokBJwm6lnMJZBVHJI,3833 -watchdog/observers/inotify_c.py,sha256=lhqDrSTGN2nx2VdQCR4arHWeLiOXeDbVMfz_mrII5zs,19875 -watchdog/observers/kqueue.py,sha256=TaIeEOIJj9QeQEpKamH3S1fk6E9oRuxPgHnEhWN0rFk,24355 -watchdog/observers/polling.py,sha256=wIUiYqBwwVkPYZWqslPiEVqkpmj2XO66BXnJfBcYb7k,4929 -watchdog/observers/read_directory_changes.py,sha256=9cEG_y2_NvpaPVPRBUcnJs5MlMcYYjcBQ3E1BRy9bDo,5381 -watchdog/observers/winapi.py,sha256=oTBA2R9mDIoPbX9mi7Jv2FWtArIv9KujEqulr-Sb4W4,13063 -watchdog/tricks/__init__.py,sha256=m75MLiC4MgnSXlfk0vD91jr5U2bEj7eWo-0TMif1zps,5186 -watchdog/tricks/__pycache__/__init__.cpython-36.pyc,, -watchdog/utils/__init__.py,sha256=wth9qVlfmqnUNJRFfytBMug6pSWt_stJ4CnSHEcpaeg,4682 -watchdog/utils/__pycache__/__init__.cpython-36.pyc,, -watchdog/utils/__pycache__/bricks.cpython-36.pyc,, -watchdog/utils/__pycache__/compat.cpython-36.pyc,, -watchdog/utils/__pycache__/delayed_queue.cpython-36.pyc,, -watchdog/utils/__pycache__/dirsnapshot.cpython-36.pyc,, -watchdog/utils/__pycache__/echo.cpython-36.pyc,, -watchdog/utils/__pycache__/platform.cpython-36.pyc,, -watchdog/utils/__pycache__/unicode_paths.cpython-36.pyc,, -watchdog/utils/__pycache__/win32stat.cpython-36.pyc,, -watchdog/utils/bricks.py,sha256=t-1055KavkqF9i8D2y99FzPVX_GWNqn-JLVImKggJsc,2898 -watchdog/utils/compat.py,sha256=YF3VTm-Q8mv5Yv6-WyWSF3T3vNwULs-ZlGW7wcQC3kM,758 -watchdog/utils/delayed_queue.py,sha256=C1ab5pNPQscHkkhUH9OtAvU8VfXEHFXX4obYz6Rou7M,2872 -watchdog/utils/dirsnapshot.py,sha256=Hjs-nCfOOt0oVuyH2vT5chNJMtU-2Jfg0W9pHOi-Mq4,12229 -watchdog/utils/echo.py,sha256=qHCfS2kGwZhPDjN5b8jYAInk9acn4tBL9lWmoHGK6X0,5313 -watchdog/utils/platform.py,sha256=M84rrTGRQPK7t0jfjr-b5RQr4HgQJ_GP-BIAyhG4dG8,1512 -watchdog/utils/unicode_paths.py,sha256=COKxLPWugflfTYrBGP1UzKp5mmSARLQ0U-ruwwZVmt4,2184 -watchdog/utils/win32stat.py,sha256=HuEM3ZbG6HaWL-o2cEbMZJz9ErOUKngPcHsWpwQWilM,4029 -watchdog/version.py,sha256=AXZdKZzS5g4cVEs1fYnvmPLr9msnOw-NRsOfDwxg5WE,975 -watchdog/watchmedo.py,sha256=Fv9YnCV4YVzIrSqcZR7bSFgapHdD8yOLZYZdN-QC80c,17594 diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/WHEEL b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/WHEEL deleted file mode 100644 index a6dd4f1c9b..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.33.4) -Root-Is-Purelib: true -Tag: cp36-none-any - diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/AUTHORS b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/AUTHORS similarity index 84% rename from robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/AUTHORS rename to robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/AUTHORS index 7330412ae8..b0bb951ba9 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/AUTHORS +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/AUTHORS @@ -1,7 +1,11 @@ -Project Lead: -------------- +Original Project Lead: +---------------------- Yesudeep Mangalapilly +Current Project Lead: +--------------------- +Mickaël Schoentgen + Contributors in alphabetical order: ----------------------------------- Adrian Tejn Kern @@ -19,7 +23,6 @@ Lukáš Lalinský Malthe Borch Martin Kreichgauer Martin Kreichgauer -Mickaël Schoentgen Mike Lundy Raymond Hettinger Roman Ovchinnikov @@ -56,14 +59,14 @@ Armin Ronacher Watchdog also includes open source libraries or adapted code from the following projects: -- MacFSEvents - http://github.com/malthe/macfsevents +- MacFSEvents - https://github.com/malthe/macfsevents - watch_directory.py - http://timgolden.me.uk/python/downloads/watch_directory.py -- pyinotify - http://github.com/seb-m/pyinotify -- fsmonitor - http://github.com/shaurz/fsmonitor +- pyinotify - https://github.com/seb-m/pyinotify +- fsmonitor - https://github.com/shaurz/fsmonitor - echo - http://wordaligned.org/articles/echo - Lukáš Lalinský's ordered set queue implementation: - http://stackoverflow.com/questions/1581895/how-check-if-a-task-is-already-in-python-queue + https://stackoverflow.com/questions/1581895/how-check-if-a-task-is-already-in-python-queue - Armin Ronacher's flask-sphinx-themes for the documentation: https://github.com/mitsuhiko/flask-sphinx-themes -- pyfilesystem - http://code.google.com/p/pyfilesystem +- pyfilesystem - https://github.com/PyFilesystem/pyfilesystem - get_FILE_NOTIFY_INFORMATION - http://blog.gmane.org/gmane.comp.python.ctypes/month=20070901 diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/COPYING b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/COPYING similarity index 93% rename from robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/COPYING rename to robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/COPYING index e6f091e8c5..8eedbe9871 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/COPYING +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/COPYING @@ -1,5 +1,5 @@ Copyright 2011 Yesudeep Mangalapilly -Copyright 2012 Google, Inc. +Copyright 2012 Google, Inc & contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/INSTALLER b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/INSTALLER similarity index 100% rename from robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/pathtools-0.1.2.dist-info/INSTALLER rename to robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/INSTALLER diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/LICENSE b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/LICENSE similarity index 100% rename from robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/LICENSE rename to robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/LICENSE diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/METADATA b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/METADATA similarity index 54% rename from robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/METADATA rename to robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/METADATA index 1a73a44c48..96ef602775 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/METADATA +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/METADATA @@ -1,13 +1,16 @@ Metadata-Version: 2.1 Name: watchdog -Version: 0.10.3 +Version: 2.1.9 Summary: Filesystem events monitoring -Home-page: http://github.com/gorakhargosh/watchdog +Home-page: https://github.com/gorakhargosh/watchdog Author: Yesudeep Mangalapilly Author-email: yesudeep@gmail.com License: Apache License 2.0 +Project-URL: Documentation, https://python-watchdog.readthedocs.io/en/stable/ +Project-URL: Source, https://github.com/gorakhargosh/watchdog/ +Project-URL: Issues, https://github.com/gorakhargosh/watchdog/issues +Project-URL: Changelog, https://github.com/gorakhargosh/watchdog/blob/master/changelog.rst Keywords: python filesystem monitoring monitor FSEvents kqueue inotify ReadDirectoryChangesW polling DirectorySnapshot -Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers @@ -17,39 +20,51 @@ Classifier: Natural Language :: English Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX :: BSD -Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000 +Classifier: Operating System :: Microsoft :: Windows :: Windows Vista +Classifier: Operating System :: Microsoft :: Windows :: Windows 7 +Classifier: Operating System :: Microsoft :: Windows :: Windows 8 +Classifier: Operating System :: Microsoft :: Windows :: Windows 8.1 +Classifier: Operating System :: Microsoft :: Windows :: Windows 10 Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Programming Language :: C Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: System :: Monitoring Classifier: Topic :: System :: Filesystems Classifier: Topic :: Utilities -Requires-Dist: pathtools (>=0.1.1) +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: COPYING +License-File: AUTHORS Provides-Extra: watchmedo Requires-Dist: PyYAML (>=3.10) ; extra == 'watchmedo' -Requires-Dist: argh (>=0.24.1) ; extra == 'watchmedo' Watchdog ======== -.. image:: https://travis-ci.org/gorakhargosh/watchdog.svg?branch=master - :target: https://travis-ci.org/gorakhargosh/watchdog +|Build Status| +|CirrusCI Status| Python API and shell utilities to monitor file system events. -Works on Python 2.7 and 3.4+. If you want to use an old version of Python, you should stick with watchdog < 0.10.0. +Works on 3.6+. + +If you want to use Python 2.6, you should stick with watchdog < 0.10.0. + +If you want to use Python 2.7, 3.4 or 3.5, you should stick with watchdog < 1.0.0. Example API Usage ----------------- + A simple program that uses watchdog to monitor directories specified as command-line arguments and logs events generated: @@ -73,13 +88,14 @@ as command-line arguments and logs events generated: try: while True: time.sleep(1) - except KeyboardInterrupt: + finally: observer.stop() - observer.join() + observer.join() Shell Utilities --------------- + Watchdog comes with an *optional* utility script called ``watchmedo``. Please type ``watchmedo --help`` at the shell prompt to know more about this tool. @@ -116,6 +132,7 @@ Please see the help information for these commands by typing: About ``watchmedo`` Tricks ~~~~~~~~~~~~~~~~~~~~~~~~~~ + ``watchmedo`` can read ``tricks.yaml`` files and execute tricks within them in response to file system events. Tricks are actually event handlers that subclass ``watchdog.tricks.Trick`` and are written by plugin authors. Trick @@ -152,20 +169,16 @@ The directory containing the ``tricks.yaml`` file will be monitored. Each trick class is initialized with its corresponding keys in the ``tricks.yaml`` file as arguments and events are fed to an instance of this class as they arrive. -Tricks will be included in the 0.5.0 release. I need community input about them. -Please file enhancement requests at the `issue tracker`_. - - Installation ------------ Install from PyPI using ``pip``: .. code-block:: bash - $ python -m pip install watchdog + $ python -m pip install -U watchdog # or to install the watchmedo utility: - $ python -m pip install watchdog[watchmedo] + $ python -m pip install -U "watchdog[watchmedo]" Install from source: @@ -179,9 +192,10 @@ Install from source: Installation Caveats ~~~~~~~~~~~~~~~~~~~~ + The ``watchmedo`` script depends on PyYAML_ which links with LibYAML_, which brings a performance boost to the PyYAML parser. However, installing -LibYAML_ is optional but recommended. On Mac OS X, you can use homebrew_ +LibYAML_ is optional but recommended. On macOS, you can use homebrew_ to install LibYAML: .. code-block:: bash @@ -193,16 +207,18 @@ do it on Ubuntu: .. code-block:: bash - $ sudo aptitude install libyaml-dev + $ sudo apt install libyaml-dev On Windows, please install PyYAML_ using the binaries they provide. Documentation ------------- + You can browse the latest release documentation_ online. Contribute ---------- + Fork the `repository`_ on GitHub and send a pull request, or file an issue ticket at the `issue tracker`_. For general help and questions use the official `mailing list`_ or ask on `stackoverflow`_ with tag `python-watchdog`. @@ -210,7 +226,7 @@ ticket at the `issue tracker`_. For general help and questions use the official Create and activate your virtual environment, then:: python -m pip install pytest pytest-cov - python -m pip install -e .[watchmedo] + python -m pip install -e ".[watchmedo]" python -m pytest tests If you are making a substantial change, add an entry to the "Unreleased" section @@ -218,8 +234,9 @@ of the `changelog`_. Supported Platforms ------------------- + * Linux 2.6 (inotify) -* Mac OS X (FSEvents, kqueue) +* macOS (FSEvents, kqueue) * FreeBSD/BSD (kqueue) * Windows (ReadDirectoryChangesW with I/O completion ports; ReadDirectoryChangesW worker threads) @@ -246,6 +263,7 @@ files. About using watchdog with editors like Vim ------------------------------------------ + Vim does not modify files unless directed to do so. It creates backup files and then swaps them in to replace the files you are editing on the disk. This means that @@ -257,6 +275,7 @@ this feature. About using watchdog with CIFS ------------------------------ + When you want to watch changes in CIFS, you need to explicitly tell watchdog to use ``PollingObserver``, that is, instead of letting watchdog decide an appropriate observer like in the example above, do:: @@ -266,26 +285,26 @@ appropriate observer like in the example above, do:: Dependencies ------------ -1. Python 2.7, 3.4 or above. -2. pathtools_ -3. XCode_ (only on Mac OS X) -4. PyYAML_ (only for ``watchmedo`` script) -5. argh_ (only for ``watchmedo`` script) +1. Python 3.6 or above. +2. XCode_ (only on macOS when installing from sources) +3. PyYAML_ (only for ``watchmedo``) Licensing --------- + Watchdog is licensed under the terms of the `Apache License, version 2.0`_. Copyright 2011 `Yesudeep Mangalapilly`_. -Copyright 2012 Google, Inc. +Copyright 2012 Google, Inc & contributors. Project `source code`_ is available at Github. Please report bugs and file enhancement requests at the `issue tracker`_. Why Watchdog? ------------- + Too many people tried to do the same thing and none did what I needed Python to do: @@ -296,39 +315,42 @@ to do: * pyinotify_ * `inotify-tools`_ * jnotify_ -* treewalker_ +* treewatcher_ * `file.monitor`_ * pyfilesystem_ .. links: .. _Yesudeep Mangalapilly: yesudeep@gmail.com -.. _source code: http://github.com/gorakhargosh/watchdog -.. _issue tracker: http://github.com/gorakhargosh/watchdog/issues -.. _Apache License, version 2.0: http://www.apache.org/licenses/LICENSE-2.0 +.. _source code: https://github.com/gorakhargosh/watchdog +.. _issue tracker: https://github.com/gorakhargosh/watchdog/issues +.. _Apache License, version 2.0: https://www.apache.org/licenses/LICENSE-2.0 .. _documentation: https://python-watchdog.readthedocs.io/ -.. _stackoverflow: http://stackoverflow.com/questions/tagged/python-watchdog -.. _mailing list: http://groups.google.com/group/watchdog-python -.. _repository: http://github.com/gorakhargosh/watchdog -.. _issue tracker: http://github.com/gorakhargosh/watchdog/issues +.. _stackoverflow: https://stackoverflow.com/questions/tagged/python-watchdog +.. _mailing list: https://groups.google.com/group/watchdog-python +.. _repository: https://github.com/gorakhargosh/watchdog +.. _issue tracker: https://github.com/gorakhargosh/watchdog/issues .. _changelog: https://github.com/gorakhargosh/watchdog/blob/master/changelog.rst -.. _homebrew: http://mxcl.github.com/homebrew/ -.. _argh: http://pypi.python.org/pypi/argh -.. _PyYAML: http://www.pyyaml.org/ -.. _XCode: http://developer.apple.com/technologies/tools/xcode.html -.. _LibYAML: http://pyyaml.org/wiki/LibYAML -.. _pathtools: http://github.com/gorakhargosh/pathtools +.. _homebrew: https://brew.sh/ +.. _PyYAML: https://www.pyyaml.org/ +.. _XCode: https://developer.apple.com/technologies/tools/xcode.html +.. _LibYAML: https://pyyaml.org/wiki/LibYAML .. _pnotify: http://mark.heily.com/pnotify .. _unison fsmonitor: https://webdav.seas.upenn.edu/viewvc/unison/trunk/src/fsmonitor.py?view=markup&pathrev=471 -.. _fsmonitor: http://github.com/shaurz/fsmonitor -.. _guard: http://github.com/guard/guard -.. _pyinotify: http://github.com/seb-m/pyinotify -.. _inotify-tools: http://github.com/rvoicilas/inotify-tools +.. _fsmonitor: https://github.com/shaurz/fsmonitor +.. _guard: https://github.com/guard/guard +.. _pyinotify: https://github.com/seb-m/pyinotify +.. _inotify-tools: https://github.com/rvoicilas/inotify-tools .. _jnotify: http://jnotify.sourceforge.net/ -.. _treewalker: http://github.com/jbd/treewatcher -.. _file.monitor: http://github.com/pke/file.monitor -.. _pyfilesystem: http://code.google.com/p/pyfilesystem +.. _treewatcher: https://github.com/jbd/treewatcher +.. _file.monitor: https://github.com/pke/file.monitor +.. _pyfilesystem: https://github.com/PyFilesystem/pyfilesystem + +.. |Build Status| image:: https://github.com/gorakhargosh/watchdog/workflows/Tests/badge.svg + :target: https://github.com/gorakhargosh/watchdog/actions?query=workflow%3ATests +.. |CirrusCI Status| image:: https://api.cirrus-ci.com/github/gorakhargosh/watchdog.svg + :target: https://cirrus-ci.com/github/gorakhargosh/watchdog/ .. :changelog: @@ -336,12 +358,210 @@ to do: Changelog --------- +2.1.9 +~~~~~ + +2022-06-10 • `full history `__ + +- [fsevents] Fix flakey test to assert that there are no errors when stopping the emitter. +- [inotify] Suppress occasional ``OSError: [Errno 9] Bad file descriptor`` at shutdown. (`#805 `__) +- [watchmedo] Make ``auto-restart`` restart the sub-process if it terminates. (`#896 `__) +- [watchmedo] Avoid zombie sub-processes when running ``shell-command`` without ``--wait``. (`#405 `__) +- Thanks to our beloved contributors: @samschott, @taleinat, @altendky, @BoboTiG + +2.1.8 +~~~~~ + +2022-05-15 • `full history `__ + +- Fix adding failed emitters on observer schedule. (`#872 `__) +- [inotify] Fix hang when unscheduling watch on a path in an unmounted filesystem. (`#869 `__) +- [watchmedo] Fix broken parsing of ``--kill-after`` argument for the ``auto-restart`` command. (`#870 `__) +- [watchmedo] Fix broken parsing of boolean arguments. (`#887 `__) +- [watchmedo] Fix broken parsing of commands from ``auto-restart``, and ``shell-command``. (`#888 `__) +- [watchmedo] Support setting verbosity level via ``-q/--quiet`` and ``-v/--verbose`` arguments. (`#889 `__) +- Thanks to our beloved contributors: @taleinat, @kianmeng, @palfrey, @IlayRosenberg, @BoboTiG + +2.1.7 +~~~~~ + +2022-03-25 • `full history `__ + +- Eliminate timeout in waiting on event queue. (`#861 `__) +- [inotify] Fix ``not`` equality implementation for ``InotifyEvent``. (`#848 `__) +- [watchmedo] Fix calling commands from within a Python script. (`#879 `__) +- [watchmedo] ``PyYAML`` is loaded only when strictly necessary. Simple usages of ``watchmedo`` are possible without the module being installed. (`#847 `__) +- Thanks to our beloved contributors: @sattlerc, @JanzenLiu, @BoboTiG + +2.1.6 +~~~~~ + +2021-10-01 • `full history `__ + +- [bsd] Fixed returned paths in ``kqueue.py`` and restored the overall results of the test suite. (`#842 `__) +- [bsd] Updated FreeBSD CI support .(`#841 `__) +- [watchmedo] Removed the ``argh`` dependency in favor of the builtin ``argparse`` module. (`#836 `__) +- [watchmedo] Removed unexistant ``WindowsApiAsyncObserver`` references and ``--debug-force-winapi-async`` arguments. +- [watchmedo] Improved the help output. +- Thanks to our beloved contributors: @knobix, @AndreaRe9, @BoboTiG + +2.1.5 +~~~~~ + +2021-08-23 • `full history `__ + +- Fix regression introduced in 2.1.4 (reverted "Allow overriding or adding custom event handlers to event dispatch map. (`#814 `__)"). (`#830 `__) +- Convert regexes of type ``str`` to ``list``. (`831 `__) +- Thanks to our beloved contributors: @unique1o1, @BoboTiG + +2.1.4 +~~~~~ + +2021-08-19 • `full history `__ + +- [watchmedo] Fix usage of ``os.setsid()`` and ``os.killpg()`` Unix-only functions. (`#809 `__) +- [mac] Fix missing ``FileModifiedEvent`` on permission or ownership changes of a file. (`#815 `__) +- [mac] Convert absolute watch path in ``FSEeventsEmitter`` with ``os.path.realpath()``. (`#822 `__) +- Fix a possible ``AttributeError`` in ``SkipRepeatsQueue._put()``. (`#818 `__) +- Allow overriding or adding custom event handlers to event dispatch map. (`#814 `__) +- Fix tests on big endian platforms. (`#828 `__) +- Thanks to our beloved contributors: @replabrobin, @BoboTiG, @SamSchott, @AndreiB97, @NiklasRosenstein, @ikokollari, @mgorny + +2.1.3 +~~~~~ + +2021-06-26 • `full history `__ + +- Publish macOS ``arm64`` and ``universal2`` wheels. (`#740 `__) +- Thanks to our beloved contributors: @kainjow, @BoboTiG + +2.1.2 +~~~~~ + +2021-05-19 • `full history `__ + +- [mac] Fix relative path handling for non-recursive watch. (`#797 `__) +- [windows] On PyPy, events happening right after ``start()`` were missed. Add a workaround for that. (`#796 `__) +- Thanks to our beloved contributors: @oprypin, @CCP-Aporia, @BoboTiG + +2.1.1 +~~~~~ + +2021-05-10 • `full history `__ + +- [mac] Fix callback exceptions when the watcher is deleted but still receiving events (`#786 `__) +- Thanks to our beloved contributors: @rom1win, @BoboTiG, @CCP-Aporia + + +2.1.0 +~~~~~ + +2021-05-04 • `full history `__ + +- [inotify] Simplify ``libc`` loading (`#776 `__) +- [mac] Add support for non-recursive watches in ``FSEventsEmitter`` (`#779 `__) +- [watchmedo] Add support for ``--debug-force-*`` arguments to ``tricks`` (`#781 `__) +- Thanks to our beloved contributors: @CCP-Aporia, @aodj, @UnitedMarsupials, @BoboTiG + + +2.0.3 +~~~~~ + +2021-04-22 • `full history `__ + +- [mac] Use ``logger.debug()`` instead of ``logger.info()`` (`#774 `__) +- Updated documentation links (`#777 `__) +- Thanks to our beloved contributors: @globau, @imba-tjd, @BoboTiG + + +2.0.2 +~~~~~ + +2021-02-22 • `full history `__ + +- [mac] Add missing exception objects (`#766 `__) +- Thanks to our beloved contributors: @CCP-Aporia, @BoboTiG + + +2.0.1 +~~~~~ + +2021-02-17 • `full history `__ + +- [mac] Fix a segmentation fault when dealing with unicode paths (`#763 `__) +- Moved the CI from Travis-CI to GitHub Actions (`#764 `__) +- Thanks to our beloved contributors: @SamSchott, @BoboTiG + + +2.0.0 +~~~~~ + +2021-02-11 • `full history `__ + +- Avoid deprecated ``PyEval_InitThreads`` on Python 3.7+ (`#746 `__) +- [inotify] Add support for ``IN_CLOSE_WRITE`` events. A ``FileCloseEvent`` event will be fired. Note that ``IN_CLOSE_NOWRITE`` events are not handled to prevent much noise. (`#184 `__, `#245 `__, `#280 `__, `#313 `__, `#690 `__) +- [inotify] Allow to stop the emitter multiple times (`#760 `__) +- [mac] Support coalesced filesystem events (`#734 `__) +- [mac] Drop support for macOS 10.12 and earlier (`#750 `__) +- [mac] Fix an issue when renaming an item changes only the casing (`#750 `__) +- Thanks to our beloved contributors: @bstaletic, @lukassup, @ysard, @SamSchott, @CCP-Aporia, @BoboTiG + + +1.0.2 +~~~~~ + +2020-12-18 • `full history `__ + +- Wheels are published for GNU/Linux, macOS and Windows (`#739 `__) +- [mac] Fix missing ``event_id`` attribute in ``fsevents`` (`#721 `__) +- [mac] Return byte paths if a byte path was given in ``fsevents`` (`#726 `__) +- [mac] Add compatibility with old macOS versions (`#733 `__) +- Uniformize event for deletion of watched dir (`#727 `__) +- Thanks to our beloved contributors: @SamSchott, @CCP-Aporia, @di, @BoboTiG + + +1.0.1 +~~~~~ + +2020-12-10 • Fix version with good metadatas. + + +1.0.0 +~~~~~ + +2020-12-10 • `full history `__ + +- Versioning is now following the `semver `__ +- Drop support for Python 2.7, 3.4 and 3.5 +- [mac] Regression fixes for native ``fsevents`` (`#717 `__) +- [windows] ``winapi.BUFFER_SIZE`` now defaults to ``64000`` (instead of ``2048``) (`#700 `__) +- [windows] Introduced ``winapi.PATH_BUFFER_SIZE`` (defaults to ``2048``) to keep the old behavior with path-realted functions (`#700 `__) +- Use ``pathlib`` from the standard library, instead of pathtools (`#556 `__) +- Allow file paths on Unix that don't follow the file system encoding (`#703 `__) +- Removed the long-time deprecated ``events.LoggingFileSystemEventHandler`` class, use ``LoggingEventHandler`` instead +- Thanks to our beloved contributors: @SamSchott, @bstaletic, @BoboTiG, @CCP-Aporia + + +0.10.4 +~~~~~~ + +2020-11-21 • `full history `__ + +- Add ``logger`` parameter for the ``LoggingEventHandler`` (`#676 `__) +- Replace mutable default arguments with ``if None`` implementation (`#677 `__) +- Expand tests to Python 2.7 and 3.5-3.10 for GNU/Linux, macOS and Windows +- [mac] Performance improvements for the ``fsevents`` module (`#680 `__) +- [mac] Prevent compilation of ``watchdog_fsevents.c`` on non-macOS machines (`#687 `__) +- [watchmedo] Handle shutdown events from ``SIGTERM`` and ``SIGINT`` more reliably (`#693 `__) +- Thanks to our beloved contributors: @Sraw, @CCP-Aporia, @BoboTiG, @maybe-sybr + + 0.10.3 ~~~~~~ -2020-0x-xx • `full history `__ +2020-06-25 • `full history `__ -- Ensure ``ObservedWatch.path`` is a string (`#651 `_) +- Ensure ``ObservedWatch.path`` is a string (`#651 `__) - [inotify] Allow to monitor single file (`#655 `__) - [inotify] Prevent raising an exception when a file in a monitored folder has no permissions (`#669 `__, `#670 `__) - Thanks to our beloved contributors: @brant-ruan, @rec, @andfoy, @BoboTiG @@ -368,7 +588,7 @@ Changelog 2020-01-30 • `full history `__ - Fixed Python 2.7 to 3.6 installation when the OS locale is set to POSIX (`#615 `__) -- Fixed the ``build_ext`` command on macOS (`#618 `__, `#620 `_) +- Fixed the ``build_ext`` command on macOS (`#618 `__, `#620 `__) - Moved requirements to ``setup.cfg`` (`#617 `__) - [mac] Removed old C code for Python 2.5 in the `fsevents` C implementation - [snapshot] Added ``EmptyDirectorySnapshot`` (`#613 `__) @@ -494,5 +714,3 @@ Changelog - [windows] Fixed octal usages to work with Python 3 as well (`#223 `__) - Thanks to our beloved contributors: @tamland, @Ormod, @berdario, @cro, @BernieSumption, @pypingou, @gotcha, @tommorris, @frewsxcv - - diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/RECORD b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/RECORD new file mode 100644 index 0000000000..7b5b4eaad4 --- /dev/null +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/RECORD @@ -0,0 +1,59 @@ +../../bin/watchmedo.exe,sha256=RSNzAH70UO-9-VtSi0wjW8eB-aSLou_5aJRiFm-p7fk,106351 +watchdog-2.1.9.dist-info/AUTHORS,sha256=3tDZN90ee5EvJc_enpUieL13W41IlP1T05zyRU6bCp8,2843 +watchdog-2.1.9.dist-info/COPYING,sha256=Ash2D5iKdukqnWy1JUVqhvew_RlThw3Ukd5ZVcuXTUE,625 +watchdog-2.1.9.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +watchdog-2.1.9.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +watchdog-2.1.9.dist-info/METADATA,sha256=NnmcF2ALy6YS2lG6eWs3MslOOkSfv4Cd_zbLRA9RWJM,33249 +watchdog-2.1.9.dist-info/RECORD,, +watchdog-2.1.9.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +watchdog-2.1.9.dist-info/WHEEL,sha256=i9qQj8KaD8_YEW0Vc2oS56fKju23RkQ-FVz-QmzVakQ,98 +watchdog-2.1.9.dist-info/entry_points.txt,sha256=qt_Oe2U5Zlfz7LNA3PHipn3_1zlfRTp9dk3wTS3Ivb8,66 +watchdog-2.1.9.dist-info/top_level.txt,sha256=OVdR7GkPGZako8sRtVuM0Nis-ZIElx3he3hKFPYnTGg,9 +watchdog/__init__.py,sha256=hO-KAJjNMN3mMRxjfTA2iF0bU1rlXuOQwdBeKpAwO10,669 +watchdog/__pycache__/__init__.cpython-38.pyc,, +watchdog/__pycache__/events.cpython-38.pyc,, +watchdog/__pycache__/version.cpython-38.pyc,, +watchdog/__pycache__/watchmedo.cpython-38.pyc,, +watchdog/events.py,sha256=6S2S_h6cy168vDwKJ0RCkuUFkhMO0B3kfm-xXMAClh0,16234 +watchdog/observers/__init__.py,sha256=AK_UkVhN5o4QC_HhXuN54gT5B4h7kTCWpI7oDcNcIGk,3474 +watchdog/observers/__pycache__/__init__.cpython-38.pyc,, +watchdog/observers/__pycache__/api.cpython-38.pyc,, +watchdog/observers/__pycache__/fsevents.cpython-38.pyc,, +watchdog/observers/__pycache__/fsevents2.cpython-38.pyc,, +watchdog/observers/__pycache__/inotify.cpython-38.pyc,, +watchdog/observers/__pycache__/inotify_buffer.cpython-38.pyc,, +watchdog/observers/__pycache__/inotify_c.cpython-38.pyc,, +watchdog/observers/__pycache__/kqueue.cpython-38.pyc,, +watchdog/observers/__pycache__/polling.cpython-38.pyc,, +watchdog/observers/__pycache__/read_directory_changes.cpython-38.pyc,, +watchdog/observers/__pycache__/winapi.cpython-38.pyc,, +watchdog/observers/api.py,sha256=ykapYn8se915Rc5BB5DfG2IarF6_Urp2JjJ6YYiqmpU,12000 +watchdog/observers/fsevents.py,sha256=Es9bcxqgeDyGksTy88HqNjSMXfmuDH63rqz59L81v_Q,13799 +watchdog/observers/fsevents2.py,sha256=z8iYylt2l2zw9QR-VZhNb3jgOdhcY7pVxwBU-WjGNBA,9071 +watchdog/observers/inotify.py,sha256=J7LkJhGbnfeXrMvLuYkBIZkOovYz9DBc1uX1uaZDe7o,8937 +watchdog/observers/inotify_buffer.py,sha256=hm1CQHcm8CEPtheCpxFjWFUunMwdSuAVr7lyQCSzx6c,4241 +watchdog/observers/inotify_c.py,sha256=azVEF7w5PeNeXXdxkVPHTq329ZaUidUvn5mLTzYqYiQ,19267 +watchdog/observers/kqueue.py,sha256=KTm2mKcyMsFM2b6B90CWbD1BoeTfTI-qbgt2PgOyDO0,24290 +watchdog/observers/polling.py,sha256=uZ0Qes7cbhnqBIUCYuDKs-lAyetSOgJzaviq_DGqlmc,4765 +watchdog/observers/read_directory_changes.py,sha256=kPeFeE3hYUesrFZSmUJpY8mXm4e71mwGKFOnlIxMaIM,5630 +watchdog/observers/winapi.py,sha256=Hj6xPdQN6breE79weX_U_RnmTJ-KzJiJTAju7GiptAg,13559 +watchdog/tricks/__init__.py,sha256=BBT_Ucr5oD9h9tOTILawaZKTrL8HguLglQVvYCIO_zo,6896 +watchdog/tricks/__pycache__/__init__.cpython-38.pyc,, +watchdog/utils/__init__.py,sha256=xrHOKXjO6AdQ37LAtDNPmz9OFd1Bl5X1R8rGUdH5iMI,4226 +watchdog/utils/__pycache__/__init__.cpython-38.pyc,, +watchdog/utils/__pycache__/bricks.cpython-38.pyc,, +watchdog/utils/__pycache__/delayed_queue.cpython-38.pyc,, +watchdog/utils/__pycache__/dirsnapshot.cpython-38.pyc,, +watchdog/utils/__pycache__/echo.cpython-38.pyc,, +watchdog/utils/__pycache__/patterns.cpython-38.pyc,, +watchdog/utils/__pycache__/platform.cpython-38.pyc,, +watchdog/utils/__pycache__/process_watcher.cpython-38.pyc,, +watchdog/utils/bricks.py,sha256=3sFY6KlQX1U76cOOMVutZUOEGXAyloGPrrwBBGkRGso,2867 +watchdog/utils/delayed_queue.py,sha256=_pQnfTVoqSzQCEJGJm7Av_-zgYLa9FHIG2Wh1JwQLDc,2856 +watchdog/utils/dirsnapshot.py,sha256=Fqk8z2WT_S2_ZkV-o2UyT7BgI9m2CChDJSfbpFF7mSA,11818 +watchdog/utils/echo.py,sha256=yQ8PbIrcKNj4NRTV7fIT6fuXWhlx8Hb-xcwrQx0uKA4,5283 +watchdog/utils/patterns.py,sha256=A6oO2TGaUxx-Q5eaKzevlZjX9Q4YrJUuPlqkzS-yfAY,3886 +watchdog/utils/platform.py,sha256=mHXCyB4j4aTT_5pI0bO_JELXNTz4NCombVVS4y_iWUI,1497 +watchdog/utils/process_watcher.py,sha256=t_0UpRPCOrDNFLLIoBT7tQ41QztMP01VGSnRgC2OQNQ,680 +watchdog/version.py,sha256=qfQJwYfiHNwEuOtc-BjY6kXIgNJExyDsAFYeuSC3VvA,959 +watchdog/watchmedo.py,sha256=v05nBmKDocSy3lfDUsHzM3OsDzOfFoVNX7-6unNLiBA,24646 diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/REQUESTED b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/REQUESTED new file mode 100644 index 0000000000..e69de29bb2 diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/WHEEL b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/WHEEL new file mode 100644 index 0000000000..9605f562ec --- /dev/null +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-win_amd64 + diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/entry_points.txt b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/entry_points.txt similarity index 98% rename from robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/entry_points.txt rename to robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/entry_points.txt index 8301c04b2a..b05809e4e1 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/entry_points.txt +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/entry_points.txt @@ -1,3 +1,2 @@ [console_scripts] watchmedo = watchdog.watchmedo:main [watchmedo] - diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/top_level.txt b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/top_level.txt similarity index 100% rename from robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-0.10.3.dist-info/top_level.txt rename to robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog-2.1.9.dist-info/top_level.txt diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/__init__.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/__init__.py index 1a641ff98f..dc7338f0a5 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/__init__.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/__init__.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/events.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/events.py index acb1731d28..5f9e92ba31 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/events.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/events.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,6 +19,7 @@ :module: watchdog.events :synopsis: File system events and event handlers. :author: yesudeep@google.com (Yesudeep Mangalapilly) +:author: contact@tiger-222.fr (Mickaël Schoentgen) Event Classes ------------- @@ -52,6 +52,10 @@ :members: :show-inheritance: +.. autoclass:: FileClosedEvent + :members: + :show-inheritance: + .. autoclass:: DirCreatedEvent :members: :show-inheritance: @@ -88,18 +92,17 @@ import os.path import logging import re -from pathtools.patterns import match_any_paths -from watchdog.utils import has_attribute -from watchdog.utils import unicode_paths +from watchdog.utils.patterns import match_any_paths EVENT_TYPE_MOVED = 'moved' EVENT_TYPE_DELETED = 'deleted' EVENT_TYPE_CREATED = 'created' EVENT_TYPE_MODIFIED = 'modified' +EVENT_TYPE_CLOSED = 'closed' -class FileSystemEvent(object): +class FileSystemEvent: """ Immutable type that represents a file system event that is triggered when a change occurs on the monitored file system. @@ -166,7 +169,7 @@ class FileSystemMovedEvent(FileSystemEvent): event_type = EVENT_TYPE_MOVED def __init__(self, src_path, dest_path): - super(FileSystemMovedEvent, self).__init__(src_path) + super().__init__(src_path) self._dest_path = dest_path @property @@ -197,55 +200,27 @@ class FileDeletedEvent(FileSystemEvent): event_type = EVENT_TYPE_DELETED - def __init__(self, src_path): - super(FileDeletedEvent, self).__init__(src_path) - - def __repr__(self): - return "<%(class_name)s: src_path=%(src_path)r>" %\ - dict(class_name=self.__class__.__name__, - src_path=self.src_path) - class FileModifiedEvent(FileSystemEvent): """File system event representing file modification on the file system.""" event_type = EVENT_TYPE_MODIFIED - def __init__(self, src_path): - super(FileModifiedEvent, self).__init__(src_path) - - def __repr__(self): - return ("<%(class_name)s: src_path=%(src_path)r>" - ) % (dict(class_name=self.__class__.__name__, - src_path=self.src_path)) - class FileCreatedEvent(FileSystemEvent): """File system event representing file creation on the file system.""" event_type = EVENT_TYPE_CREATED - def __init__(self, src_path): - super(FileCreatedEvent, self).__init__(src_path) - - def __repr__(self): - return ("<%(class_name)s: src_path=%(src_path)r>" - ) % (dict(class_name=self.__class__.__name__, - src_path=self.src_path)) - class FileMovedEvent(FileSystemMovedEvent): """File system event representing file movement on the file system.""" - def __init__(self, src_path, dest_path): - super(FileMovedEvent, self).__init__(src_path, dest_path) - def __repr__(self): - return ("<%(class_name)s: src_path=%(src_path)r, " - "dest_path=%(dest_path)r>" - ) % (dict(class_name=self.__class__.__name__, - src_path=self.src_path, - dest_path=self.dest_path)) +class FileClosedEvent(FileSystemEvent): + """File system event representing file close on the file system.""" + + event_type = EVENT_TYPE_CLOSED # Directory events. @@ -257,14 +232,6 @@ class DirDeletedEvent(FileSystemEvent): event_type = EVENT_TYPE_DELETED is_directory = True - def __init__(self, src_path): - super(DirDeletedEvent, self).__init__(src_path) - - def __repr__(self): - return ("<%(class_name)s: src_path=%(src_path)r>" - ) % (dict(class_name=self.__class__.__name__, - src_path=self.src_path)) - class DirModifiedEvent(FileSystemEvent): """ @@ -274,14 +241,6 @@ class DirModifiedEvent(FileSystemEvent): event_type = EVENT_TYPE_MODIFIED is_directory = True - def __init__(self, src_path): - super(DirModifiedEvent, self).__init__(src_path) - - def __repr__(self): - return ("<%(class_name)s: src_path=%(src_path)r>" - ) % (dict(class_name=self.__class__.__name__, - src_path=self.src_path)) - class DirCreatedEvent(FileSystemEvent): """File system event representing directory creation on the file system.""" @@ -289,32 +248,14 @@ class DirCreatedEvent(FileSystemEvent): event_type = EVENT_TYPE_CREATED is_directory = True - def __init__(self, src_path): - super(DirCreatedEvent, self).__init__(src_path) - - def __repr__(self): - return ("<%(class_name)s: src_path=%(src_path)r>" - ) % (dict(class_name=self.__class__.__name__, - src_path=self.src_path)) - class DirMovedEvent(FileSystemMovedEvent): """File system event representing directory movement on the file system.""" is_directory = True - def __init__(self, src_path, dest_path): - super(DirMovedEvent, self).__init__(src_path, dest_path) - - def __repr__(self): - return ("<%(class_name)s: src_path=%(src_path)r, " - "dest_path=%(dest_path)r>" - ) % (dict(class_name=self.__class__.__name__, - src_path=self.src_path, - dest_path=self.dest_path)) - -class FileSystemEventHandler(object): +class FileSystemEventHandler: """ Base file system event handler that you can override methods from. """ @@ -333,6 +274,7 @@ def dispatch(self, event): EVENT_TYPE_DELETED: self.on_deleted, EVENT_TYPE_MODIFIED: self.on_modified, EVENT_TYPE_MOVED: self.on_moved, + EVENT_TYPE_CLOSED: self.on_closed, }[event.event_type](event) def on_any_event(self, event): @@ -380,6 +322,15 @@ def on_modified(self, event): :class:`DirModifiedEvent` or :class:`FileModifiedEvent` """ + def on_closed(self, event): + """Called when a file opened for writing is closed. + + :param event: + Event representing file closing. + :type event: + :class:`FileClosedEvent` + """ + class PatternMatchingEventHandler(FileSystemEventHandler): """ @@ -388,7 +339,7 @@ class PatternMatchingEventHandler(FileSystemEventHandler): def __init__(self, patterns=None, ignore_patterns=None, ignore_directories=False, case_sensitive=False): - super(PatternMatchingEventHandler, self).__init__() + super().__init__() self._patterns = patterns self._ignore_patterns = ignore_patterns @@ -440,16 +391,16 @@ def dispatch(self, event): return paths = [] - if has_attribute(event, 'dest_path'): - paths.append(unicode_paths.decode(event.dest_path)) + if hasattr(event, 'dest_path'): + paths.append(os.fsdecode(event.dest_path)) if event.src_path: - paths.append(unicode_paths.decode(event.src_path)) + paths.append(os.fsdecode(event.src_path)) if match_any_paths(paths, included_patterns=self.patterns, excluded_patterns=self.ignore_patterns, case_sensitive=self.case_sensitive): - super(PatternMatchingEventHandler, self).dispatch(event) + super().dispatch(event) class RegexMatchingEventHandler(FileSystemEventHandler): @@ -457,10 +408,16 @@ class RegexMatchingEventHandler(FileSystemEventHandler): Matches given regexes with file paths associated with occurring events. """ - def __init__(self, regexes=[r".*"], ignore_regexes=[], + def __init__(self, regexes=None, ignore_regexes=None, ignore_directories=False, case_sensitive=False): - super(RegexMatchingEventHandler, self).__init__() - + super().__init__() + + if regexes is None: + regexes = [r".*"] + elif isinstance(regexes, str): + regexes = [regexes] + if ignore_regexes is None: + ignore_regexes = [] if case_sensitive: self._regexes = [re.compile(r) for r in regexes] self._ignore_regexes = [re.compile(r) for r in ignore_regexes] @@ -515,52 +472,50 @@ def dispatch(self, event): return paths = [] - if has_attribute(event, 'dest_path'): - paths.append(unicode_paths.decode(event.dest_path)) + if hasattr(event, 'dest_path'): + paths.append(os.fsdecode(event.dest_path)) if event.src_path: - paths.append(unicode_paths.decode(event.src_path)) + paths.append(os.fsdecode(event.src_path)) if any(r.match(p) for r in self.ignore_regexes for p in paths): return if any(r.match(p) for r in self.regexes for p in paths): - super(RegexMatchingEventHandler, self).dispatch(event) + super().dispatch(event) class LoggingEventHandler(FileSystemEventHandler): """Logs all the events captured.""" + def __init__(self, logger=None): + super().__init__() + + self.logger = logger or logging.root + def on_moved(self, event): - super(LoggingEventHandler, self).on_moved(event) + super().on_moved(event) what = 'directory' if event.is_directory else 'file' - logging.info("Moved %s: from %s to %s", what, event.src_path, - event.dest_path) + self.logger.info("Moved %s: from %s to %s", what, event.src_path, + event.dest_path) def on_created(self, event): - super(LoggingEventHandler, self).on_created(event) + super().on_created(event) what = 'directory' if event.is_directory else 'file' - logging.info("Created %s: %s", what, event.src_path) + self.logger.info("Created %s: %s", what, event.src_path) def on_deleted(self, event): - super(LoggingEventHandler, self).on_deleted(event) + super().on_deleted(event) what = 'directory' if event.is_directory else 'file' - logging.info("Deleted %s: %s", what, event.src_path) + self.logger.info("Deleted %s: %s", what, event.src_path) def on_modified(self, event): - super(LoggingEventHandler, self).on_modified(event) + super().on_modified(event) what = 'directory' if event.is_directory else 'file' - logging.info("Modified %s: %s", what, event.src_path) - - -class LoggingFileSystemEventHandler(LoggingEventHandler): - """ - For backwards-compatibility. Please use :class:`LoggingEventHandler` - instead. - """ + self.logger.info("Modified %s: %s", what, event.src_path) def generate_sub_moved_events(src_dir_path, dest_dir_path): diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/__init__.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/__init__.py index 7ba6d3d576..0dc18c0d43 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/__init__.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/__init__.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,7 +19,7 @@ :module: watchdog.observers :synopsis: Observer that picks a native implementation if available. :author: yesudeep@google.com (Yesudeep Mangalapilly) - +:author: contact@tiger-222.fr (Mickaël Schoentgen) Classes ======= @@ -39,8 +38,8 @@ Class Platforms Note ============== ================================ ============================== |Inotify| Linux 2.6.13+ ``inotify(7)`` based observer -|FSEvents| Mac OS X FSEvents based observer -|Kqueue| Mac OS X and BSD with kqueue(2) ``kqueue(2)`` based observer +|FSEvents| macOS FSEvents based observer +|Kqueue| macOS and BSD with kqueue(2) ``kqueue(2)`` based observer |WinApi| MS Windows Windows API-based observer |Polling| Any fallback implementation ============== ================================ ============================== @@ -49,7 +48,6 @@ .. |FSEvents| replace:: :class:`.fsevents.FSEventsObserver` .. |Kqueue| replace:: :class:`.kqueue.KqueueObserver` .. |WinApi| replace:: :class:`.read_directory_changes.WindowsApiObserver` -.. |WinApiAsync| replace:: :class:`.read_directory_changes_async.WindowsApiAsyncObserver` .. |Polling| replace:: :class:`.polling.PollingObserver` """ diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/api.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/api.py index d81a33b73d..4d63f4838a 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/api.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/api.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,15 +15,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +import queue import threading +from pathlib import Path + from watchdog.utils import BaseThread -from watchdog.utils.compat import queue from watchdog.utils.bricks import SkipRepeatsQueue -try: - from pathlib import Path as _PATH_CLASSES -except ImportError: - _PATH_CLASSES = () DEFAULT_EMITTER_TIMEOUT = 1 # in seconds. DEFAULT_OBSERVER_TIMEOUT = 1 # in seconds. @@ -40,7 +36,7 @@ class EventQueue(SkipRepeatsQueue): """ -class ObservedWatch(object): +class ObservedWatch: """An scheduled watch. :param path: @@ -50,7 +46,7 @@ class ObservedWatch(object): """ def __init__(self, path, recursive): - if isinstance(path, _PATH_CLASSES): + if isinstance(path, Path): self._path = str(path) else: self._path = path @@ -105,7 +101,7 @@ class EventEmitter(BaseThread): """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): - BaseThread.__init__(self) + super().__init__() self._event_queue = event_queue self._watch = watch self._timeout = timeout @@ -158,21 +154,32 @@ class EventDispatcher(BaseThread): that dispatch events from an event queue to appropriate event handlers. :param timeout: - Event queue blocking timeout (in seconds). + Timeout value (in seconds) passed to emitters + constructions in the child class BaseObserver. :type timeout: ``float`` """ + _stop_event = object() + """Event inserted into the queue to signal a requested stop.""" + def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): - BaseThread.__init__(self) + super().__init__() self._event_queue = EventQueue() self._timeout = timeout @property def timeout(self): - """Event queue block timeout.""" + """Timeout value to construct emitters with.""" return self._timeout + def stop(self): + BaseThread.stop(self) + try: + self.event_queue.put_nowait(EventDispatcher._stop_event) + except queue.Full: + pass + @property def event_queue(self): """The event queue which is populated with file system events @@ -180,7 +187,7 @@ def event_queue(self): thread.""" return self._event_queue - def dispatch_events(self, event_queue, timeout): + def dispatch_events(self, event_queue): """Override this method to consume events from an event queue, blocking on the queue for the specified timeout before raising :class:`queue.Empty`. @@ -188,11 +195,6 @@ def dispatch_events(self, event_queue, timeout): Event queue to populate with one set of events. :type event_queue: :class:`EventQueue` - :param timeout: - Interval period (in seconds) to wait before timing out on the - event queue. - :type timeout: - ``float`` :raises: :class:`queue.Empty` """ @@ -200,7 +202,7 @@ def dispatch_events(self, event_queue, timeout): def run(self): while self.should_keep_running(): try: - self.dispatch_events(self.event_queue, self.timeout) + self.dispatch_events(self.event_queue) except queue.Empty: continue @@ -209,7 +211,7 @@ class BaseObserver(EventDispatcher): """Base observer.""" def __init__(self, emitter_class, timeout=DEFAULT_OBSERVER_TIMEOUT): - EventDispatcher.__init__(self, timeout) + super().__init__(timeout) self._emitter_class = emitter_class self._lock = threading.RLock() self._watches = set() @@ -261,7 +263,7 @@ def start(self): except Exception: self._remove_emitter(emitter) raise - super(BaseObserver, self).start() + super().start() def schedule(self, event_handler, path, recursive=False): """ @@ -296,9 +298,9 @@ def schedule(self, event_handler, path, recursive=False): emitter = self._emitter_class(event_queue=self.event_queue, watch=watch, timeout=self.timeout) - self._add_emitter(emitter) if self.is_alive(): emitter.start() + self._add_emitter(emitter) self._watches.add(watch) return watch @@ -364,8 +366,11 @@ def unschedule_all(self): def on_thread_stop(self): self.unschedule_all() - def dispatch_events(self, event_queue, timeout): - event, watch = event_queue.get(block=True, timeout=timeout) + def dispatch_events(self, event_queue): + entry = event_queue.get(block=True) + if entry is EventDispatcher._stop_event: + return + event, watch = entry with self._lock: # To allow unschedule/stop and safe removal of event handlers diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/fsevents.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/fsevents.py index b7e4b0e839..1e962121f0 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/fsevents.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/fsevents.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,12 +19,13 @@ :module: watchdog.observers.fsevents :synopsis: FSEvents based emitter implementation. :author: yesudeep@google.com (Yesudeep Mangalapilly) -:platforms: Mac OS X +:author: contact@tiger-222.fr (Mickaël Schoentgen) +:platforms: macOS """ -from __future__ import with_statement - -import sys +import time +import logging +import os import threading import unicodedata import _watchdog_fsevents as _fsevents @@ -38,22 +38,26 @@ DirDeletedEvent, DirModifiedEvent, DirCreatedEvent, - DirMovedEvent + DirMovedEvent, + generate_sub_created_events, + generate_sub_moved_events ) -from watchdog.utils.dirsnapshot import DirectorySnapshot from watchdog.observers.api import ( BaseObserver, EventEmitter, DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT ) +from watchdog.utils.dirsnapshot import DirectorySnapshot + +logger = logging.getLogger('fsevents') class FSEventsEmitter(EventEmitter): """ - Mac OS X FSEvents Emitter class. + macOS FSEvents Emitter class. :param event_queue: The event queue to fill with events. @@ -63,112 +67,281 @@ class FSEventsEmitter(EventEmitter): :class:`watchdog.observers.api.ObservedWatch` :param timeout: Read events blocking timeout (in seconds). + :param suppress_history: + The FSEvents API may emit historic events up to 30 sec before the watch was + started. When ``suppress_history`` is ``True``, those events will be suppressed + by creating a directory snapshot of the watched path before starting the stream + as a reference to suppress old events. Warning: This may result in significant + memory usage in case of a large number of items in the watched path. :type timeout: ``float`` """ - def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): - EventEmitter.__init__(self, event_queue, watch, timeout) + def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT, suppress_history=False): + super().__init__(event_queue, watch, timeout) + self._fs_view = set() + self.suppress_history = suppress_history + self._start_time = 0.0 + self._starting_state = None self._lock = threading.Lock() - self.snapshot = DirectorySnapshot(watch.path, watch.is_recursive) + self._absolute_watch_path = os.path.realpath(os.path.abspath(os.path.expanduser(self.watch.path))) def on_thread_stop(self): - if self.watch: - _fsevents.remove_watch(self.watch) - _fsevents.stop(self) - self._watch = None - - def queue_events(self, timeout): - with self._lock: - if (not self.watch.is_recursive - and self.watch.path not in self.pathnames): - return - new_snapshot = DirectorySnapshot(self.watch.path, - self.watch.is_recursive) - events = new_snapshot - self.snapshot - self.snapshot = new_snapshot - - # Files. - for src_path in events.files_deleted: - self.queue_event(FileDeletedEvent(src_path)) - for src_path in events.files_modified: - self.queue_event(FileModifiedEvent(src_path)) - for src_path in events.files_created: - self.queue_event(FileCreatedEvent(src_path)) - for src_path, dest_path in events.files_moved: - self.queue_event(FileMovedEvent(src_path, dest_path)) - - # Directories. - for src_path in events.dirs_deleted: - self.queue_event(DirDeletedEvent(src_path)) - for src_path in events.dirs_modified: - self.queue_event(DirModifiedEvent(src_path)) - for src_path in events.dirs_created: - self.queue_event(DirCreatedEvent(src_path)) - for src_path, dest_path in events.dirs_moved: - self.queue_event(DirMovedEvent(src_path, dest_path)) + _fsevents.remove_watch(self.watch) + _fsevents.stop(self) + + def queue_event(self, event): + # fsevents defaults to be recursive, so if the watch was meant to be non-recursive then we need to drop + # all the events here which do not have a src_path / dest_path that matches the watched path + if self._watch.is_recursive: + logger.debug("queue_event %s", event) + EventEmitter.queue_event(self, event) + else: + if not self._is_recursive_event(event): + logger.debug("queue_event %s", event) + EventEmitter.queue_event(self, event) + else: + logger.debug("drop event %s", event) + + def _is_recursive_event(self, event): + src_path = event.src_path if event.is_directory else os.path.dirname(event.src_path) + if src_path == self._absolute_watch_path: + return False + + if isinstance(event, (FileMovedEvent, DirMovedEvent)): + # when moving something into the watch path we must always take the dirname, + # otherwise we miss out on `DirMovedEvent`s + dest_path = os.path.dirname(event.dest_path) + if dest_path == self._absolute_watch_path: + return False + + return True + + def _queue_created_event(self, event, src_path, dirname): + cls = DirCreatedEvent if event.is_directory else FileCreatedEvent + self.queue_event(cls(src_path)) + self.queue_event(DirModifiedEvent(dirname)) + + def _queue_deleted_event(self, event, src_path, dirname): + cls = DirDeletedEvent if event.is_directory else FileDeletedEvent + self.queue_event(cls(src_path)) + self.queue_event(DirModifiedEvent(dirname)) + + def _queue_modified_event(self, event, src_path, dirname): + cls = DirModifiedEvent if event.is_directory else FileModifiedEvent + self.queue_event(cls(src_path)) + + def _queue_renamed_event(self, src_event, src_path, dst_path, src_dirname, dst_dirname): + cls = DirMovedEvent if src_event.is_directory else FileMovedEvent + dst_path = self._encode_path(dst_path) + self.queue_event(cls(src_path, dst_path)) + self.queue_event(DirModifiedEvent(src_dirname)) + self.queue_event(DirModifiedEvent(dst_dirname)) + + def _is_historic_created_event(self, event): + + # We only queue a created event if the item was created after we + # started the FSEventsStream. + + in_history = event.inode in self._fs_view + + if self._starting_state: + try: + old_inode = self._starting_state.inode(event.path)[0] + before_start = old_inode == event.inode + except KeyError: + before_start = False + else: + before_start = False + + return in_history or before_start + + @staticmethod + def _is_meta_mod(event): + """Returns True if the event indicates a change in metadata.""" + return event.is_inode_meta_mod or event.is_xattr_mod or event.is_owner_change + + def queue_events(self, timeout, events): + + if logger.getEffectiveLevel() <= logging.DEBUG: + for event in events: + flags = ", ".join(attr for attr in dir(event) if getattr(event, attr) is True) + logger.debug(f"{event}: {flags}") + + if time.monotonic() - self._start_time > 60: + # Event history is no longer needed, let's free some memory. + self._starting_state = None + + while events: + event = events.pop(0) + + src_path = self._encode_path(event.path) + src_dirname = os.path.dirname(src_path) + + try: + stat = os.stat(src_path) + except OSError: + stat = None + + exists = stat and stat.st_ino == event.inode + + # FSevents may coalesce multiple events for the same item + path into a + # single event. However, events are never coalesced for different items at + # the same path or for the same item at different paths. Therefore, the + # event chains "removed -> created" and "created -> renamed -> removed" will + # never emit a single native event and a deleted event *always* means that + # the item no longer existed at the end of the event chain. + + # Some events will have a spurious `is_created` flag set, coalesced from an + # already emitted and processed CreatedEvent. To filter those, we keep track + # of all inodes which we know to be already created. This is safer than + # keeping track of paths since paths are more likely to be reused than + # inodes. + + # Likewise, some events will have a spurious `is_modified`, + # `is_inode_meta_mod` or `is_xattr_mod` flag set. We currently do not + # suppress those but could do so if the item still exists by caching the + # stat result and verifying that it did change. + + if event.is_created and event.is_removed: + + # Events will only be coalesced for the same item / inode. + # The sequence deleted -> created therefore cannot occur. + # Any combination with renamed cannot occur either. + + if not self._is_historic_created_event(event): + self._queue_created_event(event, src_path, src_dirname) + + self._fs_view.add(event.inode) + + if event.is_modified or self._is_meta_mod(event): + self._queue_modified_event(event, src_path, src_dirname) + + self._queue_deleted_event(event, src_path, src_dirname) + self._fs_view.discard(event.inode) + + else: + + if event.is_created and not self._is_historic_created_event(event): + self._queue_created_event(event, src_path, src_dirname) + + self._fs_view.add(event.inode) + + if event.is_modified or self._is_meta_mod(event): + self._queue_modified_event(event, src_path, src_dirname) + + if event.is_renamed: + + # Check if we have a corresponding destination event in the watched path. + dst_event = next(iter(e for e in events if e.is_renamed and e.inode == event.inode), None) + + if dst_event: + # Item was moved within the watched folder. + logger.debug("Destination event for rename is %s", dst_event) + + dst_path = self._encode_path(dst_event.path) + dst_dirname = os.path.dirname(dst_path) + + self._queue_renamed_event(event, src_path, dst_path, src_dirname, dst_dirname) + self._fs_view.add(event.inode) + + for sub_event in generate_sub_moved_events(src_path, dst_path): + self.queue_event(sub_event) + + # Process any coalesced flags for the dst_event. + + events.remove(dst_event) + + if dst_event.is_modified or self._is_meta_mod(dst_event): + self._queue_modified_event(dst_event, dst_path, dst_dirname) + + if dst_event.is_removed: + self._queue_deleted_event(dst_event, dst_path, dst_dirname) + self._fs_view.discard(dst_event.inode) + + elif exists: + # This is the destination event, item was moved into the watched + # folder. + self._queue_created_event(event, src_path, src_dirname) + self._fs_view.add(event.inode) + + for sub_event in generate_sub_created_events(src_path): + self.queue_event(sub_event) + + else: + # This is the source event, item was moved out of the watched + # folder. + self._queue_deleted_event(event, src_path, src_dirname) + self._fs_view.discard(event.inode) + + # Skip further coalesced processing. + continue + + if event.is_removed: + # Won't occur together with renamed. + self._queue_deleted_event(event, src_path, src_dirname) + self._fs_view.discard(event.inode) + + if event.is_root_changed: + # This will be set if root or any of its parents is renamed or deleted. + # TODO: find out new path and generate DirMovedEvent? + self.queue_event(DirDeletedEvent(self.watch.path)) + logger.debug("Stopping because root path was changed") + self.stop() + + self._fs_view.clear() + + def events_callback(self, paths, inodes, flags, ids): + """Callback passed to FSEventStreamCreate(), it will receive all + FS events and queue them. + """ + cls = _fsevents.NativeEvent + try: + events = [ + cls(path, inode, event_flags, event_id) + for path, inode, event_flags, event_id in zip( + paths, inodes, flags, ids + ) + ] + with self._lock: + self.queue_events(self.timeout, events) + except Exception: + logger.exception("Unhandled exception in fsevents callback") def run(self): + self.pathnames = [self.watch.path] + self._start_time = time.monotonic() try: - def callback(pathnames, flags, emitter=self): - emitter.queue_events(emitter.timeout) - - # for pathname, flag in zip(pathnames, flags): - # if emitter.watch.is_recursive: # and pathname != emitter.watch.path: - # new_sub_snapshot = DirectorySnapshot(pathname, True) - # old_sub_snapshot = self.snapshot.copy(pathname) - # diff = new_sub_snapshot - old_sub_snapshot - # self.snapshot += new_subsnapshot - # else: - # new_snapshot = DirectorySnapshot(emitter.watch.path, False) - # diff = new_snapshot - emitter.snapshot - # emitter.snapshot = new_snapshot - - # INFO: FSEvents reports directory notifications recursively - # by default, so we do not need to add subdirectory paths. - # pathnames = set([self.watch.path]) - # if self.watch.is_recursive: - # for root, directory_names, _ in os.walk(self.watch.path): - # for directory_name in directory_names: - # full_path = absolute_path( - # os.path.join(root, directory_name)) - # pathnames.add(full_path) - self.pathnames = [self.watch.path] - _fsevents.add_watch(self, - self.watch, - callback, - self.pathnames) + _fsevents.add_watch(self, self.watch, self.events_callback, self.pathnames) _fsevents.read_events(self) except Exception: - pass + logger.exception("Unhandled exception in FSEventsEmitter") + + def on_thread_start(self): + if self.suppress_history: + + if isinstance(self.watch.path, bytes): + watch_path = os.fsdecode(self.watch.path) + else: + watch_path = self.watch.path + + self._starting_state = DirectorySnapshot(watch_path) + + def _encode_path(self, path): + """Encode path only if bytes were passed to this emitter. """ + if isinstance(self.watch.path, bytes): + return os.fsencode(path) + return path class FSEventsObserver(BaseObserver): def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): - BaseObserver.__init__(self, emitter_class=FSEventsEmitter, - timeout=timeout) + super().__init__(emitter_class=FSEventsEmitter, timeout=timeout) def schedule(self, event_handler, path, recursive=False): - # Python 2/3 compat - try: - str_class = unicode - except NameError: - str_class = str - # Fix for issue #26: Trace/BPT error when given a unicode path # string. https://github.com/gorakhargosh/watchdog/issues#issue/26 - if isinstance(path, str_class): - # path = unicode(path, 'utf-8') + if isinstance(path, str): path = unicodedata.normalize('NFC', path) - # We only encode the path in Python 2 for backwards compatibility. - # On Python 3 we want the path to stay as unicode if possible for - # the sake of path matching not having to be rewritten to use the - # bytes API instead of strings. The _watchdog_fsevent.so code for - # Python 3 can handle both str and bytes paths, which is why we - # do not HAVE to encode it with Python 3. The Python 2 code in - # _watchdog_fsevents.so was not changed for the sake of backwards - # compatibility. - if sys.version_info < (3,): - path = path.encode('utf-8') return BaseObserver.schedule(self, event_handler, path, recursive) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/fsevents2.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/fsevents2.py index 9f48d7e753..52352123ce 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/fsevents2.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/fsevents2.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2014 Thomas Amland # @@ -17,14 +17,14 @@ """ :module: watchdog.observers.fsevents2 :synopsis: FSEvents based emitter implementation. -:platforms: Mac OS X +:platforms: macOS """ import os import logging +import queue import unicodedata from threading import Thread -from watchdog.utils.compat import queue from watchdog.events import ( FileDeletedEvent, @@ -87,7 +87,7 @@ def __init__(self, path): self._run_loop = None if isinstance(path, bytes): - path = path.decode('utf-8') + path = os.fsdecode(path) self._path = unicodedata.normalize('NFC', path) context = None @@ -97,7 +97,7 @@ def __init__(self, path): kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents) if self._stream_ref is None: - raise IOError("FSEvents. Could not create stream.") + raise OSError("FSEvents. Could not create stream.") def run(self): pool = AppKit.NSAutoreleasePool.alloc().init() @@ -107,7 +107,7 @@ def run(self): if not FSEventStreamStart(self._stream_ref): FSEventStreamInvalidate(self._stream_ref) FSEventStreamRelease(self._stream_ref) - raise IOError("FSEvents. Could not start stream.") + raise OSError("FSEvents. Could not start stream.") CFRunLoopRun() FSEventStreamStop(self._stream_ref) @@ -139,7 +139,7 @@ def read_events(self): return self._queue.get() -class NativeEvent(object): +class NativeEvent: def __init__(self, path, flags, event_id): self.path = path self.flags = flags @@ -183,7 +183,7 @@ class FSEventsEmitter(EventEmitter): """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): - EventEmitter.__init__(self, event_queue, watch, timeout) + super().__init__(event_queue, watch, timeout) self._fsevents = FSEventsQueue(watch.path) self._fsevents.start() @@ -205,7 +205,7 @@ def queue_events(self, timeout): # Internal moves appears to always be consecutive in the same # buffer and have IDs differ by exactly one (while others # don't) making it possible to pair up the two events coming - # from a singe move operation. (None of this is documented!) + # from a single move operation. (None of this is documented!) # Otherwise, guess whether file was moved in or out. # TODO: handle id wrapping if (i + 1 < len(events) and events[i + 1].is_renamed @@ -225,7 +225,7 @@ def queue_events(self, timeout): self.queue_event(DirModifiedEvent(os.path.dirname(event.path))) # TODO: generate events for tree - elif event.is_modified or event.is_inode_meta_mod or event.is_xattr_mod : + elif event.is_modified or event.is_inode_meta_mod or event.is_xattr_mod: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(event.path)) @@ -243,4 +243,4 @@ def queue_events(self, timeout): class FSEventsObserver2(BaseObserver): def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): - BaseObserver.__init__(self, emitter_class=FSEventsEmitter, timeout=timeout) + super().__init__(emitter_class=FSEventsEmitter, timeout=timeout) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify.py index 68ffa66ea0..2918464b8a 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -67,8 +66,6 @@ """ -from __future__ import with_statement - import os import threading from .inotify_buffer import InotifyBuffer @@ -89,10 +86,10 @@ FileModifiedEvent, FileMovedEvent, FileCreatedEvent, + FileClosedEvent, generate_sub_moved_events, generate_sub_created_events, ) -from watchdog.utils import unicode_paths class InotifyEmitter(EventEmitter): @@ -112,17 +109,18 @@ class InotifyEmitter(EventEmitter): """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): - EventEmitter.__init__(self, event_queue, watch, timeout) + super().__init__(event_queue, watch, timeout) self._lock = threading.Lock() self._inotify = None def on_thread_start(self): - path = unicode_paths.encode(self.watch.path) + path = os.fsencode(self.watch.path) self._inotify = InotifyBuffer(path, self.watch.is_recursive) def on_thread_stop(self): if self._inotify: self._inotify.close() + self._inotify = None def queue_events(self, timeout, full_events=False): # If "full_events" is true, then the method will report unmatched move events as separate events @@ -174,12 +172,23 @@ def queue_events(self, timeout, full_events=False): cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) + elif event.is_close_write and not event.is_directory: + cls = FileClosedEvent + self.queue_event(cls(src_path)) + self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) + # elif event.is_close_nowrite and not event.is_directory: + # cls = FileClosedEvent + # self.queue_event(cls(src_path)) + elif event.is_delete_self and src_path == self.watch.path: + cls = DirDeletedEvent if event.is_directory else FileDeletedEvent + self.queue_event(cls(src_path)) + self.stop() def _decode_path(self, path): - """ Decode path only if unicode string was passed to this emitter. """ + """Decode path only if unicode string was passed to this emitter. """ if isinstance(self.watch.path, bytes): return path - return unicode_paths.decode(path) + return os.fsdecode(path) class InotifyFullEmitter(InotifyEmitter): @@ -199,7 +208,7 @@ class InotifyFullEmitter(InotifyEmitter): ``float`` """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): - InotifyEmitter.__init__(self, event_queue, watch, timeout) + super().__init__(event_queue, watch, timeout) def queue_events(self, timeout, events=True): InotifyEmitter.queue_events(self, timeout, full_events=events) @@ -212,8 +221,5 @@ class InotifyObserver(BaseObserver): """ def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT, generate_full_events=False): - if (generate_full_events): - BaseObserver.__init__(self, emitter_class=InotifyFullEmitter, timeout=timeout) - else: - BaseObserver.__init__(self, emitter_class=InotifyEmitter, - timeout=timeout) + cls = InotifyFullEmitter if generate_full_events else InotifyEmitter + super().__init__(emitter_class=cls, timeout=timeout) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify_buffer.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify_buffer.py index de0802f9e2..3213c5c20a 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify_buffer.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify_buffer.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2014 Thomas Amland # @@ -30,7 +30,7 @@ class InotifyBuffer(BaseThread): delay = 0.5 def __init__(self, path, recursive=False): - BaseThread.__init__(self) + super().__init__() self._queue = DelayedQueue(self.delay) self._inotify = Inotify(path, recursive) self.start() @@ -88,6 +88,13 @@ def run(self): inotify_events = self._inotify.read_events() grouped_events = self._group_events(inotify_events) for inotify_event in grouped_events: + if not isinstance(inotify_event, tuple) and inotify_event.is_ignored: + if inotify_event.src_path == self._inotify.path: + # Watch was removed explicitly (inotify_rm_watch(2)) or automatically (file + # was deleted, or filesystem was unmounted), stop watching for events + deleted_self = True + continue + # Only add delay for unmatched move_from events delay = not isinstance(inotify_event, tuple) and inotify_event.is_moved_from self._queue.put(inotify_event, delay) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify_c.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify_c.py index 0402243567..d942d2a0d6 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify_c.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/inotify_c.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement import os import errno import struct @@ -24,47 +23,13 @@ import ctypes.util from functools import reduce from ctypes import c_int, c_char_p, c_uint32 -from watchdog.utils import has_attribute from watchdog.utils import UnsupportedLibc -from watchdog.utils.unicode_paths import decode +libc = ctypes.CDLL(None) -def _load_libc(): - libc_path = None - try: - libc_path = ctypes.util.find_library('c') - except (OSError, IOError, RuntimeError): - # Note: find_library will on some platforms raise these undocumented - # errors, e.g.on android IOError "No usable temporary directory found" - # will be raised. - pass - - if libc_path is not None: - return ctypes.CDLL(libc_path) - - # Fallbacks - try: - return ctypes.CDLL('libc.so') - except (OSError, IOError): - pass - - try: - return ctypes.CDLL('libc.so.6') - except (OSError, IOError): - pass - - # uClibc - try: - return ctypes.CDLL('libc.so.0') - except (OSError, IOError) as err: - raise err - - -libc = _load_libc() - -if not has_attribute(libc, 'inotify_init') or \ - not has_attribute(libc, 'inotify_add_watch') or \ - not has_attribute(libc, 'inotify_rm_watch'): +if not hasattr(libc, 'inotify_init') or \ + not hasattr(libc, 'inotify_add_watch') or \ + not hasattr(libc, 'inotify_rm_watch'): raise UnsupportedLibc("Unsupported libc version found: %s" % libc._name) inotify_add_watch = ctypes.CFUNCTYPE(c_int, c_int, c_char_p, c_uint32, use_errno=True)( @@ -77,7 +42,7 @@ def _load_libc(): ("inotify_init", libc)) -class InotifyConstants(object): +class InotifyConstants: # User-space events IN_ACCESS = 0x00000001 # File was accessed. IN_MODIFY = 0x00000002 # File was modified. @@ -142,6 +107,7 @@ class InotifyConstants(object): InotifyConstants.IN_DELETE, InotifyConstants.IN_DELETE_SELF, InotifyConstants.IN_DONT_FOLLOW, + InotifyConstants.IN_CLOSE_WRITE, ]) @@ -170,7 +136,7 @@ class inotify_event_struct(ctypes.Structure): DEFAULT_EVENT_BUFFER_SIZE = DEFAULT_NUM_EVENTS * (EVENT_SIZE + 16) -class Inotify(object): +class Inotify: """ Linux inotify(7) API wrapper class. @@ -277,7 +243,12 @@ def close(self): if self._path in self._wd_for_path: wd = self._wd_for_path[self._path] inotify_rm_watch(self._inotify_fd, wd) - os.close(self._inotify_fd) + + try: + os.close(self._inotify_fd) + except OSError: + # descriptor may be invalid because file was deleted + pass def read_events(self, event_buffer_size=DEFAULT_EVENT_BUFFER_SIZE): """ @@ -315,6 +286,10 @@ def _recursive_simulate(src_path): except OSError as e: if e.errno == errno.EINTR: continue + elif e.errno == errno.EBADF: + return [] + else: + raise break with self._lock: @@ -350,7 +325,6 @@ def _recursive_simulate(src_path): path = self._path_for_wd.pop(wd) if self._wd_for_path[path] == wd: del self._wd_for_path[path] - continue event_list.append(inotify_event) @@ -457,7 +431,7 @@ def _parse_event_buffer(event_buffer): yield wd, mask, cookie, name -class InotifyEvent(object): +class InotifyEvent: """ Inotify event struct wrapper. @@ -568,7 +542,7 @@ def __eq__(self, inotify_event): return self.key == inotify_event.key def __ne__(self, inotify_event): - return self.key == inotify_event.key + return self.key != inotify_event.key def __hash__(self): return hash(self.key) @@ -588,4 +562,4 @@ def __repr__(self): mask_string = self._get_mask_string(self.mask) s = '<%s: src_path=%r, wd=%d, mask=%s, cookie=%d, name=%s>' return s % (type(self).__name__, self.src_path, self.wd, mask_string, - self.cookie, decode(self.name)) + self.cookie, os.fsdecode(self.name)) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/kqueue.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/kqueue.py index 553e4bee64..54c0e284ab 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/kqueue.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/kqueue.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,7 +19,8 @@ :module: watchdog.observers.kqueue :synopsis: ``kqueue(2)`` based emitter implementation. :author: yesudeep@google.com (Yesudeep Mangalapilly) -:platforms: Mac OS X and BSD with kqueue(2). +:author: contact@tiger-222.fr (Mickaël Schoentgen) +:platforms: macOS and BSD with kqueue(2). .. WARNING:: kqueue is a very heavyweight way to monitor file systems. Each kqueue-detected directory modification triggers @@ -32,7 +32,7 @@ .. ADMONITION:: About OS X performance guidelines - Quote from the `Mac OS X File System Performance Guidelines`_: + Quote from the `macOS File System Performance Guidelines`_: "When you only want to track changes on a file or directory, be sure to open it using the ``O_EVTONLY`` flag. This flag prevents the file or @@ -62,12 +62,11 @@ :members: :show-inheritance: -.. _Mac OS X File System Performance Guidelines: +.. _macOS File System Performance Guidelines: http://developer.apple.com/library/ios/#documentation/Performance/Conceptual/FileSystem/Articles/TrackingChanges.html#//apple_ref/doc/uid/20001993-CJBJFIDD """ -from __future__ import with_statement from watchdog.utils import platform import threading @@ -77,8 +76,6 @@ import os.path import select -from pathtools.path import absolute_path - from watchdog.observers.api import ( BaseObserver, EventEmitter, @@ -86,7 +83,6 @@ DEFAULT_EMITTER_TIMEOUT ) -from watchdog.utils import stat as default_stat from watchdog.utils.dirsnapshot import DirectorySnapshot from watchdog.events import ( @@ -127,6 +123,10 @@ | select.KQ_NOTE_REVOKE ) + +def absolute_path(path): + return os.path.abspath(os.path.normpath(path)) + # Flag tests. @@ -151,7 +151,7 @@ def is_renamed(kev): return kev.fflags & select.KQ_NOTE_RENAME -class KeventDescriptorSet(object): +class KeventDescriptorSet: """ Thread-safe kevent descriptor collection. @@ -306,7 +306,7 @@ def _remove_descriptor(self, descriptor): descriptor.close() -class KeventDescriptor(object): +class KeventDescriptor: """ A kevent descriptor convenience data structure to keep together: @@ -431,8 +431,8 @@ class KqueueEmitter(EventEmitter): """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT, - stat=default_stat): - EventEmitter.__init__(self, event_queue, watch, timeout) + stat=os.stat): + super().__init__(event_queue, watch, timeout) self._kq = select.kqueue() self._lock = threading.RLock() @@ -703,4 +703,4 @@ class KqueueObserver(BaseObserver): """ def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): - BaseObserver.__init__(self, emitter_class=KqueueEmitter, timeout=timeout) + super().__init__(emitter_class=KqueueEmitter, timeout=timeout) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/polling.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/polling.py index 46ad99f5f9..e7ea2aa209 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/polling.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/polling.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,6 +20,7 @@ :module: watchdog.observers.polling :synopsis: Polling emitter implementation. :author: yesudeep@google.com (Yesudeep Mangalapilly) +:author: contact@tiger-222.fr (Mickaël Schoentgen) Classes ------- @@ -34,10 +34,10 @@ :special-members: """ -from __future__ import with_statement +import os import threading from functools import partial -from watchdog.utils import stat as default_stat + from watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff from watchdog.observers.api import ( EventEmitter, @@ -57,11 +57,6 @@ FileModifiedEvent ) -try: - from os import scandir -except ImportError: - from os import listdir as scandir - class PollingEmitter(EventEmitter): """ @@ -70,8 +65,8 @@ class PollingEmitter(EventEmitter): """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT, - stat=default_stat, listdir=scandir): - EventEmitter.__init__(self, event_queue, watch, timeout) + stat=os.stat, listdir=os.scandir): + super().__init__(event_queue, watch, timeout) self._snapshot = None self._lock = threading.Lock() self._take_snapshot = lambda: DirectorySnapshot( @@ -130,7 +125,7 @@ class PollingObserver(BaseObserver): """ def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): - BaseObserver.__init__(self, emitter_class=PollingEmitter, timeout=timeout) + super().__init__(emitter_class=PollingEmitter, timeout=timeout) class PollingObserverVFS(BaseObserver): @@ -141,9 +136,9 @@ class PollingObserverVFS(BaseObserver): def __init__(self, stat, listdir, polling_interval=1): """ :param stat: stat function. See ``os.stat`` for details. - :param listdir: listdir function. See ``os.listdir`` for details. + :param listdir: listdir function. See ``os.scandir`` for details. :type polling_interval: float :param polling_interval: interval in seconds between polling the file system. """ emitter_cls = partial(PollingEmitter, stat=stat, listdir=listdir) - BaseObserver.__init__(self, emitter_class=emitter_cls, timeout=polling_interval) + super().__init__(emitter_class=emitter_cls, timeout=polling_interval) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/read_directory_changes.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/read_directory_changes.py index 42a1e53804..6846cb839b 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/read_directory_changes.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/read_directory_changes.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,9 +19,11 @@ import threading import os.path import time +import platform from watchdog.events import ( DirCreatedEvent, + DirDeletedEvent, DirMovedEvent, DirModifiedEvent, FileCreatedEvent, @@ -58,13 +59,19 @@ class WindowsApiEmitter(EventEmitter): """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): - EventEmitter.__init__(self, event_queue, watch, timeout) + super().__init__(event_queue, watch, timeout) self._lock = threading.Lock() self._handle = None def on_thread_start(self): self._handle = get_directory_handle(self.watch.path) + if platform.python_implementation() == 'PyPy': + def start(self): + """PyPy needs some time before receiving events, see #792.""" + super().start() + time.sleep(0.01) + def on_thread_stop(self): if self._handle: close_directory_handle(self._handle) @@ -122,6 +129,7 @@ def queue_events(self, timeout): elif winapi_event.is_removed: self.queue_event(FileDeletedEvent(src_path)) elif winapi_event.is_removed_self: + self.queue_event(DirDeletedEvent(self.watch.path)) self.stop() @@ -132,5 +140,4 @@ class WindowsApiObserver(BaseObserver): """ def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): - BaseObserver.__init__(self, emitter_class=WindowsApiEmitter, - timeout=timeout) + super().__init__(emitter_class=WindowsApiEmitter, timeout=timeout) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/winapi.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/winapi.py index 65ab1c3f03..a20a0dd182 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/winapi.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/observers/winapi.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # winapi.py: Windows API-Python interface (removes dependency on pywin32) # # Copyright (C) 2007 Thomas Heller @@ -264,7 +263,17 @@ class FILE_NOTIFY_INFORMATION(ctypes.Structure): FILE_NOTIFY_CHANGE_CREATION, ]) -BUFFER_SIZE = 2048 +# ReadDirectoryChangesW buffer length. +# To handle cases with lot of changes, this seems the highest safest value we can use. +# Note: it will fail with ERROR_INVALID_PARAMETER when it is greater than 64 KB and +# the application is monitoring a directory over the network. +# This is due to a packet size limitation with the underlying file sharing protocols. +# https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw#remarks +BUFFER_SIZE = 64000 + +# Buffer length for path-related stuff. +# Introduced to keep the old behavior when we bumped BUFFER_SIZE from 2048 to 64000 in v1.0.0. +PATH_BUFFER_SIZE = 2048 def _parse_event_buffer(readBuffer, nBytes): @@ -287,8 +296,8 @@ def _is_observed_path_deleted(handle, path): # Comparison of observed path and actual path, returned by # GetFinalPathNameByHandleW. If directory moved to the trash bin, or # deleted, actual path will not be equal to observed path. - buff = ctypes.create_unicode_buffer(BUFFER_SIZE) - GetFinalPathNameByHandleW(handle, buff, BUFFER_SIZE, VOLUME_NAME_NT) + buff = ctypes.create_unicode_buffer(PATH_BUFFER_SIZE) + GetFinalPathNameByHandleW(handle, buff, PATH_BUFFER_SIZE, VOLUME_NAME_NT) return buff.value != path @@ -297,7 +306,7 @@ def _generate_observed_path_deleted_event(): path = ctypes.create_unicode_buffer('.') event = FILE_NOTIFY_INFORMATION(0, FILE_ACTION_DELETED_SELF, len(path), path.value.encode("utf-8")) event_size = ctypes.sizeof(event) - buff = ctypes.create_string_buffer(BUFFER_SIZE) + buff = ctypes.create_string_buffer(PATH_BUFFER_SIZE) ctypes.memmove(buff, ctypes.addressof(event), event_size) return buff, event_size @@ -312,7 +321,7 @@ def close_directory_handle(handle): try: CancelIoEx(handle, None) # force ReadDirectoryChangesW to return CloseHandle(handle) # close directory handle - except WindowsError: + except OSError: try: CloseHandle(handle) # close directory handle except Exception: @@ -331,7 +340,7 @@ def read_directory_changes(handle, path, recursive): len(event_buffer), recursive, WATCHDOG_FILE_NOTIFY_FLAGS, ctypes.byref(nbytes), None, None) - except WindowsError as e: + except OSError as e: if e.winerror == ERROR_OPERATION_ABORTED: return [], 0 @@ -341,15 +350,10 @@ def read_directory_changes(handle, path, recursive): raise e - # Python 2/3 compat - try: - int_class = long - except NameError: - int_class = int - return event_buffer.raw, int_class(nbytes.value) + return event_buffer.raw, int(nbytes.value) -class WinAPINativeEvent(object): +class WinAPINativeEvent: def __init__(self, action, src_path): self.action = action self.src_path = src_path diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/tricks/__init__.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/tricks/__init__.py index a9dd20fce0..6e7eaf62da 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/tricks/__init__.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/tricks/__init__.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,14 +15,45 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +:module: watchdog.tricks +:synopsis: Utility event handlers. +:author: yesudeep@google.com (Yesudeep Mangalapilly) +:author: contact@tiger-222.fr (Mickaël Schoentgen) + +Classes +------- +.. autoclass:: Trick + :members: + :show-inheritance: + +.. autoclass:: LoggerTrick + :members: + :show-inheritance: + +.. autoclass:: ShellCommandTrick + :members: + :show-inheritance: + +.. autoclass:: AutoRestartTrick + :members: + :show-inheritance: +""" + +import functools +import logging import os import signal -from robocorp_ls_core.subprocess_wrapper import subprocess +import subprocess import time -from watchdog.utils import echo, has_attribute from watchdog.events import PatternMatchingEventHandler +from watchdog.utils import echo +from watchdog.utils.process_watcher import ProcessWatcher + +logger = logging.getLogger(__name__) +echo_events = functools.partial(echo.echo, write=lambda msg: logger.info(msg)) class Trick(PatternMatchingEventHandler): @@ -56,19 +86,19 @@ class LoggerTrick(Trick): def on_any_event(self, event): pass - @echo.echo + @echo_events def on_modified(self, event): pass - @echo.echo + @echo_events def on_deleted(self, event): pass - @echo.echo + @echo_events def on_created(self, event): pass - @echo.echo + @echo_events def on_moved(self, event): pass @@ -80,17 +110,20 @@ class ShellCommandTrick(Trick): def __init__(self, shell_command=None, patterns=None, ignore_patterns=None, ignore_directories=False, wait_for_process=False, drop_during_process=False): - super(ShellCommandTrick, self).__init__(patterns, ignore_patterns, - ignore_directories) + super().__init__( + patterns=patterns, ignore_patterns=ignore_patterns, + ignore_directories=ignore_directories) self.shell_command = shell_command self.wait_for_process = wait_for_process self.drop_during_process = drop_during_process + self.process = None + self._process_watchers = set() def on_any_event(self, event): from string import Template - if self.drop_during_process and self.process and self.process.poll() is None: + if self.drop_during_process and self.is_process_running(): return if event.is_directory: @@ -106,13 +139,13 @@ def on_any_event(self, event): } if self.shell_command is None: - if has_attribute(event, 'dest_path'): + if hasattr(event, 'dest_path'): context.update({'dest_path': event.dest_path}) command = 'echo "${watch_event_type} ${watch_object} from ${watch_src_path} to ${watch_dest_path}"' else: command = 'echo "${watch_event_type} ${watch_object} ${watch_src_path}"' else: - if has_attribute(event, 'dest_path'): + if hasattr(event, 'dest_path'): context.update({'watch_dest_path': event.dest_path}) command = self.shell_command @@ -120,6 +153,15 @@ def on_any_event(self, event): self.process = subprocess.Popen(command, shell=True) if self.wait_for_process: self.process.wait() + else: + process_watcher = ProcessWatcher(self.process, None) + self._process_watchers.add(process_watcher) + process_watcher.process_termination_callback = \ + functools.partial(self._process_watchers.discard, process_watcher) + process_watcher.start() + + def is_process_running(self): + return self._process_watchers or (self.process is not None and self.process.poll() is None) class AutoRestartTrick(Trick): @@ -127,30 +169,47 @@ class AutoRestartTrick(Trick): """Starts a long-running subprocess and restarts it on matched events. The command parameter is a list of command arguments, such as - ['bin/myserver', '-c', 'etc/myconfig.ini']. + `['bin/myserver', '-c', 'etc/myconfig.ini']`. - Call start() after creating the Trick. Call stop() when stopping + Call `start()` after creating the Trick. Call `stop()` when stopping the process. """ def __init__(self, command, patterns=None, ignore_patterns=None, ignore_directories=False, stop_signal=signal.SIGINT, kill_after=10): - super(AutoRestartTrick, self).__init__( - patterns, ignore_patterns, ignore_directories) + super().__init__( + patterns=patterns, ignore_patterns=ignore_patterns, + ignore_directories=ignore_directories) self.command = command self.stop_signal = stop_signal self.kill_after = kill_after + self.process = None + self.process_watcher = None def start(self): - self.process = subprocess.Popen(self.command, preexec_fn=os.setsid) + # windows doesn't have setsid + self.process = subprocess.Popen(self.command, preexec_fn=getattr(os, 'setsid', None)) + self.process_watcher = ProcessWatcher(self.process, self._restart) + self.process_watcher.start() def stop(self): if self.process is None: return + + if self.process_watcher is not None: + self.process_watcher.stop() + self.process_watcher = None + + def kill_process(stop_signal): + if hasattr(os, 'getpgid') and hasattr(os, 'killpg'): + os.killpg(os.getpgid(self.process.pid), stop_signal) + else: + os.kill(self.process.pid, self.stop_signal) + try: - os.killpg(os.getpgid(self.process.pid), self.stop_signal) + kill_process(self.stop_signal) except OSError: # Process is already gone pass @@ -162,13 +221,16 @@ def stop(self): time.sleep(0.25) else: try: - os.killpg(os.getpgid(self.process.pid), 9) + kill_process(9) except OSError: # Process is already gone pass self.process = None - @echo.echo + @echo_events def on_any_event(self, event): + self._restart() + + def _restart(self): self.stop() self.start() diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/__init__.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/__init__.py index dfe067dd90..803d510b34 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/__init__.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/__init__.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,6 +20,7 @@ :module: watchdog.utils :synopsis: Utility classes and functions. :author: yesudeep@google.com (Yesudeep Mangalapilly) +:author: contact@tiger-222.fr (Mickaël Schoentgen) Classes ------- @@ -30,35 +30,18 @@ :inherited-members: """ -import os import sys import threading -from watchdog.utils import platform -from watchdog.utils.compat import Event -if sys.version_info[0] == 2 and platform.is_windows(): - # st_ino is not implemented in os.stat on this platform - import win32stat - stat = win32stat.stat -else: - stat = os.stat +class UnsupportedLibc(Exception): + pass -def has_attribute(ob, attribute): +class WatchdogShutdown(Exception): """ - :func:`hasattr` swallows exceptions. :func:`has_attribute` tests a Python object for the - presence of an attribute. - - :param ob: - object to inspect - :param attribute: - ``str`` for the name of the attribute. + Semantic exception used to signal an external shutdown event. """ - return getattr(ob, attribute, None) is not None - - -class UnsupportedLibc(Exception): pass @@ -67,13 +50,13 @@ class BaseThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) - if has_attribute(self, 'daemon'): + if hasattr(self, 'daemon'): self.daemon = True else: self.setDaemon(True) - self._stopped_event = Event() + self._stopped_event = threading.Event() - if not has_attribute(self._stopped_event, 'is_set'): + if not hasattr(self._stopped_event, 'is_set'): self._stopped_event.is_set = self._stopped_event.isSet @property @@ -144,7 +127,7 @@ def load_class(dotted_path): module_name = '.'.join(dotted_path_split[:-1]) module = load_module(module_name) - if has_attribute(module, klass_name): + if hasattr(module, klass_name): klass = getattr(module, klass_name) return klass # Finally create and return an instance of the class diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/bricks.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/bricks.py index 97c79832b0..0a7e1cd324 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/bricks.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/bricks.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,6 +23,7 @@ :author: yesudeep@google.com (Yesudeep Mangalapilly) :author: lalinsky@gmail.com (Lukáš Lalinský) :author: python@rcn.com (Raymond Hettinger) +:author: contact@tiger-222.fr (Mickaël Schoentgen) Classes ======= @@ -36,10 +36,10 @@ """ -from .compat import queue +import queue -class SkipRepeatsQueue(queue.Queue, object): +class SkipRepeatsQueue(queue.Queue): """Thread-safe implementation of an special queue where a put of the last-item put'd will be dropped. @@ -54,7 +54,7 @@ class SkipRepeatsQueue(queue.Queue, object): An example implementation follows:: - class Item(object): + class Item: def __init__(self, a, b): self._a = a self._b = b @@ -83,12 +83,12 @@ def __hash__(self): """ def _init(self, maxsize): - super(SkipRepeatsQueue, self)._init(maxsize) + super()._init(maxsize) self._last_item = None def _put(self, item): - if item != self._last_item: - super(SkipRepeatsQueue, self)._put(item) + if self._last_item is None or item != self._last_item: + super()._put(item) self._last_item = item else: # `put` increments `unfinished_tasks` even if we did not put @@ -96,7 +96,7 @@ def _put(self, item): self.unfinished_tasks -= 1 def _get(self): - item = super(SkipRepeatsQueue, self)._get() + item = super()._get() if item is self._last_item: self._last_item = None return item diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/compat.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/compat.py deleted file mode 100644 index c6c96d7173..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/compat.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2014 Thomas Amland -# -# 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. - -__all__ = ['queue', 'Event'] - -try: - import queue -except ImportError: - import Queue as queue - - -from threading import Event diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/delayed_queue.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/delayed_queue.py index e5d91de8c7..1187112b77 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/delayed_queue.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/delayed_queue.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2014 Thomas Amland # @@ -19,7 +19,7 @@ from collections import deque -class DelayedQueue(object): +class DelayedQueue: def __init__(self, delay): self.delay_sec = delay diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/dirsnapshot.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/dirsnapshot.py index b252e1b146..8b49969410 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/dirsnapshot.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/dirsnapshot.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +20,7 @@ :module: watchdog.utils.dirsnapshot :synopsis: Directory snapshots and comparison. :author: yesudeep@google.com (Yesudeep Mangalapilly) +:author: contact@tiger-222.fr (Mickaël Schoentgen) .. ADMONITION:: Where are the moved events? They "disappeared" @@ -51,14 +51,9 @@ import errno import os from stat import S_ISDIR -from watchdog.utils import stat as default_stat -try: - from os import scandir -except ImportError: - from os import listdir as scandir -class DirectorySnapshotDiff(object): +class DirectorySnapshotDiff: """ Compares two directory snapshots and creates an object that represents the difference between the two snapshots. @@ -217,7 +212,7 @@ def dirs_created(self): return self._dirs_created -class DirectorySnapshot(object): +class DirectorySnapshot: """ A snapshot of stat information of files in a directory. @@ -237,12 +232,11 @@ class DirectorySnapshot(object): A function taking a ``path`` as argument which will be called for every entry in the directory tree. :param listdir: - Use custom listdir function. For details see ``os.scandir`` if available, else ``os.listdir``. + Use custom listdir function. For details see ``os.scandir``. """ def __init__(self, path, recursive=True, - stat=default_stat, - listdir=scandir): + stat=os.stat, listdir=os.scandir): self.recursive = recursive self.stat = stat self.listdir = listdir @@ -288,12 +282,8 @@ def walk(self, root): if S_ISDIR(st.st_mode): for entry in self.walk(path): yield entry - except (IOError, OSError) as e: - # IOError for Python 2 - # OSError for Python 3 - # (should be only PermissionError when dropping Python 2 support) - if e.errno != errno.EACCES: - raise + except PermissionError: + pass @property def paths(self): @@ -353,7 +343,7 @@ def __repr__(self): return str(self._stat_info) -class EmptyDirectorySnapshot(object): +class EmptyDirectorySnapshot: """Class to implement an empty snapshot. This is used together with DirectorySnapshot and DirectorySnapshotDiff in order to get all the files/folders in the directory as created. diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/echo.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/echo.py index 28b6038eba..e7337207f9 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/echo.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/echo.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # echo.py: Tracing function calls using Python decorators. # # Written by Thomas Guest diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/patterns.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/patterns.py new file mode 100644 index 0000000000..86762c4fdd --- /dev/null +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/patterns.py @@ -0,0 +1,87 @@ +# coding: utf-8 +# patterns.py: Common wildcard searching/filtering functionality for files. +# +# Copyright (C) 2010 Yesudeep Mangalapilly +# +# Written by Boris Staletic + +# Non-pure path objects are only allowed on their respective OS's. +# Thus, these utilities require "pure" path objects that don't access the filesystem. +# Since pathlib doesn't have a `case_sensitive` parameter, we have to approximate it +# by converting input paths to `PureWindowsPath` and `PurePosixPath` where: +# - `PureWindowsPath` is always case-insensitive. +# - `PurePosixPath` is always case-sensitive. +# Reference: https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.match +from pathlib import PureWindowsPath, PurePosixPath + + +def _match_path(path, included_patterns, excluded_patterns, case_sensitive): + """Internal function same as :func:`match_path` but does not check arguments.""" + if case_sensitive: + path = PurePosixPath(path) + else: + included_patterns = {pattern.lower() for pattern in included_patterns} + excluded_patterns = {pattern.lower() for pattern in excluded_patterns} + path = PureWindowsPath(path) + + common_patterns = included_patterns & excluded_patterns + if common_patterns: + raise ValueError('conflicting patterns `{}` included and excluded'.format(common_patterns)) + return (any(path.match(p) for p in included_patterns) + and not any(path.match(p) for p in excluded_patterns)) + + +def filter_paths(paths, included_patterns=None, excluded_patterns=None, case_sensitive=True): + """ + Filters from a set of paths based on acceptable patterns and + ignorable patterns. + :param pathnames: + A list of path names that will be filtered based on matching and + ignored patterns. + :param included_patterns: + Allow filenames matching wildcard patterns specified in this list. + If no pattern list is specified, ["*"] is used as the default pattern, + which matches all files. + :param excluded_patterns: + Ignores filenames matching wildcard patterns specified in this list. + If no pattern list is specified, no files are ignored. + :param case_sensitive: + ``True`` if matching should be case-sensitive; ``False`` otherwise. + :returns: + A list of pathnames that matched the allowable patterns and passed + through the ignored patterns. + """ + included = ["*"] if included_patterns is None else included_patterns + excluded = [] if excluded_patterns is None else excluded_patterns + + for path in paths: + if _match_path(path, set(included), set(excluded), case_sensitive): + yield path + + +def match_any_paths(paths, included_patterns=None, excluded_patterns=None, case_sensitive=True): + """ + Matches from a set of paths based on acceptable patterns and + ignorable patterns. + :param pathnames: + A list of path names that will be filtered based on matching and + ignored patterns. + :param included_patterns: + Allow filenames matching wildcard patterns specified in this list. + If no pattern list is specified, ["*"] is used as the default pattern, + which matches all files. + :param excluded_patterns: + Ignores filenames matching wildcard patterns specified in this list. + If no pattern list is specified, no files are ignored. + :param case_sensitive: + ``True`` if matching should be case-sensitive; ``False`` otherwise. + :returns: + ``True`` if any of the paths matches; ``False`` otherwise. + """ + included = ["*"] if included_patterns is None else included_patterns + excluded = [] if excluded_patterns is None else excluded_patterns + + for path in paths: + if _match_path(path, set(included), set(excluded), case_sensitive): + return True + return False diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/platform.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/platform.py index 55bda3e5fe..47feba8fe9 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/platform.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/platform.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/process_watcher.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/process_watcher.py new file mode 100644 index 0000000000..f195764856 --- /dev/null +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/process_watcher.py @@ -0,0 +1,25 @@ +import logging + +from watchdog.utils import BaseThread + + +logger = logging.getLogger(__name__) + + +class ProcessWatcher(BaseThread): + def __init__(self, popen_obj, process_termination_callback): + super().__init__() + self.popen_obj = popen_obj + self.process_termination_callback = process_termination_callback + + def run(self): + while True: + if self.popen_obj.poll() is not None: + break + if self.stopped_event.wait(timeout=0.1): + return + + try: + self.process_termination_callback() + except Exception: + logger.exception("Error calling process termination callback") diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/unicode_paths.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/unicode_paths.py deleted file mode 100644 index 501a2f1512..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/unicode_paths.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2013 Will Bond -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -import sys - -from watchdog.utils import platform - -try: - # Python 2 - str_cls = unicode - bytes_cls = str -except NameError: - # Python 3 - str_cls = str - bytes_cls = bytes - - -# This is used by Linux when the locale seems to be improperly set. UTF-8 tends -# to be the encoding used by all distros, so this is a good fallback. -fs_fallback_encoding = 'utf-8' -fs_encoding = sys.getfilesystemencoding() or fs_fallback_encoding - - -def encode(path): - if isinstance(path, str_cls): - try: - path = path.encode(fs_encoding, 'strict') - except UnicodeEncodeError: - if not platform.is_linux(): - raise - path = path.encode(fs_fallback_encoding, 'strict') - return path - - -def decode(path): - if isinstance(path, bytes_cls): - try: - path = path.decode(fs_encoding, 'strict') - except UnicodeDecodeError: - if not platform.is_linux(): - raise - path = path.decode(fs_fallback_encoding, 'strict') - return path diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/win32stat.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/win32stat.py deleted file mode 100644 index 41da772b7f..0000000000 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/utils/win32stat.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2014 Thomas Amland -# -# 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: watchdog.utils.win32stat -:synopsis: Implementation of stat with st_ino and st_dev support. - -Functions ---------- - -.. autofunction:: stat - -""" - -import ctypes -import ctypes.wintypes -import stat as stdstat -from collections import namedtuple - - -INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value -OPEN_EXISTING = 3 -FILE_READ_ATTRIBUTES = 0x80 -FILE_ATTRIBUTE_NORMAL = 0x80 -FILE_ATTRIBUTE_READONLY = 0x1 -FILE_ATTRIBUTE_DIRECTORY = 0x10 -FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 -FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 - - -class FILETIME(ctypes.Structure): - _fields_ = [("dwLowDateTime", ctypes.wintypes.DWORD), - ("dwHighDateTime", ctypes.wintypes.DWORD)] - - -class BY_HANDLE_FILE_INFORMATION(ctypes.Structure): - _fields_ = [('dwFileAttributes', ctypes.wintypes.DWORD), - ('ftCreationTime', FILETIME), - ('ftLastAccessTime', FILETIME), - ('ftLastWriteTime', FILETIME), - ('dwVolumeSerialNumber', ctypes.wintypes.DWORD), - ('nFileSizeHigh', ctypes.wintypes.DWORD), - ('nFileSizeLow', ctypes.wintypes.DWORD), - ('nNumberOfLinks', ctypes.wintypes.DWORD), - ('nFileIndexHigh', ctypes.wintypes.DWORD), - ('nFileIndexLow', ctypes.wintypes.DWORD)] - - -kernel32 = ctypes.WinDLL("kernel32") - -CreateFile = kernel32.CreateFileW -CreateFile.restype = ctypes.wintypes.HANDLE -CreateFile.argtypes = ( - ctypes.c_wchar_p, - ctypes.wintypes.DWORD, - ctypes.wintypes.DWORD, - ctypes.c_void_p, - ctypes.wintypes.DWORD, - ctypes.wintypes.DWORD, - ctypes.wintypes.HANDLE, -) - -GetFileInformationByHandle = kernel32.GetFileInformationByHandle -GetFileInformationByHandle.restype = ctypes.wintypes.BOOL -GetFileInformationByHandle.argtypes = ( - ctypes.wintypes.HANDLE, - ctypes.wintypes.POINTER(BY_HANDLE_FILE_INFORMATION), -) - -CloseHandle = kernel32.CloseHandle -CloseHandle.restype = ctypes.wintypes.BOOL -CloseHandle.argtypes = (ctypes.wintypes.HANDLE,) - - -StatResult = namedtuple('StatResult', 'st_dev st_ino st_mode st_mtime st_size') - - -def _to_mode(attr): - m = 0 - if (attr & FILE_ATTRIBUTE_DIRECTORY): - m |= stdstat.S_IFDIR | 0o111 - else: - m |= stdstat.S_IFREG - if (attr & FILE_ATTRIBUTE_READONLY): - m |= 0o444 - else: - m |= 0o666 - return m - - -def _to_unix_time(ft): - t = (ft.dwHighDateTime) << 32 | ft.dwLowDateTime - return (t / 10000000) - 11644473600 - - -def stat(path): - hfile = CreateFile(path, - FILE_READ_ATTRIBUTES, - 0, - None, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL - | FILE_FLAG_BACKUP_SEMANTICS - | FILE_FLAG_OPEN_REPARSE_POINT, - None) - if hfile == INVALID_HANDLE_VALUE: - raise ctypes.WinError() - info = BY_HANDLE_FILE_INFORMATION() - r = GetFileInformationByHandle(hfile, info) - CloseHandle(hfile) - if not r: - raise ctypes.WinError() - return StatResult(st_dev=info.dwVolumeSerialNumber, - st_ino=(info.nFileIndexHigh << 32) + info.nFileIndexLow, - st_mode=_to_mode(info.dwFileAttributes), - st_mtime=_to_unix_time(info.ftLastWriteTime), - st_size=(info.nFileSizeHigh << 32) + info.nFileSizeLow - ) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/version.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/version.py index b25638e0d4..e4ea4178d0 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/version.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/version.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# coding: utf-8 # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,9 +18,9 @@ # When updating this version number, please update the # ``docs/source/global.rst.inc`` file as well. -VERSION_MAJOR = 0 -VERSION_MINOR = 10 -VERSION_BUILD = 3 +VERSION_MAJOR = 2 +VERSION_MINOR = 1 +VERSION_BUILD = 9 VERSION_INFO = (VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD) VERSION_STRING = "%d.%d.%d" % VERSION_INFO diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/watchmedo.py b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/watchmedo.py index da893d9b31..20d0f77203 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/watchmedo.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/libs/watchdog_lib/watchdog/watchmedo.py @@ -1,8 +1,6 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly -# Copyright 2012 Google, Inc. +# Copyright 2012 Google, Inc & contributors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,27 +17,22 @@ """ :module: watchdog.watchmedo :author: yesudeep@google.com (Yesudeep Mangalapilly) +:author: contact@tiger-222.fr (Mickaël Schoentgen) :synopsis: ``watchmedo`` shell script utility. """ +import errno +import logging +import os import os.path import sys -import yaml import time -import logging - -try: - from cStringIO import StringIO -except ImportError: - try: - from StringIO import StringIO - except ImportError: - from io import StringIO +from argparse import ArgumentParser, RawDescriptionHelpFormatter +from io import StringIO +from textwrap import dedent -from argh import arg, aliases, ArghParser, expects_obj +from watchdog.utils import WatchdogShutdown, load_class from watchdog.version import VERSION_STRING -from watchdog.utils import load_class - logging.basicConfig(level=logging.INFO) @@ -47,6 +40,75 @@ CONFIG_KEY_PYTHON_PATH = 'python-path' +class HelpFormatter(RawDescriptionHelpFormatter): + """A nicer help formatter. + + Help for arguments can be indented and contain new lines. + It will be de-dented and arguments in the help + will be separated by a blank line for better readability. + + Source: https://github.com/httpie/httpie/blob/2423f89/httpie/cli/argparser.py#L31 + """ + + def __init__(self, *args, max_help_position=6, **kwargs): + # A smaller indent for args help. + kwargs['max_help_position'] = max_help_position + super().__init__(*args, **kwargs) + + def _split_lines(self, text, width): + text = dedent(text).strip() + '\n\n' + return text.splitlines() + + +epilog = '''\ +Copyright 2011 Yesudeep Mangalapilly . +Copyright 2012 Google, Inc & contributors. + +Licensed under the terms of the Apache license, version 2.0. Please see +LICENSE in the source code for more information.''' + +cli = ArgumentParser(epilog=epilog, formatter_class=HelpFormatter) +cli.add_argument('--version', action='version', version=VERSION_STRING) +subparsers = cli.add_subparsers(dest='top_command') +command_parsers = {} + + +def argument(*name_or_flags, **kwargs): + """Convenience function to properly format arguments to pass to the + command decorator. + """ + return list(name_or_flags), kwargs + + +def command(args=[], parent=subparsers, cmd_aliases=[]): + """Decorator to define a new command in a sanity-preserving way. + The function will be stored in the ``func`` variable when the parser + parses arguments so that it can be called directly like so:: + + >>> args = cli.parse_args() + >>> args.func(args) + + """ + def decorator(func): + name = func.__name__.replace('_', '-') + desc = dedent(func.__doc__) + parser = parent.add_parser(name, + description=desc, + aliases=cmd_aliases, + formatter_class=HelpFormatter) + command_parsers[name] = parser + verbosity_group = parser.add_mutually_exclusive_group() + verbosity_group.add_argument('-q', '--quiet', dest='verbosity', + action='append_const', const=-1) + verbosity_group.add_argument('-v', '--verbose', dest='verbosity', + action='append_const', const=1) + for arg in args: + parser.add_argument(*arg[0], **arg[1]) + parser.set_defaults(func=func) + return func + return decorator + + def path_split(pathname_spec, separator=os.pathsep): """ Splits a pathname specification separated by an OS-dependent separator. @@ -82,6 +144,8 @@ def load_config(tricks_file_pathname): :returns: A dictionary of configuration information. """ + import yaml + with open(tricks_file_pathname, 'rb') as f: return yaml.safe_load(f.read()) @@ -117,7 +181,7 @@ def observe_with(observer, event_handler, pathnames, recursive): try: while True: time.sleep(1) - except KeyboardInterrupt: + except WatchdogShutdown: observer.stop() observer.join() @@ -144,30 +208,57 @@ def schedule_tricks(observer, tricks, pathname, recursive): observer.schedule(handler, trick_pathname, recursive) -@aliases('tricks') -@arg('files', - nargs='*', - help='perform tricks from given file') -@arg('--python-path', - default='.', - help='paths separated by %s to add to the python path' % os.pathsep) -@arg('--interval', - '--timeout', - dest='timeout', - default=1.0, - help='use this as the polling interval/blocking timeout (in seconds)') -@arg('--recursive', - default=True, - help='recursively monitor paths') -@expects_obj +@command([argument('files', + nargs='*', + help='perform tricks from given file'), + argument('--python-path', + default='.', + help=f'Paths separated by {os.pathsep!r} to add to the Python path.'), + argument('--interval', + '--timeout', + dest='timeout', + default=1.0, + type=float, + help='Use this as the polling interval/blocking timeout (in seconds).'), + argument('--recursive', + action='store_true', + default=True, + help='Recursively monitor paths (defaults to True).'), + argument('--debug-force-polling', + action='store_true', + help='[debug] Forces polling.'), + argument('--debug-force-kqueue', + action='store_true', + help='[debug] Forces BSD kqueue(2).'), + argument('--debug-force-winapi', + action='store_true', + help='[debug] Forces Windows API.'), + argument('--debug-force-fsevents', + action='store_true', + help='[debug] Forces macOS FSEvents.'), + argument('--debug-force-inotify', + action='store_true', + help='[debug] Forces Linux inotify(7).')], + cmd_aliases=['tricks']) def tricks_from(args): """ - Subcommand to execute tricks from a tricks configuration file. - - :param args: - Command line argument options. + Command to execute tricks from a tricks configuration file. """ - from watchdog.observers import Observer + if args.debug_force_polling: + from watchdog.observers.polling import PollingObserver as Observer + elif args.debug_force_kqueue: + from watchdog.observers.kqueue import KqueueObserver as Observer + elif args.debug_force_winapi: + from watchdog.observers.read_directory_changes import\ + WindowsApiObserver as Observer + elif args.debug_force_inotify: + from watchdog.observers.inotify import InotifyObserver as Observer + elif args.debug_force_fsevents: + from watchdog.observers.fsevents import FSEventsObserver as Observer + else: + # Automatically picks the most appropriate observer for the platform + # on which it is running. + from watchdog.observers import Observer add_to_sys_path(path_split(args.python_path)) observers = [] @@ -175,14 +266,14 @@ def tricks_from(args): observer = Observer(timeout=args.timeout) if not os.path.exists(tricks_file): - raise IOError("cannot find tricks file: %s" % tricks_file) + raise OSError(errno.ENOENT, os.strerror(errno.ENOENT), tricks_file) config = load_config(tricks_file) try: tricks = config[CONFIG_KEY_TRICKS] except KeyError: - raise KeyError("No `%s' key specified in %s." % ( + raise KeyError("No %r key specified in %s." % ( CONFIG_KEY_TRICKS, tricks_file)) if CONFIG_KEY_PYTHON_PATH in config: @@ -198,7 +289,7 @@ def tricks_from(args): try: while True: time.sleep(1) - except KeyboardInterrupt: + except WatchdogShutdown: for o in observers: o.unschedule_all() o.stop() @@ -206,32 +297,31 @@ def tricks_from(args): o.join() -@aliases('generate-tricks-yaml') -@arg('trick_paths', - nargs='*', - help='Dotted paths for all the tricks you want to generate') -@arg('--python-path', - default='.', - help='paths separated by %s to add to the python path' % os.pathsep) -@arg('--append-to-file', - default=None, - help='appends the generated tricks YAML to a file; \ -if not specified, prints to standard output') -@arg('-a', - '--append-only', - dest='append_only', - default=False, - help='if --append-to-file is not specified, produces output for \ -appending instead of a complete tricks yaml file.') -@expects_obj +@command([argument('trick_paths', + nargs='*', + help='Dotted paths for all the tricks you want to generate.'), + argument('--python-path', + default='.', + help=f'Paths separated by {os.pathsep!r} to add to the Python path.'), + argument('--append-to-file', + default=None, + help=''' + Appends the generated tricks YAML to a file. + If not specified, prints to standard output.'''), + argument('-a', + '--append-only', + dest='append_only', + action='store_true', + help=''' + If --append-to-file is not specified, produces output for + appending instead of a complete tricks YAML file.''')], + cmd_aliases=['generate-tricks-yaml']) def tricks_generate_yaml(args): """ - Subcommand to generate Yaml configuration for tricks named on the command - line. - - :param args: - Command line argument options. + Command to generate Yaml configuration for tricks named on the command line. """ + import yaml + python_paths = path_split(args.python_path) add_to_sys_path(python_paths) output = StringIO() @@ -257,71 +347,66 @@ def tricks_generate_yaml(args): output.write(content) -@arg('directories', - nargs='*', - default='.', - help='directories to watch.') -@arg('-p', - '--pattern', - '--patterns', - dest='patterns', - default='*', - help='matches event paths with these patterns (separated by ;).') -@arg('-i', - '--ignore-pattern', - '--ignore-patterns', - dest='ignore_patterns', - default='', - help='ignores event paths with these patterns (separated by ;).') -@arg('-D', - '--ignore-directories', - dest='ignore_directories', - default=False, - help='ignores events for directories') -@arg('-R', - '--recursive', - dest='recursive', - default=False, - help='monitors the directories recursively') -@arg('--interval', - '--timeout', - dest='timeout', - default=1.0, - help='use this as the polling interval/blocking timeout') -@arg('--trace', - default=False, - help='dumps complete dispatching trace') -@arg('--debug-force-polling', - default=False, - help='[debug] forces polling') -@arg('--debug-force-kqueue', - default=False, - help='[debug] forces BSD kqueue(2)') -@arg('--debug-force-winapi', - default=False, - help='[debug] forces Windows API') -@arg('--debug-force-winapi-async', - default=False, - help='[debug] forces Windows API + I/O completion') -@arg('--debug-force-fsevents', - default=False, - help='[debug] forces Mac OS X FSEvents') -@arg('--debug-force-inotify', - default=False, - help='[debug] forces Linux inotify(7)') -@expects_obj +@command([argument('directories', + nargs='*', + default='.', + help='Directories to watch. (default: \'.\').'), + argument('-p', + '--pattern', + '--patterns', + dest='patterns', + default='*', + help='Matches event paths with these patterns (separated by ;).'), + argument('-i', + '--ignore-pattern', + '--ignore-patterns', + dest='ignore_patterns', + default='', + help='Ignores event paths with these patterns (separated by ;).'), + argument('-D', + '--ignore-directories', + dest='ignore_directories', + action='store_true', + help='Ignores events for directories.'), + argument('-R', + '--recursive', + dest='recursive', + action='store_true', + help='Monitors the directories recursively.'), + argument('--interval', + '--timeout', + dest='timeout', + default=1.0, + type=float, + help='Use this as the polling interval/blocking timeout.'), + argument('--trace', + action='store_true', + help='Dumps complete dispatching trace.'), + argument('--debug-force-polling', + action='store_true', + help='[debug] Forces polling.'), + argument('--debug-force-kqueue', + action='store_true', + help='[debug] Forces BSD kqueue(2).'), + argument('--debug-force-winapi', + action='store_true', + help='[debug] Forces Windows API.'), + argument('--debug-force-fsevents', + action='store_true', + help='[debug] Forces macOS FSEvents.'), + argument('--debug-force-inotify', + action='store_true', + help='[debug] Forces Linux inotify(7).')]) def log(args): """ - Subcommand to log file system events to the console. - - :param args: - Command line argument options. + Command to log file system events to the console. """ from watchdog.utils import echo from watchdog.tricks import LoggerTrick if args.trace: - echo.echo_class(LoggerTrick) + class_module_logger = logging.getLogger(LoggerTrick.__module__) + echo.echo_class(LoggerTrick, write=lambda msg: class_module_logger.info(msg)) patterns, ignore_patterns =\ parse_patterns(args.patterns, args.ignore_patterns) @@ -332,9 +417,6 @@ def log(args): from watchdog.observers.polling import PollingObserver as Observer elif args.debug_force_kqueue: from watchdog.observers.kqueue import KqueueObserver as Observer - elif args.debug_force_winapi_async: - from watchdog.observers.read_directory_changes_async import\ - WindowsApiAsyncObserver as Observer elif args.debug_force_winapi: from watchdog.observers.read_directory_changes import\ WindowsApiObserver as Observer @@ -350,80 +432,77 @@ def log(args): observe_with(observer, handler, args.directories, args.recursive) -@arg('directories', - nargs='*', - default='.', - help='directories to watch') -@arg('-c', - '--command', - dest='command', - default=None, - help='''shell command executed in response to matching events. -These interpolation variables are available to your command string:: - - ${watch_src_path} - event source path; - ${watch_dest_path} - event destination path (for moved events); - ${watch_event_type} - event type; - ${watch_object} - ``file`` or ``directory`` - -Note:: - Please ensure you do not use double quotes (") to quote - your command string. That will force your shell to - interpolate before the command is processed by this - subcommand. - -Example option usage:: - - --command='echo "${watch_src_path}"' -''') -@arg('-p', - '--pattern', - '--patterns', - dest='patterns', - default='*', - help='matches event paths with these patterns (separated by ;).') -@arg('-i', - '--ignore-pattern', - '--ignore-patterns', - dest='ignore_patterns', - default='', - help='ignores event paths with these patterns (separated by ;).') -@arg('-D', - '--ignore-directories', - dest='ignore_directories', - default=False, - help='ignores events for directories') -@arg('-R', - '--recursive', - dest='recursive', - default=False, - help='monitors the directories recursively') -@arg('--interval', - '--timeout', - dest='timeout', - default=1.0, - help='use this as the polling interval/blocking timeout') -@arg('-w', '--wait', - dest='wait_for_process', - action='store_true', - default=False, - help="wait for process to finish to avoid multiple simultaneous instances") -@arg('-W', '--drop', - dest='drop_during_process', - action='store_true', - default=False, - help="Ignore events that occur while command is still being executed " - "to avoid multiple simultaneous instances") -@arg('--debug-force-polling', - default=False, - help='[debug] forces polling') -@expects_obj +@command([argument('directories', + nargs='*', + default='.', + help='Directories to watch.'), + argument('-c', + '--command', + dest='command', + default=None, + help=''' + Shell command executed in response to matching events. + These interpolation variables are available to your command string: + + ${watch_src_path} - event source path + ${watch_dest_path} - event destination path (for moved events) + ${watch_event_type} - event type + ${watch_object} - 'file' or 'directory' + + Note: + Please ensure you do not use double quotes (") to quote + your command string. That will force your shell to + interpolate before the command is processed by this + command. + + Example: + + --command='echo "${watch_src_path}"' + '''), + argument('-p', + '--pattern', + '--patterns', + dest='patterns', + default='*', + help='Matches event paths with these patterns (separated by ;).'), + argument('-i', + '--ignore-pattern', + '--ignore-patterns', + dest='ignore_patterns', + default='', + help='Ignores event paths with these patterns (separated by ;).'), + argument('-D', + '--ignore-directories', + dest='ignore_directories', + default=False, + action='store_true', + help='Ignores events for directories.'), + argument('-R', + '--recursive', + dest='recursive', + action='store_true', + help='Monitors the directories recursively.'), + argument('--interval', + '--timeout', + dest='timeout', + default=1.0, + type=float, + help='Use this as the polling interval/blocking timeout.'), + argument('-w', '--wait', + dest='wait_for_process', + action='store_true', + help='Wait for process to finish to avoid multiple simultaneous instances.'), + argument('-W', '--drop', + dest='drop_during_process', + action='store_true', + help='Ignore events that occur while command is still being' + ' executed to avoid multiple simultaneous instances.'), + argument('--debug-force-polling', + action='store_true', + help='[debug] Forces polling.')]) def shell_command(args): """ - Subcommand to execute shell commands in response to file system events. - - :param args: - Command line argument options. + Command to execute shell commands in response to file system events. """ from watchdog.tricks import ShellCommandTrick @@ -447,71 +526,69 @@ def shell_command(args): observe_with(observer, handler, args.directories, args.recursive) -@arg('command', - help='''Long-running command to run in a subprocess. -''') -@arg('command_args', - metavar='arg', - nargs='*', - help='''Command arguments. - -Note: Use -- before the command arguments, otherwise watchmedo will -try to interpret them. -''') -@arg('-d', - '--directory', - dest='directories', - metavar='directory', - action='append', - help='Directory to watch. Use another -d or --directory option ' - 'for each directory.') -@arg('-p', - '--pattern', - '--patterns', - dest='patterns', - default='*', - help='matches event paths with these patterns (separated by ;).') -@arg('-i', - '--ignore-pattern', - '--ignore-patterns', - dest='ignore_patterns', - default='', - help='ignores event paths with these patterns (separated by ;).') -@arg('-D', - '--ignore-directories', - dest='ignore_directories', - default=False, - help='ignores events for directories') -@arg('-R', - '--recursive', - dest='recursive', - default=False, - help='monitors the directories recursively') -@arg('--interval', - '--timeout', - dest='timeout', - default=1.0, - help='use this as the polling interval/blocking timeout') -@arg('--signal', - dest='signal', - default='SIGINT', - help='stop the subprocess with this signal (default SIGINT)') -@arg('--debug-force-polling', - default=False, - help='[debug] forces polling') -@arg('--kill-after', - dest='kill_after', - default=10.0, - help='when stopping, kill the subprocess after the specified timeout ' - '(default 10)') -@expects_obj +@command([argument('command', + help='Long-running command to run in a subprocess.'), + argument('command_args', + metavar='arg', + nargs='*', + help=''' + Command arguments. + + Note: Use -- before the command arguments, otherwise watchmedo will + try to interpret them. + '''), + argument('-d', + '--directory', + dest='directories', + metavar='DIRECTORY', + action='append', + help='Directory to watch. Use another -d or --directory option ' + 'for each directory.'), + argument('-p', + '--pattern', + '--patterns', + dest='patterns', + default='*', + help='Matches event paths with these patterns (separated by ;).'), + argument('-i', + '--ignore-pattern', + '--ignore-patterns', + dest='ignore_patterns', + default='', + help='Ignores event paths with these patterns (separated by ;).'), + argument('-D', + '--ignore-directories', + dest='ignore_directories', + default=False, + action='store_true', + help='Ignores events for directories.'), + argument('-R', + '--recursive', + dest='recursive', + action='store_true', + help='Monitors the directories recursively.'), + argument('--interval', + '--timeout', + dest='timeout', + default=1.0, + type=float, + help='Use this as the polling interval/blocking timeout.'), + argument('--signal', + dest='signal', + default='SIGINT', + help='Stop the subprocess with this signal (default SIGINT).'), + argument('--debug-force-polling', + action='store_true', + help='[debug] Forces polling.'), + argument('--kill-after', + dest='kill_after', + default=10.0, + type=float, + help='When stopping, kill the subprocess after the specified timeout ' + 'in seconds (default 10.0).')]) def auto_restart(args): """ - Subcommand to start a long-running subprocess and restart it - on matched events. - - :param args: - Command line argument options. + Command to start a long-running subprocess and restart it on matched events. """ if args.debug_force_polling: @@ -531,12 +608,18 @@ def auto_restart(args): else: stop_signal = int(args.signal) - # Handle SIGTERM in the same manner as SIGINT so that - # this program has a chance to stop the child process. - def handle_sigterm(_signum, _frame): - raise KeyboardInterrupt() + # Handle termination signals by raising a semantic exception which will + # allow us to gracefully unwind and stop the observer + termination_signals = {signal.SIGTERM, signal.SIGINT} + + def handler_termination_signal(_signum, _frame): + # Neuter all signals so that we don't attempt a double shutdown + for signum in termination_signals: + signal.signal(signum, signal.SIG_IGN) + raise WatchdogShutdown - signal.signal(signal.SIGTERM, handle_sigterm) + for signum in termination_signals: + signal.signal(signum, handler_termination_signal) patterns, ignore_patterns = parse_patterns(args.patterns, args.ignore_patterns) @@ -550,31 +633,45 @@ def handle_sigterm(_signum, _frame): kill_after=args.kill_after) handler.start() observer = Observer(timeout=args.timeout) - observe_with(observer, handler, args.directories, args.recursive) - handler.stop() + try: + observe_with(observer, handler, args.directories, args.recursive) + except WatchdogShutdown: + pass + finally: + handler.stop() -epilog = """Copyright 2011 Yesudeep Mangalapilly . -Copyright 2012 Google, Inc. +class LogLevelException(Exception): + pass -Licensed under the terms of the Apache license, version 2.0. Please see -LICENSE in the source code for more information.""" -parser = ArghParser(epilog=epilog) -parser.add_commands([tricks_from, - tricks_generate_yaml, - log, - shell_command, - auto_restart]) -parser.add_argument('--version', - action='version', - version='%(prog)s ' + VERSION_STRING) +def _get_log_level_from_args(args): + verbosity = sum(args.verbosity or []) + if verbosity < -1: + raise LogLevelException("-q/--quiet may be specified only once.") + if verbosity > 2: + raise LogLevelException("-v/--verbose may be specified up to 2 times.") + return ['ERROR', 'WARNING', 'INFO', 'DEBUG'][1 + verbosity] def main(): """Entry-point function.""" - parser.dispatch() + args = cli.parse_args() + if args.top_command is None: + cli.print_help() + return 1 + + try: + log_level = _get_log_level_from_args(args) + except LogLevelException as exc: + print("Error: " + exc.args[0], file=sys.stderr) + command_parsers[args.top_command].print_help() + return 1 + logging.getLogger('watchdog').setLevel(log_level) + + args.func(args) + return 0 if __name__ == '__main__': - main() + sys.exit(main()) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/system_mutex.py b/robocorp-python-ls-core/src/robocorp_ls_core/system_mutex.py index f6fe40a65f..8d65d800b9 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/system_mutex.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/system_mutex.py @@ -235,12 +235,13 @@ def release_mutex(*args, **kwargs): handle.close() except Exception: traceback.print_exc() - try: - # Removing is pretty much optional (but let's do it to keep the - # filesystem cleaner). - os.unlink(filename) - except Exception: - pass + # Don't remove it (so that it doesn't get another inode afterwards). + # try: + # # Removing is pretty much optional (but let's do it to keep the + # # filesystem cleaner). + # os.unlink(filename) + # except Exception: + # pass # Don't use __del__: this approach doesn't have as many pitfalls. self._ref = weakref.ref(self, release_mutex) diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/watchdog_wrapper.py b/robocorp-python-ls-core/src/robocorp_ls_core/watchdog_wrapper.py index 4f7c3d2e91..afb5b28974 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/watchdog_wrapper.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/watchdog_wrapper.py @@ -80,6 +80,21 @@ def _get_watchdog_lib_dir(): watchdog_dir = os.path.join(parent_dir, "libs", "watchdog_lib") if not os.path.exists(watchdog_dir): raise RuntimeError("Expected: %s to exist." % (watchdog_dir,)) + + internal = os.path.join(watchdog_dir, "watchdog") + if not os.path.exists(internal): + raise RuntimeError( + "Expected: %s to exist (contents: %s)." + % (internal, os.listdir(watchdog_dir)) + ) + + watchdog_init = os.path.join(internal, "__init__.py") + if not os.path.exists(watchdog_init): + raise RuntimeError( + "Expected: %s to exist (contents: %s)." + % (watchdog_init, os.listdir(internal)) + ) + return watchdog_dir @@ -203,12 +218,22 @@ def __typecheckself__(self) -> None: class _FSNotifyWatchList(object): def __init__(self, new_tracked_paths, new_notifications, observer): - self._new_tracked_paths = new_tracked_paths + """ + :param List[fsnotify.TrackedPath] new_tracked_paths: + + :param Tuple[str, Callback, Tuple[...], bool] new_notifications: + (path.path, on_change, call_args, recursive) + + :param _FSNotifyObserver observer: + """ + import fsnotify + + self._new_tracked_paths: List[fsnotify.TrackedPath] = new_tracked_paths self._new_notifications = new_notifications self._observer = weakref.ref(observer) def stop_tracking(self): - observer = self._observer() + observer: Optional[_FSNotifyObserver] = self._observer() if observer is not None and self._new_tracked_paths: observer._stop_tracking(self._new_tracked_paths, self._new_notifications) self._new_tracked_paths = [] @@ -259,7 +284,7 @@ def __init__(self, extensions: Optional[Tuple[str, ...]]): self._all_paths_to_track: List[fsnotify.TrackedPath] = [] self._lock = threading.Lock() - self._notifications: List[Tuple[str, Any, List[Any]]] = [] + self._notifications: List[Tuple[str, Any, List[Any], bool]] = [] self._was_started = False def dispose(self): @@ -267,15 +292,37 @@ def dispose(self): self._disposed.set() self._watcher.dispose() + _dir_separators: Tuple[str, ...] = ("/",) + if sys.platform == "win32": + _dir_separators = ("\\", "/") + def run(self): log.debug("Started listening on _FSNotifyObserver.") try: while not self._disposed.is_set(): + dir_separators = self._dir_separators for _change, src_path in self._watcher.iter_changes(): - lower = src_path.lower() - for path, on_change, call_args in self._notifications: - if lower.startswith(path.lower()): - on_change(normalize_drive(src_path), *call_args) + src_path_lower = src_path.lower() + for path, on_change, call_args, recursive in self._notifications: + path_lower = path.lower() + if src_path_lower.startswith(path_lower): + if len(src_path_lower) == len(path_lower): + pass + elif src_path_lower[len(path_lower)] in dir_separators: + pass + else: + continue + + if recursive: + on_change(normalize_drive(src_path), *call_args) + else: + remainder = src_path_lower[len(path_lower) + 1 :] + count_dir_sep = 0 + for dir_sep in dir_separators: + count_dir_sep += remainder.count(dir_sep) + if count_dir_sep == 0: + on_change(normalize_drive(src_path), *call_args) + except: log.exception("Error collecting changes in _FSNotifyObserver.") finally: @@ -322,7 +369,9 @@ def on_change_with_extensions(src_path, *args): for path in paths: tracked_path = fsnotify.TrackedPath(path.path, path.recursive) new_paths_to_track.append(tracked_path) - new_notifications.append((path.path, used_on_change, call_args)) + new_notifications.append( + (path.path, used_on_change, call_args, path.recursive) + ) self._notifications.extend(new_notifications) self._all_paths_to_track.extend(new_paths_to_track) @@ -344,14 +393,22 @@ def __typecheckself__(self) -> None: class _WatchdogWatchList(object): - def __init__(self, watches, observer): + def __init__(self, watches, observer, info_to_count): self.watches = watches + self._info_to_count = info_to_count self._observer = weakref.ref(observer) def stop_tracking(self): observer = self._observer() if observer is not None: for watch in self.watches: + count = self._info_to_count[watch.key] + if count > 1: + self._info_to_count[watch.key] = count - 1 + continue + else: + del self._info_to_count[watch.key] + try: observer.unschedule(watch) except Exception: @@ -371,6 +428,7 @@ def __init__(self, extensions=None): self._observer = Observer() self._started = False self._extensions = extensions + self._info_to_count = {} def dispose(self): self._observer.stop() @@ -424,6 +482,13 @@ def __init__( FileSystemEventHandler.__init__(self) def on_any_event(self, event): + # with open("c:/temp/out.txt", "a+") as stream: + # stream.write(f"Event src: {event.src_path}\n") + # try: + # stream.write(f"Event dest: {event.dest_path}\n") + # except: + # pass + if extensions is not None: for ext in extensions: if event.src_path.endswith(ext): @@ -442,15 +507,26 @@ def on_any_event(self, event): handler = _Handler() watches = [] + for path_info in paths: - watches.append( - self._observer.schedule( - handler, path_info.path, recursive=path_info.recursive - ) + # with open("c:/temp/out.txt", "a+") as stream: + # stream.write( + # f"schedule: {path_info.path}, recursive: {path_info.recursive}\n" + # ) + + watch = self._observer.schedule( + handler, path_info.path, recursive=path_info.recursive ) + key = watch.key + if key not in self._info_to_count: + self._info_to_count[key] = 1 + else: + self._info_to_count[key] = self._info_to_count[key] + 1 + + watches.append(watch) self._start() - return _WatchdogWatchList(watches, self._observer) + return _WatchdogWatchList(watches, self._observer, self._info_to_count) def __typecheckself__(self) -> None: from robocorp_ls_core.protocols import check_implements diff --git a/robocorp-python-ls-core/src/robocorp_ls_core/workspace.py b/robocorp-python-ls-core/src/robocorp_ls_core/workspace.py index 9f59158562..5cd756c12a 100644 --- a/robocorp-python-ls-core/src/robocorp_ls_core/workspace.py +++ b/robocorp-python-ls-core/src/robocorp_ls_core/workspace.py @@ -319,7 +319,7 @@ def __init__( root_uri: str, fs_observer: IFSObserver, workspace_folders: Optional[List[IWorkspaceFolder]] = None, - track_file_extensions=(".robot", ".resource"), + track_file_extensions=(".robot", ".resource", ".py", ".yml", ".yaml"), ) -> None: from robocorp_ls_core.lsp import WorkspaceFolder from robocorp_ls_core.callbacks import Callback diff --git a/robocorp-python-ls-core/tests/robocorp_ls_core_tests/pyls_jsonrpc_tests/test_endpoint.py b/robocorp-python-ls-core/tests/robocorp_ls_core_tests/pyls_jsonrpc_tests/test_endpoint.py index eca17ade03..c09368df12 100644 --- a/robocorp-python-ls-core/tests/robocorp_ls_core_tests/pyls_jsonrpc_tests/test_endpoint.py +++ b/robocorp-python-ls-core/tests/robocorp_ls_core_tests/pyls_jsonrpc_tests/test_endpoint.py @@ -350,10 +350,10 @@ def check_result_or_cancelled(): def test_log_slow_calls(endpoint, dispatcher, consumer, monkeypatch): from robocorp_ls_core.robotframework_log import configure_logger - monkeypatch.setattr(endpoint, "SHOW_THREAD_DUMP_AFTER_TIMEOUT", 0.5) + monkeypatch.setattr(endpoint, "SHOW_THREAD_DUMP_AFTER_TIMEOUT", 0.4) def async_slow_handler(): - time.sleep(1) + time.sleep(1.5) return 1234 s = io.StringIO() @@ -380,7 +380,7 @@ def check_result(): await_assertion(check_result, timeout=3) - assert "time.sleep(1)" in s.getvalue() + assert "time.sleep(1.5)" in s.getvalue() assert "in async_slow_handler" in s.getvalue() diff --git a/robocorp-python-ls-core/tests/robocorp_ls_core_tests/test_remote_fs_observer.py b/robocorp-python-ls-core/tests/robocorp_ls_core_tests/test_remote_fs_observer.py index 936a8c86eb..3123e898bc 100644 --- a/robocorp-python-ls-core/tests/robocorp_ls_core_tests/test_remote_fs_observer.py +++ b/robocorp-python-ls-core/tests/robocorp_ls_core_tests/test_remote_fs_observer.py @@ -69,6 +69,117 @@ def check1(): observer.dispose() +def test_remote_fs_observer_conflicts(remote_fs_observer, tmpdir): + from robocorp_ls_core import watchdog_wrapper + from robocorp_ls_core.watchdog_wrapper import PathInfo + from robocorp_ls_core.unittest_tools.fixtures import wait_for_test_condition + from robocorp_ls_core.watchdog_wrapper import IFSObserver + import os + + tmpdir.join("dir").mkdir() + + found_not_recursive = [] + found_recursive = [] + found_recursive2 = [] + + def on_change_not_recursive(filepath, *args): + found_not_recursive.append(os.path.basename(filepath)) + + def on_change_recursive(filepath, *args): + found_recursive.append(os.path.basename(filepath)) + + def on_change_recursive2(filepath, *args): + found_recursive2.append(os.path.basename(filepath)) + + notifier_not_recursive = watchdog_wrapper.create_notifier( + on_change_not_recursive, timeout=0.1 + ) + notifier_recursive = watchdog_wrapper.create_notifier( + on_change_recursive, timeout=0.1 + ) + notifier_recursive2 = watchdog_wrapper.create_notifier( + on_change_recursive2, timeout=0.1 + ) + + observer: IFSObserver = remote_fs_observer + + watch_not_recursive = observer.notify_on_any_change( + [ + PathInfo(tmpdir.join("dir"), False), + ], + notifier_not_recursive.on_change, + ) + watch_recursive = observer.notify_on_any_change( + [ + PathInfo(tmpdir.join("dir"), True), + ], + notifier_recursive.on_change, + ) + watch_recursive2 = observer.notify_on_any_change( + [ + PathInfo(tmpdir.join("dir"), True), + ], + notifier_recursive2.on_change, + ) + + # This should be almost a no-op. + observer.notify_on_any_change( + [ + PathInfo(tmpdir.join("dir"), True), + ], + lambda *args, **kwargs: None, + ).stop_tracking() + + # Sleep a bit because things may take a bit of time to be setup in the + # other process. + import time + + time.sleep(2) + try: + tmpdir.join("dir").join("mya.txt").write("foo") + tmpdir.join("dir").join("dir_inside").mkdir() + tmpdir.join("dir").join("dir_inside").join("myb.txt").write("bar") + + def check_recursive(): + expected = {"mya.txt", "myb.txt"} + return set(found_recursive).issuperset(expected) + + wait_for_test_condition( + check_recursive, + msg=lambda: f"Basenames found: {found_recursive}", + ) + + def check_recursive2(): + expected = {"mya.txt", "myb.txt"} + return set(found_recursive2).issuperset(expected) + + wait_for_test_condition( + check_recursive2, + msg=lambda: f"Basenames found: {found_recursive2}", + ) + + def check_not_recursive(): + expected = {"mya.txt"} + return ( + set(found_not_recursive).issuperset(expected) + and "myb.txt" not in found_not_recursive + ) + + wait_for_test_condition( + check_not_recursive, + msg=lambda: f"Basenames found: {found_not_recursive}", + ) + + finally: + watch_not_recursive.stop_tracking() + watch_recursive.stop_tracking() + watch_recursive2.stop_tracking() + notifier_not_recursive.dispose() + notifier_recursive.dispose() + notifier_recursive2.dispose() + observer.dispose() + + def test_glob_matches_path(): from robocorp_ls_core.load_ignored_dirs import glob_matches_path import sys diff --git a/robocorp-python-ls-core/tests/robocorp_ls_core_tests/test_watchdog_wrapper.py b/robocorp-python-ls-core/tests/robocorp_ls_core_tests/test_watchdog_wrapper.py index 5ea3eacbcc..a45015a419 100644 --- a/robocorp-python-ls-core/tests/robocorp_ls_core_tests/test_watchdog_wrapper.py +++ b/robocorp-python-ls-core/tests/robocorp_ls_core_tests/test_watchdog_wrapper.py @@ -16,19 +16,19 @@ def test_watchdog_macos(): assert os.path.exists( os.path.join( watchdog_wrapper._get_watchdog_lib_dir(), - "_watchdog_fsevents.cpython-37m-darwin.so", + "_watchdog_fsevents.cpython-38-darwin.so", ) ) assert os.path.exists( os.path.join( watchdog_wrapper._get_watchdog_lib_dir(), - "_watchdog_fsevents.cpython-38-darwin.so", + "_watchdog_fsevents.cpython-39-darwin.so", ) ) assert os.path.exists( os.path.join( watchdog_wrapper._get_watchdog_lib_dir(), - "_watchdog_fsevents.cpython-39-darwin.so", + "_watchdog_fsevents.cpython-310-darwin.so", ) ) @@ -93,6 +93,79 @@ def on_change(filepath, *args): observer.dispose() +@pytest.mark.parametrize("backend", ["watchdog", "fsnotify"]) +def test_watchdog_conflicts(tmpdir, backend): + from robocorp_ls_core import watchdog_wrapper + from robocorp_ls_core.watchdog_wrapper import PathInfo + from robocorp_ls_core.unittest_tools.fixtures import wait_for_test_condition + import os + + dir_rec = tmpdir.join("dir_rec") + dir_rec.mkdir() + + found = set() + + def on_change(filepath, *args): + found.add(os.path.basename(filepath)) + + found2 = set() + + def on_change2(filepath, *args): + found2.add(os.path.basename(filepath)) + + observer = watchdog_wrapper.create_observer(backend, None) + + notifier = watchdog_wrapper.create_notifier(on_change, timeout=0.1) + notifier2 = watchdog_wrapper.create_notifier(on_change2, timeout=0.1) + + watch = observer.notify_on_any_change( + [ + PathInfo(dir_rec, True), + ], + notifier.on_change, + ) + watch2 = observer.notify_on_any_change( + [ + PathInfo(dir_rec, False), + ], + notifier2.on_change, + ) + + # It can take a bit of time for listeners to be setup + time.sleep(2) + + try: + dir_rec.join("mya.txt").write("foo") + dir_rec.join("inner").mkdir() + dir_rec.join("inner").join("myb.txt").write("bar") + + def collect_basenames(): + return found + + def check1(): + expected = {"mya.txt", "myb.txt"} + return collect_basenames().issuperset(expected) + + wait_for_test_condition( + check1, + msg=lambda: f"Basenames found: {collect_basenames()}", + ) + + def check2(): + return found2.issuperset({"mya.txt"}) and "myb.txt" not in found2 + + wait_for_test_condition( + check2, + msg=lambda: f"Basenames found: {found2}", + ) + finally: + watch.stop_tracking() + watch2.stop_tracking() + notifier.dispose() + notifier2.dispose() + observer.dispose() + + @pytest.mark.parametrize("backend", ["watchdog", "fsnotify"]) def test_watchdog_all(tmpdir, backend): from robocorp_ls_core import watchdog_wrapper diff --git a/robotframework-ls/src/robotframework_ls/impl/auto_import_completions.py b/robotframework-ls/src/robotframework_ls/impl/auto_import_completions.py index d9531e68b4..0482b26b81 100644 --- a/robotframework-ls/src/robotframework_ls/impl/auto_import_completions.py +++ b/robotframework-ls/src/robotframework_ls/impl/auto_import_completions.py @@ -17,7 +17,7 @@ from robocorp_ls_core import uris from robocorp_ls_core.protocols import IWorkspace from robotframework_ls.impl.protocols import ISymbolsCache -from robotframework_ls.impl.robot_constants import ALL_RELATED_FILE_EXTENSIONS +from robotframework_ls.impl.robot_constants import ALL_KEYWORD_RELATED_FILE_EXTENSIONS class _Collector(object): @@ -107,7 +107,7 @@ def _create_completion_item( check = lib_import or resource_path if check: basename = os.path.basename(check) - if basename.endswith(ALL_RELATED_FILE_EXTENSIONS): + if basename.endswith(ALL_KEYWORD_RELATED_FILE_EXTENSIONS): basename = os.path.splitext(basename)[0] text = f"{basename}.{keyword_name}" diff --git a/robotframework-ls/src/robotframework_ls/impl/completion_context_workspace_caches.py b/robotframework-ls/src/robotframework_ls/impl/completion_context_workspace_caches.py index 01bb485489..b4765581e7 100644 --- a/robotframework-ls/src/robotframework_ls/impl/completion_context_workspace_caches.py +++ b/robotframework-ls/src/robotframework_ls/impl/completion_context_workspace_caches.py @@ -11,6 +11,7 @@ from robotframework_ls.impl.robot_constants import ( ROBOT_AND_TXT_FILE_EXTENSIONS, LIBRARY_FILE_EXTENSIONS, + VARIABLE_FILE_EXTENSIONS, ) from contextlib import contextmanager from robocorp_ls_core.options import BaseOptions @@ -157,6 +158,9 @@ def on_file_changed(self, filename: str): # which files at this level. self.clear_caches() + elif lower.endswith(VARIABLE_FILE_EXTENSIONS): + self.clear_caches() + def on_updated_document(self, uri: str, document: Optional[IRobotDocument]): """ Called when a (robot) document was updated in-memory. diff --git a/robotframework-ls/src/robotframework_ls/impl/robot_constants.py b/robotframework-ls/src/robotframework_ls/impl/robot_constants.py index fd79282851..485569ef54 100644 --- a/robotframework-ls/src/robotframework_ls/impl/robot_constants.py +++ b/robotframework-ls/src/robotframework_ls/impl/robot_constants.py @@ -11,7 +11,13 @@ ROBOT_FILE_EXTENSIONS = (".robot", ".resource") ROBOT_AND_TXT_FILE_EXTENSIONS = ROBOT_FILE_EXTENSIONS + (".txt",) LIBRARY_FILE_EXTENSIONS = (".py",) -ALL_RELATED_FILE_EXTENSIONS = ROBOT_AND_TXT_FILE_EXTENSIONS + LIBRARY_FILE_EXTENSIONS + +# Extensions which may have keywords +ALL_KEYWORD_RELATED_FILE_EXTENSIONS = ( + ROBOT_AND_TXT_FILE_EXTENSIONS + LIBRARY_FILE_EXTENSIONS +) + +VARIABLE_FILE_EXTENSIONS = (".yaml", ".yml") # From: robot.variables.scopes.GlobalVariables._set_built_in_variables # (var name and description) diff --git a/robotframework-ls/tests/robotframework_ls_tests/test_code_analysis.py b/robotframework-ls/tests/robotframework_ls_tests/test_code_analysis.py index 347dbd44d6..809fcb53c3 100644 --- a/robotframework-ls/tests/robotframework_ls_tests/test_code_analysis.py +++ b/robotframework-ls/tests/robotframework_ls_tests/test_code_analysis.py @@ -2224,3 +2224,45 @@ def test_variables_curdir(workspace, libspec_manager, data_regression): doc = workspace.get_doc("case_vars_curdir.robot", accept_from_file=True) _collect_errors(workspace, doc, data_regression, basename="no_error") + + +def test_resolve_caches(workspace, libspec_manager, data_regression): + import os + import pathlib + import threading + + workspace.set_root("case_vars_file", libspec_manager=libspec_manager) + doc = workspace.put_doc("case_vars_curdir.robot") + + doc.source = """ +*** Settings *** +Variables ${CURDIR}${/}not there now${/}common variables.yaml + + +*** Test Cases *** +Test + Log ${COMMON_2} console=True +""" + + _collect_errors(workspace, doc, data_regression) + event = threading.Event() + + def on_file_changed(src_path): + if src_path.lower().endswith(".yaml"): + event.set() + + workspace.ws.on_file_changed.register(on_file_changed) + + path = os.path.dirname(doc.path) + p = pathlib.Path(os.path.join(path, "not there now", "common variables.yaml")) + p.parent.mkdir() + + p.write_text( + """ +COMMON_1: 10 +COMMON_2: 20 +""", + encoding="utf-8", + ) + assert event.wait(5) + _collect_errors(workspace, doc, data_regression, basename="no_error") diff --git a/robotframework-ls/tests/robotframework_ls_tests/test_code_analysis/test_resolve_caches.yml b/robotframework-ls/tests/robotframework_ls_tests/test_code_analysis/test_resolve_caches.yml new file mode 100644 index 0000000000..7fe912d7fe --- /dev/null +++ b/robotframework-ls/tests/robotframework_ls_tests/test_code_analysis/test_resolve_caches.yml @@ -0,0 +1,20 @@ +- message: 'Unresolved variable import: ${CURDIR}${/}not there now${/}common variables.yaml' + range: + end: + character: 71 + line: 2 + start: + character: 20 + line: 2 + severity: 1 + source: robotframework +- message: 'Undefined variable: COMMON_2' + range: + end: + character: 21 + line: 7 + start: + character: 13 + line: 7 + severity: 1 + source: robotframework