diff --git a/PyUcc.ico b/PyUcc.ico index e69de29..2c7d133 100644 Binary files a/PyUcc.ico and b/PyUcc.ico differ diff --git a/pyucc/_version.py b/pyucc/_version.py new file mode 100644 index 0000000..bfe2427 --- /dev/null +++ b/pyucc/_version.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# File generated by PyInstaller GUI Wrapper. DO NOT EDIT MANUALLY. +# Contains build-time information scraped from Git (if available) +# and a helper function to format version strings. + +import re + +# --- Version Data (Generated) --- +__version__ = "v.0.0.0.11-0-g7e0435a-dirty" +GIT_COMMIT_HASH = "7e0435ab7d0316bd2bcf5fdcd33cb84c39efa09c" +GIT_BRANCH = "master" +BUILD_TIMESTAMP = "2025-11-27T12:49:18.815605+00:00" +IS_GIT_REPO = True + +# --- Default Values (for comparison or fallback) --- +DEFAULT_VERSION = "0.0.0+unknown" +DEFAULT_COMMIT = "Unknown" +DEFAULT_BRANCH = "Unknown" + +# --- Helper Function --- +def get_version_string(format_string=None): + """ + Returns a formatted string based on the build version information. + + Args: + format_string (str, optional): A format string using placeholders. + Defaults to "{version} ({branch}/{commit_short})" if None. + Placeholders: + {{version}}: Full version string (e.g., 'v1.0.0-5-gabcdef-dirty') + {{tag}}: Clean tag part if exists (e.g., 'v1.0.0'), else DEFAULT_VERSION. + {{commit}}: Full Git commit hash. + {{commit_short}}: Short Git commit hash (7 chars). + {{branch}}: Git branch name. + {{dirty}}: '-dirty' if the repo was dirty, empty otherwise. + {{timestamp}}: Full build timestamp (ISO 8601 UTC). + {{timestamp_short}}: Build date only (YYYY-MM-DD). + {{is_git}}: 'Git' if IS_GIT_REPO is True, 'Unknown' otherwise. + + Returns: + str: The formatted version string, or an error message if formatting fails. + """ + if format_string is None: + format_string = "{version} ({branch}/{commit_short})" # Default format + + replacements = {} + try: + replacements['version'] = __version__ if __version__ else DEFAULT_VERSION + replacements['commit'] = GIT_COMMIT_HASH if GIT_COMMIT_HASH else DEFAULT_COMMIT + replacements['commit_short'] = GIT_COMMIT_HASH[:7] if GIT_COMMIT_HASH and len(GIT_COMMIT_HASH) >= 7 else DEFAULT_COMMIT + replacements['branch'] = GIT_BRANCH if GIT_BRANCH else DEFAULT_BRANCH + replacements['timestamp'] = BUILD_TIMESTAMP if BUILD_TIMESTAMP else "Unknown" + replacements['timestamp_short'] = BUILD_TIMESTAMP.split('T')[0] if BUILD_TIMESTAMP and 'T' in BUILD_TIMESTAMP else "Unknown" + replacements['is_git'] = "Git" if IS_GIT_REPO else "Unknown" + replacements['dirty'] = "-dirty" if __version__ and __version__.endswith('-dirty') else "" + + tag = DEFAULT_VERSION + if __version__ and IS_GIT_REPO: + match = re.match(r'^(v?([0-9]+(?:\.[0-9]+)*))', __version__) + if match: + tag = match.group(1) + replacements['tag'] = tag + + output_string = format_string + for placeholder, value in replacements.items(): + pattern = re.compile(r'{{\s*' + re.escape(placeholder) + r'\s*}}') + output_string = pattern.sub(str(value), output_string) + + if re.search(r'{\s*\w+\s*}', output_string): + pass # Or log a warning: print(f"Warning: Unreplaced placeholders found: {output_string}") + + return output_string + + except Exception as e: + return f"[Formatting Error: {e}]" diff --git a/pyucc/core/differ.py b/pyucc/core/differ.py index 31ac8d6..deccaea 100644 --- a/pyucc/core/differ.py +++ b/pyucc/core/differ.py @@ -144,9 +144,6 @@ class BaselineManager: if compute_sha1: # also compute for 0-byte files try: sha1 = _sha1_of_file(Path(fpath)) - # Log empty __init__.py files - if st.st_size == 0 and "__init__.py" in fn: - print(f"[BASELINE-SCAN] {rel_unix} (size={st.st_size}) SHA1={sha1}") except Exception: sha1 = None files_meta.append(FileMeta(path=rel_unix, size=st.st_size, mtime=st.st_mtime, sha1=sha1)) @@ -209,16 +206,6 @@ class BaselineManager: os.makedirs(dst_parent, exist_ok=True) try: shutil.copy2(src_file, dst_file) # copy2 preserves metadata - # Verify copy for empty __init__.py files - if fm.size == 0 and "__init__.py" in fm.path: - sha1_src = _sha1_of_file(Path(src_file)) - sha1_dst = _sha1_of_file(Path(dst_file)) - print(f"[BASELINE-COPY] {fm.path}") - print(f" Source SHA1: {sha1_src}") - print(f" Dest SHA1: {sha1_dst}") - print(f" Saved SHA1: {fm.sha1}") - if sha1_src != fm.sha1: - print(f" WARNING: Source file changed after initial scan!") except Exception: pass # skip files that cannot be copied @@ -468,9 +455,6 @@ class Differ: # Compute SHA1 for all files, including 0-byte files try: sha1 = _sha1_of_file(Path(fpath)) - # Log empty __init__.py files - if st.st_size == 0 and "__init__.py" in fn: - print(f"[DIFFER-CURRENT] {rel.replace(chr(92), '/')} (size={st.st_size}) SHA1={sha1}") except Exception: sha1 = None files_meta.append(FileMeta(path=rel.replace("\\", "/"), size=st.st_size, mtime=st.st_mtime, sha1=sha1)) @@ -715,15 +699,6 @@ class Differ: return res def diff(self) -> Dict: - import sys - sys.stdout.flush() - sys.stderr.flush() - print(f"\n{'='*80}", flush=True) - print(f"[DIFFER-START] Using baseline: {self.baseline.baseline_id}", flush=True) - print(f"[DIFFER-START] Baseline files dir: {self.baseline_files_dir}", flush=True) - print(f"[DIFFER-START] Current dir: {self.current_dir}", flush=True) - print(f"{'='*80}\n", flush=True) - baseline_files = self.baseline.files current_files = self.build_current_file_list() pairs = self.match_files(baseline_files, current_files) @@ -742,23 +717,6 @@ class Differ: for a, b in pairs: fa = os.path.join(self.baseline_files_dir, a.path) if a is not None else None fb = os.path.join(self.current_dir, b.path) if b is not None else None - - # DEBUG: Log empty __init__.py comparisons - if a and b and a.size == 0 and "__init__.py" in a.path: - import sys - print(f"\n[DIFFER-COMPARE] {a.path}", flush=True) - print(f" Baseline SHA1 (metadata): {a.sha1}", flush=True) - print(f" Current SHA1 (metadata): {b.sha1}", flush=True) - print(f" Baseline file: {fa}", flush=True) - print(f" Current file: {fb}", flush=True) - if fa and os.path.exists(fa): - sha1_baseline_actual = _sha1_of_file(Path(fa)) - print(f" Baseline SHA1 (actual): {sha1_baseline_actual}", flush=True) - if fb and os.path.exists(fb): - sha1_current_actual = _sha1_of_file(Path(fb)) - print(f" Current SHA1 (actual): {sha1_current_actual}", flush=True) - sys.stdout.flush() - futures.append(ex.submit(self._diff_file_pair, fa, fb)) for (a, b), fut in zip(pairs, futures): res = fut.result() diff --git a/pyucc/gui/action_handlers.py b/pyucc/gui/action_handlers.py index e432d46..4065b76 100644 --- a/pyucc/gui/action_handlers.py +++ b/pyucc/gui/action_handlers.py @@ -340,20 +340,20 @@ class ActionHandlers: rows = (_row_from_pair(p) for p in result.get('pairs', [])) export_rows_to_csv(csv_path, headers, rows) - # Generate text report - profile_config = { - 'name': profile_name, - 'root': project, - 'paths': paths if paths else [project], - 'languages': allowed_exts if allowed_exts else [], - 'exclude_patterns': ignore_patterns - } - generate_differ_report(result, profile_config, baseline_id, report_path) - except Exception: - # non-fatal: continue even if export fails - pass - - # Show summary dialog + # Generate text report + profile_config = { + 'name': profile_name, + 'root': project, + 'paths': paths if paths else [project], + 'languages': allowed_exts if allowed_exts else [], + 'exclude_patterns': ignore_patterns + } + generate_differ_report(result, profile_config, baseline_id, report_path) + self.app.log(f"Differ report saved to: {report_path}", level='INFO') + except Exception as e: + # non-fatal: continue even if export fails + self.app.log(f"Failed to export differ results: {e}", level='WARNING') + pass # Show summary dialog self._show_differ_summary_dialog(result, baseline_id, bdir) except Exception: self.app._set_phase('Idle') @@ -515,25 +515,11 @@ class ActionHandlers: b.sha1 = None # Compare hashes - # DEBUG: Log SHA1 comparison for empty __init__.py files - if a and b and a.size == 0 and "__init__.py" in str(a.path): - import sys - print(f"\n[ACTION_HANDLERS] SHA1 comparison for {a.path}", flush=True) - print(f" a.sha1={getattr(a, 'sha1', 'NO ATTR')}", flush=True) - print(f" b.sha1={getattr(b, 'sha1', 'NO ATTR')}", flush=True) - print(f" hasattr(a, 'sha1')={hasattr(a, 'sha1')}", flush=True) - print(f" a.sha1 is truthy={bool(a.sha1) if hasattr(a, 'sha1') else False}", flush=True) - print(f" b.sha1 is truthy={bool(b.sha1)}", flush=True) - print(f" Match: {a.sha1 == b.sha1 if (hasattr(a, 'sha1') and a.sha1 and b.sha1) else 'N/A'}", flush=True) - sys.stdout.flush() - if hasattr(a, 'sha1') and a.sha1 and b.sha1 and a.sha1 == b.sha1: # Identical files res['counts'] = {'added': 0, 'deleted': 0, 'modified': 0, 'unmodified': 1} else: # Modified file - if a and b and a.size == 0 and "__init__.py" in str(a.path): - print(f" → Marked as MODIFIED!", flush=True) res['counts'] = {'added': 0, 'deleted': 0, 'modified': 1, 'unmodified': 0} else: # Both None (shouldn't happen) diff --git a/pyucc/gui/diff_viewer.py b/pyucc/gui/diff_viewer.py index 0130f76..0656e79 100644 --- a/pyucc/gui/diff_viewer.py +++ b/pyucc/gui/diff_viewer.py @@ -114,6 +114,9 @@ class DiffViewer(tk.Toplevel): highlightbackground='#ccc') self.minimap.grid(row=1, column=1, sticky='nsew', padx=2, pady=0) + # Ridisegna minimap quando il canvas viene ridimensionato + self.minimap.bind('', lambda e: self._draw_minimap()) + # Scrollbar orizzontali scrollbar_h_a = ttk.Scrollbar(main_frame, orient='horizontal', command=self.text_a.xview) scrollbar_h_a.grid(row=2, column=0, sticky='ew', padx=(2, 1)) @@ -413,13 +416,22 @@ class DiffViewer(tk.Toplevel): # Rimuovi rettangolo precedente self.minimap.delete('viewport') - # Calcola posizione viewport + if not hasattr(self, 'minimap_blocks') or not self.minimap_blocks: + return + + # Calcola posizione viewport basandosi sul totale delle linee yview = self.text_a.yview() canvas_height = self.minimap.winfo_height() + canvas_width = self.minimap.winfo_width() + # yview restituisce (frazione_inizio, frazione_fine) del documento + # Usa queste frazioni direttamente sull'altezza del canvas y1 = yview[0] * canvas_height y2 = yview[1] * canvas_height - canvas_width = self.minimap.winfo_width() + + # Assicurati che il viewport sia visibile (minimo 5 pixel) + if y2 - y1 < 5: + y2 = y1 + 5 # Disegna rettangolo viewport self.minimap.create_rectangle(0, y1, canvas_width, y2,