dazzlelink 0.8.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.
dazzlelink/__init__.py ADDED
@@ -0,0 +1,306 @@
1
+ """
2
+ Dazzlelink - Symbolic Link Preservation Tool
3
+
4
+ A tool for exporting, importing, and managing symbolic links across
5
+ different systems, particularly useful for network shares and cross-platform
6
+ environments where native symlinks might not be properly supported.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import logging
12
+ from pathlib import Path
13
+
14
+ from ._version import __version__, __app_name__
15
+
16
+ # Set up package-level logger
17
+ logger = logging.getLogger(__name__)
18
+ logger.setLevel(logging.INFO)
19
+
20
+ # Create console handler if not already present
21
+ if not logger.handlers:
22
+ console_handler = logging.StreamHandler()
23
+ console_handler.setFormatter(
24
+ logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
25
+ )
26
+ logger.addHandler(console_handler)
27
+
28
+ # Import core functionality
29
+ from .exceptions import DazzleLinkException
30
+ from .data import DazzleLinkData
31
+ from .config import DazzleLinkConfig
32
+ from .path import (
33
+ UNCAdapter,
34
+ get_unc_adapter,
35
+ convert_to_drive,
36
+ convert_to_unc,
37
+ normalize_path,
38
+ refresh_mappings
39
+ )
40
+ from .operations import (
41
+ DazzleLink,
42
+ create_windows_symlink,
43
+ restore_file_attributes,
44
+ scan_directory,
45
+ find_dazzlelinks,
46
+ batch_import,
47
+ convert_directory,
48
+ mirror_directory,
49
+ batch_copy,
50
+ update_config_batch,
51
+ check_links,
52
+ rebase_links,
53
+ rebase_dazzlelinks,
54
+ recreate_link,
55
+ execute_dazzlelink
56
+ )
57
+
58
+ # Create a global instance for convenience
59
+ _dazzlelink_instance = None
60
+
61
+ def get_dazzlelink_instance():
62
+ """Get or create the global DazzleLink instance."""
63
+ global _dazzlelink_instance
64
+ if _dazzlelink_instance is None:
65
+ _dazzlelink_instance = DazzleLink()
66
+ return _dazzlelink_instance
67
+
68
+ # Convenience functions that use the global instance
69
+ def export_link(link_path, output_path=None, make_executable=None, mode=None):
70
+ """
71
+ Export a symlink to a .dazzlelink file
72
+
73
+ Args:
74
+ link_path: Path to the symlink
75
+ output_path: Output path for the dazzlelink file
76
+ make_executable: Whether to make the dazzlelink executable
77
+ mode: Default execution mode for this dazzlelink
78
+
79
+ Returns:
80
+ Path to the created dazzlelink file
81
+ """
82
+ dl = get_dazzlelink_instance()
83
+ return dl.serialize_link(link_path, output_path, make_executable, mode)
84
+
85
+ def import_link(dazzlelink_path, target_location=None, timestamp_strategy='current',
86
+ update_dazzlelink=False, use_live_target=False):
87
+ """
88
+ Import (recreate) a symlink from a .dazzlelink file
89
+
90
+ Args:
91
+ dazzlelink_path: Path to the dazzlelink file
92
+ target_location: Override location for the recreated symlink
93
+ timestamp_strategy: Strategy for setting timestamps
94
+ update_dazzlelink: Whether to update dazzlelink metadata
95
+ use_live_target: Whether to check live target for timestamps
96
+
97
+ Returns:
98
+ Path to the created symlink
99
+ """
100
+ return recreate_link(
101
+ dazzlelink_path,
102
+ target_location,
103
+ timestamp_strategy,
104
+ update_dazzlelink,
105
+ use_live_target
106
+ )
107
+
108
+ def create_link(target, link_name, make_executable=None, mode=None):
109
+ """
110
+ Create a new dazzlelink pointing to a target
111
+
112
+ Args:
113
+ target: The file or directory to link to
114
+ link_name: The path for the new dazzlelink
115
+ make_executable: Whether to make the dazzlelink executable
116
+ mode: Default execution mode for this dazzlelink
117
+
118
+ Returns:
119
+ Path to the created dazzlelink file
120
+ """
121
+ dl = get_dazzlelink_instance()
122
+ return dl.serialize_link(
123
+ target,
124
+ output_path=link_name,
125
+ make_executable=make_executable,
126
+ mode=mode,
127
+ require_symlink=False
128
+ )
129
+
130
+ def convert(directory, recursive=True, keep_originals=True,
131
+ make_executable=None, mode=None, config=None):
132
+ """
133
+ Convert symlinks in a directory to dazzlelinks
134
+
135
+ Args:
136
+ directory: Directory to scan
137
+ recursive: Whether to scan recursively
138
+ keep_originals: Whether to keep original symlinks
139
+ make_executable: Whether to make the dazzlelinks executable
140
+ (None uses config default)
141
+ mode: Default execution mode for the dazzlelinks (None uses config default)
142
+ config: Configuration object to use (None creates a fresh one).
143
+ Pass the CLI's config so --executable/--mode are honored.
144
+
145
+ Returns:
146
+ List of created dazzlelink paths
147
+ """
148
+ return convert_directory(
149
+ directory,
150
+ recursive=recursive,
151
+ keep_originals=keep_originals,
152
+ make_executable=make_executable,
153
+ mode=mode,
154
+ config=config
155
+ )
156
+
157
+ def mirror(src_dir, dest_dir, recursive=True,
158
+ make_executable=None, mode=None, config=None):
159
+ """
160
+ Mirror a directory structure with dazzlelinks
161
+
162
+ Args:
163
+ src_dir: Source directory
164
+ dest_dir: Destination directory
165
+ recursive: Whether to scan recursively
166
+ make_executable: Whether to make the dazzlelinks executable
167
+ (None uses config default)
168
+ mode: Default execution mode for the dazzlelinks (None uses config default)
169
+ config: Configuration object to use (None creates a fresh one).
170
+ Pass the CLI's config so --executable/--mode are honored.
171
+
172
+ Returns:
173
+ List of created dazzlelink paths
174
+ """
175
+ return mirror_directory(
176
+ src_dir, dest_dir, recursive=recursive,
177
+ make_executable=make_executable, mode=mode, config=config
178
+ )
179
+
180
+ def execute(dazzlelink_path, mode=None, config_override=None):
181
+ """
182
+ Execute/open a dazzlelink
183
+
184
+ Args:
185
+ dazzlelink_path: Path to the dazzlelink
186
+ mode: Override execution mode
187
+ config_override: Configuration object whose default_mode is used as a
188
+ last-resort fallback (after the CLI mode and the file's embedded mode)
189
+ """
190
+ return execute_dazzlelink(dazzlelink_path, mode, config_override=config_override)
191
+
192
+ def scan(directory, recursive=True):
193
+ """
194
+ Scan a directory for symlinks
195
+
196
+ Args:
197
+ directory: Directory to scan
198
+ recursive: Whether to scan recursively
199
+
200
+ Returns:
201
+ List of symlink paths
202
+ """
203
+ return scan_directory(directory, recursive)
204
+
205
+ def check(directory, recursive=True, fix=False):
206
+ """
207
+ Check symlinks in a directory and report broken ones
208
+
209
+ Args:
210
+ directory: Directory to scan
211
+ recursive: Whether to scan recursively
212
+ fix: Try to fix broken links when possible
213
+
214
+ Returns:
215
+ Dictionary with status of links
216
+ """
217
+ return check_links(directory, recursive, not fix, fix)
218
+
219
+ def rebase(directory, recursive=True, make_relative=None, target_base=None, only_broken=False):
220
+ """
221
+ Rebase links in a directory
222
+
223
+ Args:
224
+ directory: Directory containing links to rebase
225
+ recursive: Whether to scan recursively
226
+ make_relative: Convert to relative paths if True, absolute if False
227
+ target_base: Replace base part of absolute paths
228
+ only_broken: Only rebase broken links
229
+
230
+ Returns:
231
+ Dictionary with status of links
232
+ """
233
+ return rebase_links(directory, recursive, make_relative, target_base, only_broken)
234
+
235
+ def configure_logging(level=logging.INFO, log_file=None):
236
+ """
237
+ Configure logging for dazzlelink
238
+
239
+ Args:
240
+ level: Logging level (default: INFO)
241
+ log_file: Path to log file (if None, only console logging is used)
242
+ """
243
+ logger.setLevel(level)
244
+
245
+ # Clear existing handlers
246
+ for handler in logger.handlers[:]:
247
+ logger.removeHandler(handler)
248
+
249
+ # Add console handler
250
+ console_handler = logging.StreamHandler()
251
+ console_handler.setFormatter(
252
+ logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
253
+ )
254
+ logger.addHandler(console_handler)
255
+
256
+ # Add file handler if specified
257
+ if log_file:
258
+ file_handler = logging.FileHandler(log_file)
259
+ file_handler.setFormatter(
260
+ logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
261
+ )
262
+ logger.addHandler(file_handler)
263
+
264
+ def enable_verbose_logging():
265
+ """
266
+ Enable verbose (debug) logging
267
+ """
268
+ configure_logging(logging.DEBUG)
269
+ # Also set environment variable for modules that check it directly
270
+ os.environ['DAZZLELINK_VERBOSE'] = '1'
271
+
272
+ # Export public API
273
+ __all__ = [
274
+ # Classes
275
+ 'DazzleLinkException',
276
+ 'DazzleLinkData',
277
+ 'DazzleLinkConfig',
278
+ 'DazzleLink',
279
+ 'UNCAdapter',
280
+
281
+ # Path functions
282
+ 'get_unc_adapter',
283
+ 'convert_to_drive',
284
+ 'convert_to_unc',
285
+ 'normalize_path',
286
+ 'refresh_mappings',
287
+
288
+ # High-level functions
289
+ 'export_link',
290
+ 'import_link',
291
+ 'create_link',
292
+ 'convert',
293
+ 'mirror',
294
+ 'execute',
295
+ 'scan',
296
+ 'check',
297
+ 'rebase',
298
+
299
+ # Utility functions
300
+ 'get_dazzlelink_instance',
301
+ 'configure_logging',
302
+ 'enable_verbose_logging',
303
+
304
+ # Version
305
+ '__version__'
306
+ ]
dazzlelink/__main__.py ADDED
@@ -0,0 +1,10 @@
1
+ """Allow running as: python -m dazzlelink"""
2
+ import sys
3
+
4
+ from .cli import main
5
+
6
+ if __name__ == "__main__":
7
+ # Propagate main()'s return code as the process exit status so errors are
8
+ # detectable by scripts/CI (without this, `python -m dazzlelink` always
9
+ # exited 0, even on failure).
10
+ sys.exit(main())
dazzlelink/_version.py ADDED
@@ -0,0 +1,83 @@
1
+ """
2
+ Version information for dazzlelink.
3
+
4
+ This file is the canonical source for version numbers.
5
+ The __version__ string is automatically updated by git hooks
6
+ with build metadata (branch, build number, date, commit hash).
7
+
8
+ Format: MAJOR.MINOR.PATCH[-PHASE]_BRANCH_BUILD-YYYYMMDD-COMMITHASH
9
+ Example: 0.7.0_main_1-20260331-a1b2c3d4
10
+
11
+ To sync versions: python scripts/repokit-common/sync-versions.py
12
+ To bump version: python scripts/repokit-common/sync-versions.py --bump patch
13
+ """
14
+
15
+ # Version components - edit these for version bumps
16
+ MAJOR = 0
17
+ MINOR = 8
18
+ PATCH = 0
19
+ PHASE = "" # Per-MINOR feature set: None, "alpha", "beta", "rc1", etc.
20
+ PRE_RELEASE_NUM = 1 # PEP 440 pre-release number (e.g., a1, b2)
21
+ PROJECT_PHASE = "alpha" # Project-wide: "prealpha", "alpha", "beta", "stable"
22
+
23
+ # Auto-updated by git hooks - do not edit manually
24
+ __version__ = "0.8.0_main_45-20260612-55c76b11"
25
+ __app_name__ = "dazzlelink"
26
+
27
+
28
+ def get_version():
29
+ """Return the full version string including branch and build info."""
30
+ return __version__
31
+
32
+
33
+ def get_display_version():
34
+ """Return a human-friendly version string with project phase."""
35
+ base = get_base_version()
36
+ if PROJECT_PHASE and PROJECT_PHASE != "stable":
37
+ return f"{PROJECT_PHASE.upper()} {base}"
38
+ return base
39
+
40
+
41
+ def get_base_version():
42
+ """Return the semantic version string (MAJOR.MINOR.PATCH[-PHASE])."""
43
+ if "_" in __version__:
44
+ return __version__.split("_")[0]
45
+ base = f"{MAJOR}.{MINOR}.{PATCH}"
46
+ if PHASE:
47
+ base = f"{base}-{PHASE}"
48
+ return base
49
+
50
+
51
+ def get_pip_version():
52
+ """
53
+ Return PEP 440 compliant version for pip/setuptools.
54
+
55
+ Converts our version format to PEP 440:
56
+ - Main branch: 0.7.0_main_6-20260331-hash -> 0.7.0
57
+ - Dev branch: 0.7.0_dev_6-20260331-hash -> 0.7.0.dev6
58
+ """
59
+ base = f"{MAJOR}.{MINOR}.{PATCH}"
60
+
61
+ phase_map = {"alpha": f"a{PRE_RELEASE_NUM}", "beta": f"b{PRE_RELEASE_NUM}"}
62
+ if PHASE:
63
+ base += phase_map.get(PHASE, PHASE)
64
+
65
+ if "_" not in __version__:
66
+ return base
67
+
68
+ parts = __version__.split("_")
69
+ branch = parts[1] if len(parts) > 1 else "unknown"
70
+
71
+ if branch == "main":
72
+ return base
73
+ else:
74
+ build_info = "_".join(parts[2:]) if len(parts) > 2 else ""
75
+ build_num = build_info.split("-")[0] if "-" in build_info else "0"
76
+ return f"{base}.dev{build_num}"
77
+
78
+
79
+ # For convenience in imports
80
+ VERSION = get_version()
81
+ BASE_VERSION = get_base_version()
82
+ PIP_VERSION = get_pip_version()
83
+ DISPLAY_VERSION = get_display_version()