unctools 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unctools/__init__.py +100 -0
- unctools/converter.py +378 -0
- unctools/detector.py +531 -0
- unctools/operations.py +562 -0
- unctools/utils/__init__.py +40 -0
- unctools/utils/compat.py +331 -0
- unctools/utils/logger.py +228 -0
- unctools/utils/validation.py +321 -0
- unctools/windows/__init__.py +45 -0
- unctools/windows/network.py +490 -0
- unctools/windows/registry.py +410 -0
- unctools/windows/security.py +586 -0
- unctools-0.1.0.dist-info/METADATA +189 -0
- unctools-0.1.0.dist-info/RECORD +17 -0
- unctools-0.1.0.dist-info/WHEEL +5 -0
- unctools-0.1.0.dist-info/licenses/LICENSE +21 -0
- unctools-0.1.0.dist-info/top_level.txt +1 -0
unctools/__init__.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
UNCtools - A comprehensive toolkit for handling UNC paths, network drives, and substituted drives.
|
|
3
|
+
|
|
4
|
+
This package provides utilities for working with UNC paths (\\server\share) and network/substituted
|
|
5
|
+
drives across different operating systems, with special support for Windows environments.
|
|
6
|
+
|
|
7
|
+
Basic Usage:
|
|
8
|
+
from unctools import convert_to_local, convert_to_unc
|
|
9
|
+
|
|
10
|
+
# Convert UNC path to local drive path
|
|
11
|
+
local_path = convert_to_local("\\\\server\\share\\folder\\file.txt")
|
|
12
|
+
|
|
13
|
+
# Convert local drive path back to UNC
|
|
14
|
+
unc_path = convert_to_unc("Z:\\folder\\file.txt")
|
|
15
|
+
|
|
16
|
+
# Detect path types
|
|
17
|
+
from unctools import is_unc_path, is_network_drive
|
|
18
|
+
|
|
19
|
+
if is_unc_path(path):
|
|
20
|
+
print("This is a UNC path")
|
|
21
|
+
|
|
22
|
+
if is_network_drive("Z:"):
|
|
23
|
+
print("Z: is a network drive")
|
|
24
|
+
|
|
25
|
+
Advanced Windows functionality:
|
|
26
|
+
from unctools.windows import fix_security_zone, get_network_mappings
|
|
27
|
+
|
|
28
|
+
# Fix Windows security zone for a server
|
|
29
|
+
fix_security_zone("server")
|
|
30
|
+
|
|
31
|
+
# Get all network drive mappings
|
|
32
|
+
mappings = get_network_mappings()
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
__version__ = "0.1.0"
|
|
36
|
+
|
|
37
|
+
import os
|
|
38
|
+
import sys
|
|
39
|
+
import logging
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
# Set up package-level logger
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
# Import core functionality into the main namespace
|
|
46
|
+
from .converter import convert_to_local, convert_to_unc, normalize_path
|
|
47
|
+
from .detector import (
|
|
48
|
+
is_unc_path, is_network_drive, is_subst_drive,
|
|
49
|
+
get_path_type, get_network_mappings, detect_path_issues
|
|
50
|
+
)
|
|
51
|
+
from .operations import (
|
|
52
|
+
safe_open, safe_copy, batch_convert, batch_copy,
|
|
53
|
+
process_files, file_exists, replace_in_file, batch_replace_in_files,
|
|
54
|
+
get_unc_path_elements, build_unc_path, is_path_accessible, find_accessible_path
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Determine if we're running on Windows
|
|
58
|
+
IS_WINDOWS = os.name == 'nt'
|
|
59
|
+
|
|
60
|
+
# Import Windows-specific modules if on Windows
|
|
61
|
+
if IS_WINDOWS:
|
|
62
|
+
try:
|
|
63
|
+
from .windows import fix_security_zone, add_to_intranet_zone
|
|
64
|
+
except ImportError as e:
|
|
65
|
+
logger.warning(f"Windows-specific modules could not be imported: {e}")
|
|
66
|
+
else:
|
|
67
|
+
# Define stub functions for non-Windows platforms
|
|
68
|
+
def fix_security_zone(server_name):
|
|
69
|
+
"""Stub function for non-Windows platforms."""
|
|
70
|
+
logger.warning("fix_security_zone is only available on Windows")
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def add_to_intranet_zone(server_name):
|
|
74
|
+
"""Stub function for non-Windows platforms."""
|
|
75
|
+
logger.warning("add_to_intranet_zone is only available on Windows")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
# Configure default logging
|
|
79
|
+
def configure_logging(level=logging.INFO, handler=None):
|
|
80
|
+
"""
|
|
81
|
+
Configure the package's logging settings.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
level: The logging level (default: logging.INFO)
|
|
85
|
+
handler: A logging handler to use (default: StreamHandler)
|
|
86
|
+
"""
|
|
87
|
+
if handler is None:
|
|
88
|
+
handler = logging.StreamHandler()
|
|
89
|
+
|
|
90
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
91
|
+
handler.setFormatter(formatter)
|
|
92
|
+
|
|
93
|
+
logger = logging.getLogger(__name__)
|
|
94
|
+
logger.setLevel(level)
|
|
95
|
+
logger.addHandler(handler)
|
|
96
|
+
|
|
97
|
+
# Version information
|
|
98
|
+
def get_version():
|
|
99
|
+
"""Return the package version."""
|
|
100
|
+
return __version__
|
unctools/converter.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Path conversion utilities for UNC paths, network drives, and substituted drives.
|
|
3
|
+
|
|
4
|
+
This module provides functions to convert between UNC paths (\\server\share) and
|
|
5
|
+
local drive paths, as well as normalization and validation functions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import logging
|
|
11
|
+
import subprocess
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, Union, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
# Set up module-level logger
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Define if we're running on Windows
|
|
19
|
+
IS_WINDOWS = os.name == 'nt'
|
|
20
|
+
|
|
21
|
+
# Global flag for win32net availability
|
|
22
|
+
HAVE_WIN32NET = False
|
|
23
|
+
|
|
24
|
+
# Only try to import Windows-specific modules if we're on Windows
|
|
25
|
+
if IS_WINDOWS:
|
|
26
|
+
from unctools.utils.compat import is_module_available
|
|
27
|
+
|
|
28
|
+
# Check if win32net is available without importing it
|
|
29
|
+
if is_module_available('win32net'):
|
|
30
|
+
try:
|
|
31
|
+
import win32net
|
|
32
|
+
HAVE_WIN32NET = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
# This should rarely happen since we checked for availability
|
|
35
|
+
logger.debug("win32net module found but failed to import.")
|
|
36
|
+
|
|
37
|
+
# Define constants
|
|
38
|
+
UNC_PATTERN = re.compile(r'^\\\\([^\\]+)\\([^\\]+)(?:\\(.*))?$')
|
|
39
|
+
DRIVE_LETTER_PATTERN = re.compile(r'^([A-Za-z]:)(?:\\(.*))?$')
|
|
40
|
+
|
|
41
|
+
class UNCConverter:
|
|
42
|
+
r"""
|
|
43
|
+
Handles conversion between UNC paths and mapped drive paths.
|
|
44
|
+
|
|
45
|
+
This class provides methods to convert UNC paths (\\server\share) to their
|
|
46
|
+
corresponding drive paths (X:\) and vice versa, based on the system's
|
|
47
|
+
network mappings.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, refresh_on_init=True):
|
|
51
|
+
"""
|
|
52
|
+
Initialize the UNC converter.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
refresh_on_init: Whether to refresh network mappings on initialization.
|
|
56
|
+
Default is True.
|
|
57
|
+
"""
|
|
58
|
+
self._mapping: Dict[str, str] = {} # UNC prefix -> drive letter
|
|
59
|
+
self._reverse_mapping: Dict[str, str] = {} # drive letter -> UNC prefix
|
|
60
|
+
|
|
61
|
+
# Windows network share command is only available on Windows
|
|
62
|
+
self._is_windows = IS_WINDOWS
|
|
63
|
+
|
|
64
|
+
if refresh_on_init:
|
|
65
|
+
self.refresh_mappings()
|
|
66
|
+
|
|
67
|
+
def refresh_mappings(self) -> Dict[str, str]:
|
|
68
|
+
"""
|
|
69
|
+
Refresh the mapping of UNC paths to drive letters by querying the system.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
A dictionary mapping UNC prefixes to drive letters.
|
|
73
|
+
"""
|
|
74
|
+
if not self._is_windows:
|
|
75
|
+
logger.debug("Not running on Windows, no network mappings to refresh")
|
|
76
|
+
return {}
|
|
77
|
+
|
|
78
|
+
old_mapping = self._mapping.copy()
|
|
79
|
+
self._mapping.clear()
|
|
80
|
+
self._reverse_mapping.clear()
|
|
81
|
+
|
|
82
|
+
# Try to use win32net if available
|
|
83
|
+
if HAVE_WIN32NET:
|
|
84
|
+
success = self._get_mappings_with_win32net()
|
|
85
|
+
if not success:
|
|
86
|
+
# Fall back to subprocess
|
|
87
|
+
self._get_mappings_with_subprocess()
|
|
88
|
+
else:
|
|
89
|
+
# Use subprocess method
|
|
90
|
+
self._get_mappings_with_subprocess()
|
|
91
|
+
|
|
92
|
+
# Check if mappings changed
|
|
93
|
+
if self._mapping != old_mapping:
|
|
94
|
+
logger.debug(f"Network mappings changed: {len(self._mapping)} mappings")
|
|
95
|
+
|
|
96
|
+
return self._mapping
|
|
97
|
+
|
|
98
|
+
def _get_mappings_with_win32net(self) -> bool:
|
|
99
|
+
"""
|
|
100
|
+
Get network mappings using the win32net API.
|
|
101
|
+
|
|
102
|
+
This method populates the internal mapping dictionaries.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
True if successful, False otherwise.
|
|
106
|
+
"""
|
|
107
|
+
if not HAVE_WIN32NET:
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
# Import here to ensure it's only imported when needed
|
|
112
|
+
import win32net
|
|
113
|
+
|
|
114
|
+
# Level 2 provides detailed information
|
|
115
|
+
connections, total, resume = win32net.NetUseEnum(None, 2)
|
|
116
|
+
|
|
117
|
+
for conn in connections:
|
|
118
|
+
local = conn.get('local', '').upper()
|
|
119
|
+
remote = conn.get('remote', '').lower()
|
|
120
|
+
|
|
121
|
+
if local and remote:
|
|
122
|
+
# Ensure drive letter has trailing backslash
|
|
123
|
+
if local and not local.endswith('\\'):
|
|
124
|
+
local += '\\'
|
|
125
|
+
|
|
126
|
+
# Store UNC path without trailing backslash as key
|
|
127
|
+
remote = remote.rstrip('\\')
|
|
128
|
+
|
|
129
|
+
self._mapping[remote] = local
|
|
130
|
+
self._reverse_mapping[local.rstrip('\\')] = remote
|
|
131
|
+
|
|
132
|
+
logger.debug(f"Retrieved {len(self._mapping)} network mappings using win32net")
|
|
133
|
+
return True
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.warning(f"Error in win32net.NetUseEnum: {e}")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def _get_mappings_with_subprocess(self) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Get network mappings by parsing 'net use' command output.
|
|
141
|
+
|
|
142
|
+
This method populates the internal mapping dictionaries.
|
|
143
|
+
"""
|
|
144
|
+
if not self._is_windows:
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
output = subprocess.check_output(["net", "use"], text=True, stderr=subprocess.STDOUT)
|
|
149
|
+
|
|
150
|
+
# Parse output and extract mappings
|
|
151
|
+
for line in output.splitlines():
|
|
152
|
+
# Look for lines like: "OK Z: \\server\share"
|
|
153
|
+
m = re.search(r'^(OK|Disconnected)\s+([A-Za-z]:)\s+(\\\\\S+)', line, re.IGNORECASE)
|
|
154
|
+
if m:
|
|
155
|
+
drive_letter = m.group(2).upper()
|
|
156
|
+
if not drive_letter.endswith('\\'):
|
|
157
|
+
drive_letter += '\\'
|
|
158
|
+
|
|
159
|
+
unc_path = m.group(3).lower().rstrip('\\')
|
|
160
|
+
|
|
161
|
+
self._mapping[unc_path] = drive_letter
|
|
162
|
+
self._reverse_mapping[drive_letter.rstrip('\\')] = unc_path
|
|
163
|
+
|
|
164
|
+
logger.debug(f"Retrieved {len(self._mapping)} network mappings using 'net use'")
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.warning(f"Failed to get network mappings using 'net use': {e}")
|
|
167
|
+
|
|
168
|
+
def convert_to_local(self, path: Union[str, Path]) -> Path:
|
|
169
|
+
"""
|
|
170
|
+
Convert a UNC path to its corresponding local drive path if possible.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
path: The path to convert, potentially a UNC path.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Path: The converted path using a drive letter if a mapping exists,
|
|
177
|
+
otherwise the original path.
|
|
178
|
+
"""
|
|
179
|
+
path_str = str(path).replace('/', '\\')
|
|
180
|
+
|
|
181
|
+
# If the path already has a drive letter, return it unchanged
|
|
182
|
+
if re.match(r'^[A-Za-z]:', path_str):
|
|
183
|
+
return Path(path_str)
|
|
184
|
+
|
|
185
|
+
# Check if it's a UNC path (starts with \\)
|
|
186
|
+
if not path_str.startswith('\\\\'):
|
|
187
|
+
return Path(path_str)
|
|
188
|
+
|
|
189
|
+
# Try to match the UNC path with known mappings
|
|
190
|
+
# Sort keys by length in descending order to match the most specific first
|
|
191
|
+
for unc_prefix in sorted(self._mapping.keys(), key=len, reverse=True):
|
|
192
|
+
if path_str.lower().startswith(unc_prefix.lower()):
|
|
193
|
+
# Replace the UNC prefix with the drive letter
|
|
194
|
+
local_part = path_str[len(unc_prefix):]
|
|
195
|
+
drive_path = f"{self._mapping[unc_prefix]}{local_part.lstrip(chr(92))}"
|
|
196
|
+
logger.debug(f"Converted UNC path '{path_str}' to local path '{drive_path}'")
|
|
197
|
+
return Path(drive_path)
|
|
198
|
+
|
|
199
|
+
# No matching mapping found, return the original path
|
|
200
|
+
logger.debug(f"No drive mapping found for UNC path '{path_str}'")
|
|
201
|
+
return Path(path_str)
|
|
202
|
+
|
|
203
|
+
def convert_to_unc(self, path: Union[str, Path]) -> Path:
|
|
204
|
+
"""
|
|
205
|
+
Convert a local drive path to its corresponding UNC path if possible.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
path: The path to convert, potentially using a mapped drive.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Path: The converted UNC path if the drive is mapped to a network share,
|
|
212
|
+
otherwise the original path.
|
|
213
|
+
"""
|
|
214
|
+
path_str = str(path).replace('/', '\\')
|
|
215
|
+
|
|
216
|
+
# Check if the path starts with a drive letter
|
|
217
|
+
match = re.match(r'^([A-Za-z]:[\\]?)', path_str, re.IGNORECASE)
|
|
218
|
+
if not match:
|
|
219
|
+
# Not a drive path, return unchanged
|
|
220
|
+
return Path(path_str)
|
|
221
|
+
|
|
222
|
+
drive = match.group(1).upper()
|
|
223
|
+
if not drive.endswith('\\'):
|
|
224
|
+
drive += '\\'
|
|
225
|
+
|
|
226
|
+
drive_no_slash = drive.rstrip('\\')
|
|
227
|
+
|
|
228
|
+
# Check if the drive is in our mapping
|
|
229
|
+
if drive_no_slash in self._reverse_mapping:
|
|
230
|
+
unc_prefix = self._reverse_mapping[drive_no_slash]
|
|
231
|
+
# Replace the drive with the UNC path
|
|
232
|
+
rest_of_path = path_str[len(drive_no_slash):].lstrip(chr(92))
|
|
233
|
+
unc_path = f"{unc_prefix}{chr(92)}{rest_of_path}"
|
|
234
|
+
logger.debug(f"Converted local path '{path_str}' to UNC path '{unc_path}'")
|
|
235
|
+
return Path(unc_path)
|
|
236
|
+
|
|
237
|
+
# No matching mapping found, return the original path
|
|
238
|
+
logger.debug(f"No UNC mapping found for local path '{path_str}'")
|
|
239
|
+
return Path(path_str)
|
|
240
|
+
|
|
241
|
+
def get_mappings(self) -> Dict[str, str]:
|
|
242
|
+
"""
|
|
243
|
+
Get a dictionary of current UNC path to drive letter mappings.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
A dictionary mapping UNC paths to drive letters.
|
|
247
|
+
"""
|
|
248
|
+
return self._mapping.copy()
|
|
249
|
+
|
|
250
|
+
def get_reverse_mappings(self) -> Dict[str, str]:
|
|
251
|
+
"""
|
|
252
|
+
Get a dictionary of current drive letter to UNC path mappings.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
A dictionary mapping drive letters to UNC paths.
|
|
256
|
+
"""
|
|
257
|
+
return self._reverse_mapping.copy()
|
|
258
|
+
|
|
259
|
+
# Create a global instance for convenience
|
|
260
|
+
_global_converter = None
|
|
261
|
+
|
|
262
|
+
def _get_global_converter() -> UNCConverter:
|
|
263
|
+
"""
|
|
264
|
+
Get or create the global UNCConverter instance.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
The global UNCConverter instance.
|
|
268
|
+
"""
|
|
269
|
+
global _global_converter
|
|
270
|
+
if _global_converter is None:
|
|
271
|
+
_global_converter = UNCConverter()
|
|
272
|
+
return _global_converter
|
|
273
|
+
|
|
274
|
+
def convert_to_local(path: Union[str, Path]) -> Path:
|
|
275
|
+
"""
|
|
276
|
+
Convert a UNC path to its corresponding local drive path if possible.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
path: The path to convert, potentially a UNC path.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Path: The converted path using a drive letter if a mapping exists,
|
|
283
|
+
otherwise the original path.
|
|
284
|
+
"""
|
|
285
|
+
converter = _get_global_converter()
|
|
286
|
+
return converter.convert_to_local(path)
|
|
287
|
+
|
|
288
|
+
def convert_to_unc(path: Union[str, Path]) -> Path:
|
|
289
|
+
"""
|
|
290
|
+
Convert a local drive path to its corresponding UNC path if possible.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
path: The path to convert, potentially using a mapped drive.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Path: The converted UNC path if the drive is mapped to a network share,
|
|
297
|
+
otherwise the original path.
|
|
298
|
+
"""
|
|
299
|
+
converter = _get_global_converter()
|
|
300
|
+
return converter.convert_to_unc(path)
|
|
301
|
+
|
|
302
|
+
def refresh_mappings() -> Dict[str, str]:
|
|
303
|
+
"""
|
|
304
|
+
Refresh the global mapping of UNC paths to drive letters.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
A dictionary mapping UNC paths to drive letters.
|
|
308
|
+
"""
|
|
309
|
+
converter = _get_global_converter()
|
|
310
|
+
return converter.refresh_mappings()
|
|
311
|
+
|
|
312
|
+
def get_mappings() -> Dict[str, str]:
|
|
313
|
+
"""
|
|
314
|
+
Get the current global UNC path to drive letter mappings.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
A dictionary mapping UNC paths to drive letters.
|
|
318
|
+
"""
|
|
319
|
+
converter = _get_global_converter()
|
|
320
|
+
return converter.get_mappings()
|
|
321
|
+
|
|
322
|
+
def normalize_path(path: Union[str, Path], prefer_unc: bool = False) -> Path:
|
|
323
|
+
"""
|
|
324
|
+
Normalize a path by ensuring consistent format and optionally converting between UNC and local.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
path: The path to normalize.
|
|
328
|
+
prefer_unc: If True, convert local paths to UNC if possible.
|
|
329
|
+
If False, convert UNC paths to local if possible.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
The normalized path.
|
|
333
|
+
"""
|
|
334
|
+
path_obj = Path(str(path).replace('/', '\\'))
|
|
335
|
+
|
|
336
|
+
if prefer_unc:
|
|
337
|
+
return convert_to_unc(path_obj)
|
|
338
|
+
else:
|
|
339
|
+
return convert_to_local(path_obj)
|
|
340
|
+
|
|
341
|
+
def parse_unc_path(path: Union[str, Path]) -> Optional[Tuple[str, str, str]]:
|
|
342
|
+
"""
|
|
343
|
+
Parse a UNC path into server, share, and path components.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
path: The UNC path to parse.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
A tuple of (server, share, path) if the path is a valid UNC path,
|
|
350
|
+
or None if the path is not a UNC path.
|
|
351
|
+
"""
|
|
352
|
+
path_str = str(path).replace('/', '\\')
|
|
353
|
+
match = UNC_PATTERN.match(path_str)
|
|
354
|
+
|
|
355
|
+
if match:
|
|
356
|
+
server = match.group(1)
|
|
357
|
+
share = match.group(2)
|
|
358
|
+
rest = match.group(3) or ""
|
|
359
|
+
return (server, share, rest)
|
|
360
|
+
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
def join_unc_path(server: str, share: str, rest: str = "") -> str:
|
|
364
|
+
"""
|
|
365
|
+
Join UNC path components into a complete UNC path.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
server: The server name.
|
|
369
|
+
share: The share name.
|
|
370
|
+
rest: The rest of the path (optional).
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
A properly formatted UNC path.
|
|
374
|
+
"""
|
|
375
|
+
if rest:
|
|
376
|
+
return f"{chr(92)}{chr(92)}{server}{chr(92)}{share}{chr(92)}{rest.lstrip(chr(92))}"
|
|
377
|
+
else:
|
|
378
|
+
return f"{chr(92)}{chr(92)}{server}{chr(92)}{share}"
|