From b02a15f7e510b2df32140c162ed2ec457570f6a2 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Wed, 21 Jan 2026 17:08:24 +0100 Subject: [PATCH] Remove unused files --- portfolio-tracker-v2.el | 451 ----------------------------------- symbol_finder.py | 508 ---------------------------------------- 2 files changed, 959 deletions(-) delete mode 100644 portfolio-tracker-v2.el delete mode 100755 symbol_finder.py diff --git a/portfolio-tracker-v2.el b/portfolio-tracker-v2.el deleted file mode 100644 index 0ac7f07..0000000 --- a/portfolio-tracker-v2.el +++ /dev/null @@ -1,451 +0,0 @@ -;;; portfolio-tracker-v2.el --- Portfolio tracking with transaction history -*- lexical-binding: t -*- - -;;; Commentary: -;; Enhanced portfolio tracker with full transaction history and cost basis tracking -;; Supports buy/sell transactions, dividends, and multiple lots (FIFO/LIFO) - -;;; Code: - -(require 'cl-lib) -(require 'tabulated-list) -(require 'url) -(require 'json) - -(defgroup portfolio-tracker nil - "Portfolio tracking with live price updates." - :group 'applications) - -(defcustom portfolio-tracker-update-interval 300 - "Interval in seconds between automatic price updates." - :type 'integer - :group 'portfolio-tracker) - -(defcustom portfolio-tracker-cost-basis-method 'fifo - "Method for calculating cost basis: fifo or lifo." - :type '(choice (const :tag "FIFO" fifo) - (const :tag "LIFO" lifo)) - :group 'portfolio-tracker) - -;; Data structures -(cl-defstruct portfolio-transaction - date - type ; buy, sell, dividend, split - symbol - shares - price - fees - notes) - -(cl-defstruct portfolio-lot - symbol - shares ; remaining shares in this lot - purchase-date - purchase-price - fees) - -(cl-defstruct portfolio-holding - symbol - name - total-shares - lots ; list of portfolio-lot structs - average-cost - current-price - previous-close - value - unrealized-gain - unrealized-gain-percent - realized-gain ; from sell transactions - dividends ; total dividends received - type) ; stock, etf, crypto - -(defvar portfolio-tracker-transactions nil - "List of all transactions.") - -(defvar portfolio-tracker-holdings nil - "Current holdings calculated from transactions.") - -(defvar portfolio-tracker-prices-cache nil - "Cache of current prices.") - -;; Transaction processing -(defun portfolio-tracker-process-transactions () - "Process all transactions to calculate current holdings." - (let ((holdings-table (make-hash-table :test 'equal)) - (realized-gains (make-hash-table :test 'equal)) - (dividends-table (make-hash-table :test 'equal))) - - ;; Sort transactions by date - (setq portfolio-tracker-transactions - (sort portfolio-tracker-transactions - (lambda (a b) - (string< (portfolio-transaction-date a) - (portfolio-transaction-date b))))) - - ;; Process each transaction - (dolist (txn portfolio-tracker-transactions) - (let ((symbol (portfolio-transaction-symbol txn)) - (type (portfolio-transaction-type txn))) - - (cond - ;; Buy transaction - add lot - ((eq type 'buy) - (let* ((holding (gethash symbol holdings-table)) - (new-lot (make-portfolio-lot - :symbol symbol - :shares (portfolio-transaction-shares txn) - :purchase-date (portfolio-transaction-date txn) - :purchase-price (portfolio-transaction-price txn) - :fees (or (portfolio-transaction-fees txn) 0)))) - (if holding - (push new-lot (portfolio-holding-lots holding)) - (puthash symbol - (make-portfolio-holding - :symbol symbol - :lots (list new-lot) - :realized-gain 0 - :dividends 0) - holdings-table)))) - - ;; Sell transaction - remove shares using FIFO/LIFO - ((eq type 'sell) - (let ((holding (gethash symbol holdings-table)) - (shares-to-sell (portfolio-transaction-shares txn)) - (sell-price (portfolio-transaction-price txn)) - (realized 0)) - (when holding - (let ((lots (if (eq portfolio-tracker-cost-basis-method 'lifo) - (reverse (portfolio-holding-lots holding)) - (portfolio-holding-lots holding)))) - (dolist (lot lots) - (when (> shares-to-sell 0) - (let ((shares-from-lot (min shares-to-sell - (portfolio-lot-shares lot)))) - (setq realized - (+ realized - (* shares-from-lot - (- sell-price (portfolio-lot-purchase-price lot))))) - (setf (portfolio-lot-shares lot) - (- (portfolio-lot-shares lot) shares-from-lot)) - (setq shares-to-sell (- shares-to-sell shares-from-lot))))) - ;; Remove empty lots - (setf (portfolio-holding-lots holding) - (cl-remove-if (lambda (lot) (<= (portfolio-lot-shares lot) 0)) - (portfolio-holding-lots holding))) - ;; Add to realized gains - (setf (portfolio-holding-realized-gain holding) - (+ (portfolio-holding-realized-gain holding) realized)))))) - - ;; Dividend transaction - ((eq type 'dividend) - (let ((holding (gethash symbol holdings-table))) - (when holding - (setf (portfolio-holding-dividends holding) - (+ (portfolio-holding-dividends holding) - (* (portfolio-transaction-shares txn) - (portfolio-transaction-price txn))))))))) - - ;; Calculate totals for each holding - (maphash (lambda (symbol holding) - (let ((total-shares 0) - (total-cost 0)) - (dolist (lot (portfolio-holding-lots holding)) - (setq total-shares (+ total-shares (portfolio-lot-shares lot))) - (setq total-cost (+ total-cost - (* (portfolio-lot-shares lot) - (portfolio-lot-purchase-price lot))))) - (setf (portfolio-holding-total-shares holding) total-shares) - (setf (portfolio-holding-average-cost holding) - (if (> total-shares 0) - (/ total-cost total-shares) - 0)))) - holdings-table) - - ;; Convert hash table to list - (setq portfolio-tracker-holdings nil) - (maphash (lambda (symbol holding) - (when (> (portfolio-holding-total-shares holding) 0) - (push holding portfolio-tracker-holdings))) - holdings-table))) - -;; Yahoo Finance integration -(defun portfolio-tracker-fetch-price (symbol callback) - "Fetch current price for SYMBOL and call CALLBACK with the result." - (let ((url (format "https://query1.finance.yahoo.com/v8/finance/chart/%s" symbol))) - (url-retrieve url - (lambda (status) - (if (plist-get status :error) - (message "Error fetching price for %s" symbol) - (goto-char (point-min)) - (re-search-forward "^$") - (let* ((json-object-type 'plist) - (json-array-type 'list) - (json (json-read)) - (result (plist-get json :chart)) - (data (car (plist-get result :result))) - (meta (plist-get data :meta)) - (price (plist-get meta :regularMarketPrice)) - (prev-close (plist-get meta :previousClose)) - (name (plist-get meta :longName))) - (funcall callback symbol price prev-close name)))) - nil t))) - -(defun portfolio-tracker-update-prices () - "Update prices for all holdings." - (interactive) - (message "Updating prices...") - (dolist (holding portfolio-tracker-holdings) - (portfolio-tracker-fetch-price - (portfolio-holding-symbol holding) - (lambda (symbol price prev-close name) - (let ((h (cl-find symbol portfolio-tracker-holdings - :key #'portfolio-holding-symbol - :test #'string=))) - (when h - (setf (portfolio-holding-current-price h) price) - (setf (portfolio-holding-previous-close h) prev-close) - (when name - (setf (portfolio-holding-name h) name)) - ;; Calculate unrealized gains - (let ((total-cost 0)) - (dolist (lot (portfolio-holding-lots h)) - (setq total-cost (+ total-cost - (* (portfolio-lot-shares lot) - (portfolio-lot-purchase-price lot))))) - (setf (portfolio-holding-value h) - (* (portfolio-holding-total-shares h) price)) - (setf (portfolio-holding-unrealized-gain h) - (- (portfolio-holding-value h) total-cost)) - (setf (portfolio-holding-unrealized-gain-percent h) - (if (> total-cost 0) - (* 100 (/ (portfolio-holding-unrealized-gain h) total-cost)) - 0)))) - (portfolio-tracker-refresh-display)))))) - -;; Display functions -(eval-and-compile - (defvar portfolio-tracker-mode-map - (let ((map (make-sparse-keymap))) - ;; Use string keys directly for single characters - (define-key map "g" 'portfolio-tracker-refresh) - (define-key map "a" 'portfolio-tracker-add-transaction) - (define-key map "t" 'portfolio-tracker-show-transactions) - (define-key map "h" 'portfolio-tracker-refresh-display) - (define-key map "s" 'portfolio-tracker-save) - (define-key map "l" 'portfolio-tracker-load) - (define-key map "r" 'portfolio-tracker-refresh) - (define-key map "q" 'quit-window) - map) - "Keymap for portfolio-tracker-mode.") - - (define-derived-mode portfolio-tracker-mode tabulated-list-mode "Portfolio" - "Major mode for tracking investment portfolio. -\\{portfolio-tracker-mode-map}" - :keymap portfolio-tracker-mode-map - ;; Disable CUA mode to allow single-key commands - (setq-local cua-mode nil) - (setq-local cua-enable-cua-keys nil) - (setq tabulated-list-format - [("Symbol" 8 t) - ("Name" 20 t) - ("Shares" 10 t) - ("Avg Cost" 10 nil) - ("Current" 10 nil) - ("Value" 12 nil) - ("Unrealized" 12 nil) - ("Realized" 10 nil) - ("Dividends" 10 nil) - ("Total %" 8 nil)]) - (setq tabulated-list-padding 2) - (setq tabulated-list-sort-key (cons "Symbol" nil)) - (tabulated-list-init-header))) - -(define-derived-mode portfolio-transactions-mode tabulated-list-mode "Transactions" - "Major mode for viewing portfolio transactions." - (setq tabulated-list-format - [("Date" 12 t) - ("Type" 8 t) - ("Symbol" 8 t) - ("Shares" 10 nil) - ("Price" 10 nil) - ("Total" 12 nil) - ("Fees" 8 nil) - ("Notes" 30 nil)]) - (setq tabulated-list-padding 2) - (setq tabulated-list-sort-key (cons "Date" t)) - (tabulated-list-init-header)) - -(defun portfolio-tracker-refresh-display () - "Refresh the holdings display." - (interactive) - (let ((buf (get-buffer "*Portfolio Holdings*"))) - (when buf - (with-current-buffer buf - (let ((inhibit-read-only t)) - (setq tabulated-list-entries - (mapcar (lambda (holding) - (list holding - (vector - (portfolio-holding-symbol holding) - (or (portfolio-holding-name holding) "") - (format "%.2f" (portfolio-holding-total-shares holding)) - (format "$%.2f" (portfolio-holding-average-cost holding)) - (if (portfolio-holding-current-price holding) - (format "$%.2f" (portfolio-holding-current-price holding)) - "...") - (format "$%.2f" (or (portfolio-holding-value holding) 0)) - (propertize (format "$%.2f" (or (portfolio-holding-unrealized-gain holding) 0)) - 'face (if (>= (or (portfolio-holding-unrealized-gain holding) 0) 0) - 'success 'error)) - (format "$%.2f" (portfolio-holding-realized-gain holding)) - (format "$%.2f" (portfolio-holding-dividends holding)) - (propertize (format "%.1f%%" (or (portfolio-holding-unrealized-gain-percent holding) 0)) - 'face (if (>= (or (portfolio-holding-unrealized-gain-percent holding) 0) 0) - 'success 'error))))) - portfolio-tracker-holdings)) - (tabulated-list-print t) - ;; Add summary at the bottom - (portfolio-tracker-display-summary))))))) - -(defun portfolio-tracker-display-summary () - "Display portfolio summary with totals at bottom of buffer." - (let ((inhibit-read-only t) - (total-cost 0) - (total-value 0) - (total-unrealized 0) - (total-realized 0) - (total-dividends 0)) - ;; Calculate totals - (dolist (holding portfolio-tracker-holdings) - (let ((cost (* (portfolio-holding-total-shares holding) - (portfolio-holding-average-cost holding)))) - (setq total-cost (+ total-cost cost)) - (setq total-value (+ total-value (or (portfolio-holding-value holding) cost))) - (setq total-unrealized (+ total-unrealized (or (portfolio-holding-unrealized-gain holding) 0))) - (setq total-realized (+ total-realized (portfolio-holding-realized-gain holding))) - (setq total-dividends (+ total-dividends (portfolio-holding-dividends holding))))) - - ;; Display summary - (goto-char (point-max)) - (insert "\n" (make-string 100 ?-) "\n") - (insert (propertize "PORTFOLIO TOTALS\n" 'face 'bold)) - (insert (format " Total Buy-In (Cost Basis): %s%.2f\n" - (propertize "$" 'face 'default) - total-cost)) - (insert (format " Current Market Value: %s%.2f\n" - (propertize "$" 'face 'default) - total-value)) - (insert (format " Unrealized Gain/Loss: %s\n" - (propertize (format "$%.2f (%.1f%%)" - total-unrealized - (if (> total-cost 0) - (* 100 (/ total-unrealized total-cost)) - 0)) - 'face (if (>= total-unrealized 0) 'success 'error)))) - (insert (format " Realized Gain/Loss: %s\n" - (propertize (format "$%.2f" total-realized) - 'face (if (>= total-realized 0) 'success 'error)))) - (insert (format " Total Dividends Received: %s%.2f\n" - (propertize "$" 'face 'default) - total-dividends)) - (insert (make-string 100 ?-) "\n") - (let ((total-gain (+ total-unrealized total-realized total-dividends)) - (total-return-pct (if (> total-cost 0) - (* 100 (/ (+ total-unrealized total-dividends) total-cost)) - 0))) - (insert (format " TOTAL RETURN: %s\n" - (propertize (format "$%.2f (%.1f%%)" - total-gain - total-return-pct) - 'face (if (>= total-gain 0) 'success 'error)))) - (insert (make-string 100 ?=) "\n")) - (insert "\nKeys: [g] Refresh | [a] Add Transaction | [t] Show Transactions | [s] Save | [l] Load | [q] Quit\n"))) - -(defun portfolio-tracker-refresh () - "Refresh prices and redisplay." - (interactive) - (portfolio-tracker-process-transactions) - (portfolio-tracker-update-prices) - (portfolio-tracker-refresh-display)) - -(defun portfolio-tracker-add-transaction () - "Add a new transaction interactively." - (interactive) - (let* ((date (read-string "Date (YYYY-MM-DD): " (format-time-string "%Y-%m-%d"))) - (type (intern (completing-read "Type: " '("buy" "sell" "dividend") nil t))) - (symbol (upcase (read-string "Symbol: "))) - (shares (read-number "Shares: ")) - (price (read-number "Price per share: ")) - (fees (read-number "Fees (0 if none): " 0)) - (notes (read-string "Notes (optional): "))) - (push (make-portfolio-transaction - :date date - :type type - :symbol symbol - :shares shares - :price price - :fees fees - :notes notes) - portfolio-tracker-transactions) - (portfolio-tracker-process-transactions) - (portfolio-tracker-update-prices))) - -(defun portfolio-tracker-show-transactions () - "Show all transactions in a separate buffer." - (interactive) - (let ((buf (get-buffer-create "*Portfolio Transactions*"))) - (with-current-buffer buf - (portfolio-transactions-mode) - (setq tabulated-list-entries - (mapcar (lambda (txn) - (list txn - (vector - (portfolio-transaction-date txn) - (symbol-name (portfolio-transaction-type txn)) - (portfolio-transaction-symbol txn) - (format "%.2f" (portfolio-transaction-shares txn)) - (format "$%.2f" (portfolio-transaction-price txn)) - (format "$%.2f" (* (portfolio-transaction-shares txn) - (portfolio-transaction-price txn))) - (format "$%.2f" (or (portfolio-transaction-fees txn) 0)) - (or (portfolio-transaction-notes txn) "")))) - (reverse portfolio-tracker-transactions))) - (tabulated-list-print)) - (switch-to-buffer buf))) - -(defun portfolio-tracker-save (file) - "Save portfolio data to FILE." - (interactive "FSave portfolio to: ") - (with-temp-file file - (insert ";; Portfolio Tracker Data\n") - (insert ";; Transaction history and settings\n\n") - (insert "(setq portfolio-tracker-transactions\n") - (pp portfolio-tracker-transactions (current-buffer)) - (insert ")\n")) - (message "Portfolio saved to %s" file)) - -(defun portfolio-tracker-load (file) - "Load portfolio data from FILE." - (interactive "fLoad portfolio from: ") - (load-file file) - (portfolio-tracker-process-transactions) - (portfolio-tracker-refresh-display) - (portfolio-tracker-update-prices) - (message "Portfolio loaded from %s" file)) - -;;;###autoload -(defun portfolio-tracker () - "Start the portfolio tracker." - (interactive) - (require 'tabulated-list) - (require 'cl-lib) - (let ((buf (get-buffer-create "*Portfolio Holdings*"))) - (switch-to-buffer buf) - (unless (eq major-mode 'portfolio-tracker-mode) - (portfolio-tracker-mode)) - (when portfolio-tracker-transactions - (portfolio-tracker-process-transactions) - (portfolio-tracker-refresh-display)) - (message "Portfolio Tracker: 'a' add transaction | 't' show transactions | 'g' refresh prices | 'l' load portfolio"))) - -(provide 'portfolio-tracker-v2) -;;; portfolio-tracker-v2.el ends here diff --git a/symbol_finder.py b/symbol_finder.py deleted file mode 100755 index 4033ba0..0000000 --- a/symbol_finder.py +++ /dev/null @@ -1,508 +0,0 @@ -#!/usr/bin/env python3 -""" -Symbol finder tool for C++ and QML source files with caching support. -""" - -import os -import re -import json -import time -import hashlib -import argparse -import sys -from pathlib import Path -from typing import Dict, List, Tuple, Optional, Set -from dataclasses import dataclass, asdict -from collections import defaultdict - -@dataclass -class Symbol: - """Represents a symbol found in source code.""" - name: str - file_path: str - line_number: int - symbol_type: str # 'class', 'function', 'variable', 'property', 'signal', 'method', 'enum', 'namespace' - context: str # Line content for context - - def to_dict(self): - return asdict(self) - - @classmethod - def from_dict(cls, data): - return cls(**data) - -class SymbolCache: - """Manages caching of symbol information.""" - - def __init__(self, cache_dir: str = ".symbol_cache"): - self.cache_dir = Path(cache_dir) - self.cache_dir.mkdir(exist_ok=True) - self.index_file = self.cache_dir / "index.json" - self.symbols_file = self.cache_dir / "symbols.json" - self.index = self._load_index() - self.symbols = self._load_symbols() - - def _load_index(self) -> Dict[str, Dict]: - """Load file index from cache.""" - if self.index_file.exists(): - with open(self.index_file, 'r') as f: - return json.load(f) - return {} - - def _load_symbols(self) -> Dict[str, List[Dict]]: - """Load symbols from cache.""" - if self.symbols_file.exists(): - with open(self.symbols_file, 'r') as f: - return json.load(f) - return {} - - def save(self): - """Save cache to disk.""" - with open(self.index_file, 'w') as f: - json.dump(self.index, f, indent=2) - with open(self.symbols_file, 'w') as f: - json.dump(self.symbols, f, indent=2) - - def is_file_cached(self, file_path: str) -> bool: - """Check if file is cached and up to date.""" - if file_path not in self.index: - return False - - cached_mtime = self.index[file_path].get('mtime', 0) - current_mtime = os.path.getmtime(file_path) - return cached_mtime >= current_mtime - - def update_file(self, file_path: str, symbols: List[Symbol]): - """Update cache for a specific file.""" - self.index[file_path] = { - 'mtime': os.path.getmtime(file_path), - 'symbol_count': len(symbols) - } - self.symbols[file_path] = [s.to_dict() for s in symbols] - - def get_symbols(self, file_path: str) -> List[Symbol]: - """Get cached symbols for a file.""" - if file_path in self.symbols: - return [Symbol.from_dict(s) for s in self.symbols[file_path]] - return [] - - def get_all_symbols(self) -> List[Symbol]: - """Get all cached symbols.""" - all_symbols = [] - for file_path, symbols in self.symbols.items(): - all_symbols.extend([Symbol.from_dict(s) for s in symbols]) - return all_symbols - -class CppParser: - """Parser for C++ source files.""" - - # Patterns for C++ symbols - CLASS_PATTERN = re.compile(r'^\s*(class|struct|union)\s+([A-Za-z_]\w*)', re.MULTILINE) - FUNCTION_PATTERN = re.compile(r'^\s*(?:(?:static|inline|virtual|const|explicit|friend|extern)\s+)*(?:[\w:]+\s+)?([A-Za-z_]\w*)\s*\([^)]*\)\s*(?:const)?\s*(?:override)?\s*[{;]', re.MULTILINE) - NAMESPACE_PATTERN = re.compile(r'^\s*namespace\s+([A-Za-z_]\w*)', re.MULTILINE) - ENUM_PATTERN = re.compile(r'^\s*enum\s+(?:class\s+)?([A-Za-z_]\w*)', re.MULTILINE) - TYPEDEF_PATTERN = re.compile(r'^\s*(?:typedef|using)\s+.*\s+([A-Za-z_]\w*)\s*[;=]', re.MULTILINE) - MEMBER_VAR_PATTERN = re.compile(r'^\s*(?:(?:static|const|mutable)\s+)*(?:[\w:]+\s+)+([A-Za-z_]\w*)\s*[;=]', re.MULTILINE) - - def parse_file(self, file_path: str) -> List[Symbol]: - """Parse a C++ file and extract symbols.""" - symbols = [] - - try: - with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: - content = f.read() - lines = content.split('\n') - except Exception as e: - print(f"Error reading {file_path}: {e}", file=sys.stderr) - return symbols - - # Extract classes/structs - for match in self.CLASS_PATTERN.finditer(content): - line_num = content[:match.start()].count('\n') + 1 - symbols.append(Symbol( - name=match.group(2), - file_path=file_path, - line_number=line_num, - symbol_type='class', - context=lines[line_num - 1].strip() if line_num <= len(lines) else "" - )) - - # Extract namespaces - for match in self.NAMESPACE_PATTERN.finditer(content): - line_num = content[:match.start()].count('\n') + 1 - symbols.append(Symbol( - name=match.group(1), - file_path=file_path, - line_number=line_num, - symbol_type='namespace', - context=lines[line_num - 1].strip() if line_num <= len(lines) else "" - )) - - # Extract enums - for match in self.ENUM_PATTERN.finditer(content): - line_num = content[:match.start()].count('\n') + 1 - symbols.append(Symbol( - name=match.group(1), - file_path=file_path, - line_number=line_num, - symbol_type='enum', - context=lines[line_num - 1].strip() if line_num <= len(lines) else "" - )) - - # Extract functions (basic pattern, may need refinement) - for match in self.FUNCTION_PATTERN.finditer(content): - name = match.group(1) - # Filter out some common false positives - if name not in ['if', 'while', 'for', 'switch', 'return', 'delete', 'new']: - line_num = content[:match.start()].count('\n') + 1 - symbols.append(Symbol( - name=name, - file_path=file_path, - line_number=line_num, - symbol_type='function', - context=lines[line_num - 1].strip() if line_num <= len(lines) else "" - )) - - return symbols - -class QmlParser: - """Parser for QML files.""" - - # Patterns for QML symbols - more comprehensive - QML_TYPE_PATTERN = re.compile(r'^\s*([A-Z]\w*)\s*\{', re.MULTILINE) - # Match both regular and readonly properties with better type capture - PROPERTY_PATTERN = re.compile(r'^\s*(?:readonly\s+)?property\s+(?:[\w.<>]+\s+)?([a-zA-Z_]\w*)', re.MULTILINE) - SIGNAL_PATTERN = re.compile(r'^\s*signal\s+([a-zA-Z_]\w*)', re.MULTILINE) - FUNCTION_PATTERN = re.compile(r'^\s*function\s+([a-zA-Z_]\w*)\s*\(', re.MULTILINE) - ID_PATTERN = re.compile(r'^\s*id:\s*([a-zA-Z_]\w*)', re.MULTILINE) - # Match simple property assignments - but be more selective - PROPERTY_BINDING_PATTERN = re.compile(r'^\s*([a-zA-Z_]\w*):\s*(?:["\'{#]|[0-9]|true|false)', re.MULTILINE) - - def parse_file(self, file_path: str, debug: bool = False) -> List[Symbol]: - """Parse a QML file and extract symbols.""" - symbols = [] - - try: - with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: - content = f.read() - lines = content.split('\n') - except Exception as e: - print(f"Error reading {file_path}: {e}", file=sys.stderr) - return symbols - - if debug: - print(f"Debug: Parsing {file_path}, {len(lines)} lines", file=sys.stderr) - - # Extract QML types - for match in self.QML_TYPE_PATTERN.finditer(content): - line_num = content[:match.start()].count('\n') + 1 - symbols.append(Symbol( - name=match.group(1), - file_path=file_path, - line_number=line_num, - symbol_type='class', - context=lines[line_num - 1].strip() if line_num <= len(lines) else "" - )) - - # Extract properties (including readonly) - property_matches = list(self.PROPERTY_PATTERN.finditer(content)) - if debug: - print(f"Debug: Found {len(property_matches)} property declarations", file=sys.stderr) - - for match in property_matches: - line_num = content[:match.start()].count('\n') + 1 - name = match.group(1) - # Avoid duplicates by checking if we already have this symbol at same line - if not any(s.name == name and s.line_number == line_num for s in symbols): - symbols.append(Symbol( - name=name, - file_path=file_path, - line_number=line_num, - symbol_type='property', - context=lines[line_num - 1].strip() if line_num <= len(lines) else "" - )) - if debug and 'color' in name.lower(): - print(f"Debug: Found color property: {name} at line {line_num}", file=sys.stderr) - - # Extract property bindings (like colorMedTechNavy1: "#value") - for match in self.PROPERTY_BINDING_PATTERN.finditer(content): - line_num = content[:match.start()].count('\n') + 1 - name = match.group(1) - # Skip common keywords that aren't properties - if name not in {'import', 'if', 'else', 'for', 'while', 'return', 'var', 'let', 'const'}: - # Check if this isn't already captured - if not any(s.name == name and abs(s.line_number - line_num) < 2 for s in symbols): - symbols.append(Symbol( - name=name, - file_path=file_path, - line_number=line_num, - symbol_type='property', - context=lines[line_num - 1].strip() if line_num <= len(lines) else "" - )) - - # Extract signals - for match in self.SIGNAL_PATTERN.finditer(content): - line_num = content[:match.start()].count('\n') + 1 - symbols.append(Symbol( - name=match.group(1), - file_path=file_path, - line_number=line_num, - symbol_type='signal', - context=lines[line_num - 1].strip() if line_num <= len(lines) else "" - )) - - # Extract functions - for match in self.FUNCTION_PATTERN.finditer(content): - line_num = content[:match.start()].count('\n') + 1 - symbols.append(Symbol( - name=match.group(1), - file_path=file_path, - line_number=line_num, - symbol_type='function', - context=lines[line_num - 1].strip() if line_num <= len(lines) else "" - )) - - # Extract IDs - for match in self.ID_PATTERN.finditer(content): - line_num = content[:match.start()].count('\n') + 1 - symbols.append(Symbol( - name=match.group(1), - file_path=file_path, - line_number=line_num, - symbol_type='variable', - context=lines[line_num - 1].strip() if line_num <= len(lines) else "" - )) - - return symbols - -class SymbolFinder: - """Main symbol finder class.""" - - def __init__(self, root_dir: str = ".", cache_dir: str = ".symbol_cache"): - self.root_dir = Path(root_dir).resolve() - # Always use absolute path for cache_dir - if not Path(cache_dir).is_absolute(): - cache_dir = self.root_dir / cache_dir - self.cache = SymbolCache(str(cache_dir)) - self.cpp_parser = CppParser() - self.qml_parser = QmlParser() - self.file_extensions = { - '.cpp', '.cc', '.cxx', '.c++', '.hpp', '.h', '.hh', '.hxx', '.h++', - '.qml', '.js' - } - - def should_index_file(self, file_path: Path) -> bool: - """Check if a file should be indexed.""" - return file_path.suffix.lower() in self.file_extensions - - def index_file(self, file_path: str, debug: bool = False, force: bool = False) -> List[Symbol]: - """Index a single file.""" - file_path_obj = Path(file_path) - - if not file_path_obj.exists(): - if debug: - print(f"Debug: File does not exist: {file_path}", file=sys.stderr) - return [] - - # Check cache first (unless force is True) - if not force and self.cache.is_file_cached(file_path): - if debug: - print(f"Debug: Using cached symbols for {file_path}", file=sys.stderr) - return self.cache.get_symbols(file_path) - - # Parse file based on extension - symbols = [] - if file_path_obj.suffix.lower() in {'.qml', '.js'}: - symbols = self.qml_parser.parse_file(file_path, debug=debug) - else: - symbols = self.cpp_parser.parse_file(file_path) - - if debug: - print(f"Debug: Found {len(symbols)} symbols in {file_path}", file=sys.stderr) - - # Update cache - self.cache.update_file(file_path, symbols) - return symbols - - def index_directory(self, directory: str = None, force_reindex: bool = False, debug: bool = False): - """Index all files in a directory tree.""" - if directory is None: - directory = self.root_dir - - directory = Path(directory).resolve() - - if debug: - print(f"Debug: Indexing directory {directory}", file=sys.stderr) - - indexed_count = 0 - skipped_count = 0 - - for root, dirs, files in os.walk(directory): - # Skip hidden directories and common cache directories - # Note: 'build' removed to allow indexing build directories if needed - dirs[:] = [d for d in dirs if not d.startswith('.') and d not in {'node_modules', '__pycache__', 'CMakeFiles', 'dist', 'target'}] - - for file in files: - file_path = Path(root) / file - if self.should_index_file(file_path): - file_str = str(file_path) - if force_reindex or not self.cache.is_file_cached(file_str): - print(f"Indexing: {file_str}") - symbols = self.index_file(file_str, debug=debug, force=force_reindex) - if symbols: - indexed_count += 1 - else: - if debug: - print(f"Debug: No symbols found in {file_str}", file=sys.stderr) - else: - skipped_count += 1 - if debug: - print(f"Debug: Skipping cached file: {file_str}", file=sys.stderr) - - self.cache.save() - print(f"Indexing complete. Indexed {indexed_count} files, skipped {skipped_count} cached files.") - print(f"Total files in cache: {len(self.cache.symbols)}") - - def find_symbol(self, symbol_name: str, exact_match: bool = False) -> List[Symbol]: - """Find a symbol by name.""" - results = [] - all_symbols = self.cache.get_all_symbols() - - for symbol in all_symbols: - if exact_match: - if symbol.name == symbol_name: - results.append(symbol) - else: - if symbol_name.lower() in symbol.name.lower(): - results.append(symbol) - - # Sort by relevance (exact matches first, then by name length) - results.sort(key=lambda s: (s.name != symbol_name, len(s.name), s.name)) - return results - - def find_definition(self, symbol_name: str) -> Optional[Symbol]: - """Find the most likely definition of a symbol.""" - symbols = self.find_symbol(symbol_name, exact_match=True) - - # Prioritize classes, then functions, then others - priority_order = ['class', 'namespace', 'function', 'enum', 'property', 'signal', 'variable'] - - for symbol_type in priority_order: - for symbol in symbols: - if symbol.symbol_type == symbol_type: - return symbol - - return symbols[0] if symbols else None - - def find_references(self, symbol_name: str) -> List[Tuple[str, int, str]]: - """Find all references to a symbol (simple grep-based).""" - references = [] - - for file_path in self.cache.symbols.keys(): - try: - with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: - lines = f.readlines() - for i, line in enumerate(lines, 1): - if re.search(r'\b' + re.escape(symbol_name) + r'\b', line): - references.append((file_path, i, line.strip())) - except Exception as e: - print(f"Error searching {file_path}: {e}", file=sys.stderr) - - return references - -def main(): - parser = argparse.ArgumentParser(description='Symbol finder for C++ and QML files') - parser.add_argument('--index', action='store_true', help='Index/reindex all files') - parser.add_argument('--force', action='store_true', help='Force reindex even if cached') - parser.add_argument('--find', metavar='SYMBOL', help='Find symbol by name') - parser.add_argument('--definition', metavar='SYMBOL', help='Find definition of symbol') - parser.add_argument('--references', metavar='SYMBOL', help='Find all references to symbol') - parser.add_argument('--exact', action='store_true', help='Exact match only') - parser.add_argument('--emacs', action='store_true', help='Output in Emacs format') - parser.add_argument('--root', metavar='DIR', default='.', help='Root directory to search') - parser.add_argument('--cache-dir', metavar='DIR', default='.symbol_cache', help='Cache directory') - parser.add_argument('--debug', action='store_true', help='Enable debug output') - parser.add_argument('--stats', action='store_true', help='Show cache statistics') - parser.add_argument('--list-files', action='store_true', help='List files that would be indexed') - - args = parser.parse_args() - - if args.debug: - print(f"Debug: Working directory: {os.getcwd()}", file=sys.stderr) - print(f"Debug: Root directory: {Path(args.root).resolve()}", file=sys.stderr) - print(f"Debug: Cache directory: {args.cache_dir}", file=sys.stderr) - - finder = SymbolFinder(args.root, args.cache_dir) - - if args.list_files: - # List all files that would be indexed - directory = Path(args.root).resolve() - print(f"Files that would be indexed from {directory}:") - count = 0 - for root, dirs, files in os.walk(directory): - dirs[:] = [d for d in dirs if not d.startswith('.') and d not in {'node_modules', '__pycache__', 'CMakeFiles', 'dist', 'target'}] - for file in files: - file_path = Path(root) / file - if finder.should_index_file(file_path): - print(f" {file_path}") - count += 1 - print(f"\nTotal: {count} files") - sys.exit(0) - - if args.stats: - print(f"Cache Statistics:") - print(f" Root: {finder.root_dir}") - print(f" Cache location: {finder.cache.cache_dir}") - print(f" Files indexed: {len(finder.cache.index)}") - total_symbols = sum(len(symbols) for symbols in finder.cache.symbols.values()) - print(f" Total symbols: {total_symbols}") - if finder.cache.index: - print(f"\nIndexed files:") - for file_path, info in list(finder.cache.index.items())[:10]: - print(f" {file_path}: {info.get('symbol_count', 0)} symbols") - if len(finder.cache.index) > 10: - print(f" ... and {len(finder.cache.index) - 10} more files") - sys.exit(0) - - if args.index: - finder.index_directory(force_reindex=args.force, debug=args.debug) - elif args.find: - symbols = finder.find_symbol(args.find, exact_match=args.exact) - if args.emacs: - # Emacs format: file:line:column: - for symbol in symbols: - print(f"{symbol.file_path}:{symbol.line_number}:1:{symbol.symbol_type} {symbol.name}") - else: - for symbol in symbols: - print(f"{symbol.name} ({symbol.symbol_type}) - {symbol.file_path}:{symbol.line_number}") - print(f" {symbol.context}") - elif args.definition: - symbol = finder.find_definition(args.definition) - if symbol: - if args.emacs: - print(f"{symbol.file_path}:{symbol.line_number}:1:") - else: - print(f"Definition: {symbol.name} ({symbol.symbol_type})") - print(f"Location: {symbol.file_path}:{symbol.line_number}") - print(f"Context: {symbol.context}") - else: - print(f"No definition found for '{args.definition}'") - sys.exit(1) - elif args.references: - refs = finder.find_references(args.references) - if args.emacs: - for file_path, line_num, context in refs: - print(f"{file_path}:{line_num}:1:{context}") - else: - for file_path, line_num, context in refs: - print(f"{file_path}:{line_num}: {context}") - else: - # Default: index if no cache exists - if not finder.cache.index: - print("No cache found. Indexing files...") - finder.index_directory() - else: - parser.print_help() - -if __name__ == '__main__': - main() \ No newline at end of file