kicad-sch-api 0.3.4__py3-none-any.whl → 0.4.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.
Potentially problematic release.
This version of kicad-sch-api might be problematic. Click here for more details.
- kicad_sch_api/collections/__init__.py +21 -0
- kicad_sch_api/collections/base.py +294 -0
- kicad_sch_api/collections/components.py +434 -0
- kicad_sch_api/collections/junctions.py +366 -0
- kicad_sch_api/collections/labels.py +404 -0
- kicad_sch_api/collections/wires.py +406 -0
- kicad_sch_api/core/components.py +5 -0
- kicad_sch_api/core/formatter.py +3 -1
- kicad_sch_api/core/labels.py +348 -0
- kicad_sch_api/core/managers/__init__.py +26 -0
- kicad_sch_api/core/managers/file_io.py +243 -0
- kicad_sch_api/core/managers/format_sync.py +501 -0
- kicad_sch_api/core/managers/graphics.py +579 -0
- kicad_sch_api/core/managers/metadata.py +268 -0
- kicad_sch_api/core/managers/sheet.py +454 -0
- kicad_sch_api/core/managers/text_elements.py +536 -0
- kicad_sch_api/core/managers/validation.py +474 -0
- kicad_sch_api/core/managers/wire.py +346 -0
- kicad_sch_api/core/nets.py +310 -0
- kicad_sch_api/core/no_connects.py +276 -0
- kicad_sch_api/core/parser.py +75 -41
- kicad_sch_api/core/schematic.py +904 -1074
- kicad_sch_api/core/texts.py +343 -0
- kicad_sch_api/core/types.py +13 -4
- kicad_sch_api/geometry/font_metrics.py +3 -1
- kicad_sch_api/geometry/symbol_bbox.py +56 -43
- kicad_sch_api/interfaces/__init__.py +17 -0
- kicad_sch_api/interfaces/parser.py +76 -0
- kicad_sch_api/interfaces/repository.py +70 -0
- kicad_sch_api/interfaces/resolver.py +117 -0
- kicad_sch_api/parsers/__init__.py +14 -0
- kicad_sch_api/parsers/base.py +145 -0
- kicad_sch_api/parsers/label_parser.py +254 -0
- kicad_sch_api/parsers/registry.py +155 -0
- kicad_sch_api/parsers/symbol_parser.py +222 -0
- kicad_sch_api/parsers/wire_parser.py +99 -0
- kicad_sch_api/symbols/__init__.py +18 -0
- kicad_sch_api/symbols/cache.py +467 -0
- kicad_sch_api/symbols/resolver.py +361 -0
- kicad_sch_api/symbols/validators.py +504 -0
- {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/METADATA +1 -1
- kicad_sch_api-0.4.0.dist-info/RECORD +67 -0
- kicad_sch_api-0.3.4.dist-info/RECORD +0 -34
- {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Symbol cache interface and implementation for KiCAD symbol libraries.
|
|
3
|
+
|
|
4
|
+
Provides high-performance caching with clear separation between cache
|
|
5
|
+
management and symbol resolution concerns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import time
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
|
17
|
+
|
|
18
|
+
import sexpdata
|
|
19
|
+
|
|
20
|
+
from ..library.cache import LibraryStats, SymbolDefinition
|
|
21
|
+
from ..utils.validation import ValidationError
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ISymbolCache(ABC):
|
|
27
|
+
"""
|
|
28
|
+
Interface for symbol caching implementations.
|
|
29
|
+
|
|
30
|
+
Defines the contract for symbol caching without coupling to specific
|
|
31
|
+
inheritance resolution or validation logic.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def get_symbol(self, lib_id: str) -> Optional[SymbolDefinition]:
|
|
36
|
+
"""
|
|
37
|
+
Get a symbol by library ID.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
lib_id: Library identifier (e.g., "Device:R")
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Symbol definition if found, None otherwise
|
|
44
|
+
"""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def has_symbol(self, lib_id: str) -> bool:
|
|
49
|
+
"""
|
|
50
|
+
Check if symbol exists in cache.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
lib_id: Library identifier to check
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
True if symbol exists in cache
|
|
57
|
+
"""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def add_library_path(self, library_path: Union[str, Path]) -> bool:
|
|
62
|
+
"""
|
|
63
|
+
Add a library path to the cache.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
library_path: Path to .kicad_sym file
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if library was added successfully
|
|
70
|
+
"""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def get_library_symbols(self, library_name: str) -> List[str]:
|
|
75
|
+
"""
|
|
76
|
+
Get all symbol IDs from a specific library.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
library_name: Name of library
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
List of symbol lib_ids from the library
|
|
83
|
+
"""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def clear_cache(self) -> None:
|
|
88
|
+
"""Clear all cached symbols."""
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def get_cache_statistics(self) -> Dict[str, Any]:
|
|
93
|
+
"""
|
|
94
|
+
Get cache performance statistics.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Dictionary with cache statistics
|
|
98
|
+
"""
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class SymbolCache(ISymbolCache):
|
|
103
|
+
"""
|
|
104
|
+
High-performance symbol cache implementation.
|
|
105
|
+
|
|
106
|
+
Focuses purely on caching functionality without inheritance resolution,
|
|
107
|
+
which is handled by the SymbolResolver.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(self, cache_dir: Optional[Path] = None, enable_persistence: bool = True):
|
|
111
|
+
"""
|
|
112
|
+
Initialize the symbol cache.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
cache_dir: Directory to store cached symbol data
|
|
116
|
+
enable_persistence: Whether to persist cache to disk
|
|
117
|
+
"""
|
|
118
|
+
self._symbols: Dict[str, SymbolDefinition] = {}
|
|
119
|
+
self._library_paths: Set[Path] = set()
|
|
120
|
+
|
|
121
|
+
# Cache configuration
|
|
122
|
+
self._cache_dir = cache_dir or Path.home() / ".cache" / "kicad-sch-api" / "symbols"
|
|
123
|
+
self._enable_persistence = enable_persistence
|
|
124
|
+
|
|
125
|
+
if enable_persistence:
|
|
126
|
+
self._cache_dir.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
|
|
128
|
+
# Indexes for fast lookup
|
|
129
|
+
self._symbol_index: Dict[str, str] = {} # symbol_name -> lib_id
|
|
130
|
+
self._library_index: Dict[str, Path] = {} # library_name -> path
|
|
131
|
+
self._lib_stats: Dict[str, LibraryStats] = {}
|
|
132
|
+
|
|
133
|
+
# Performance tracking
|
|
134
|
+
self._cache_hits = 0
|
|
135
|
+
self._cache_misses = 0
|
|
136
|
+
self._total_load_time = 0.0
|
|
137
|
+
|
|
138
|
+
# Load persistent cache if available
|
|
139
|
+
self._index_file = self._cache_dir / "symbol_index.json" if enable_persistence else None
|
|
140
|
+
if enable_persistence:
|
|
141
|
+
self._load_persistent_index()
|
|
142
|
+
|
|
143
|
+
logger.info(f"Symbol cache initialized (persistence: {enable_persistence})")
|
|
144
|
+
|
|
145
|
+
def get_symbol(self, lib_id: str) -> Optional[SymbolDefinition]:
|
|
146
|
+
"""
|
|
147
|
+
Get a symbol by library ID.
|
|
148
|
+
|
|
149
|
+
Note: This returns the raw symbol without inheritance resolution.
|
|
150
|
+
Use SymbolResolver for fully resolved symbols.
|
|
151
|
+
"""
|
|
152
|
+
if lib_id in self._symbols:
|
|
153
|
+
self._cache_hits += 1
|
|
154
|
+
symbol = self._symbols[lib_id]
|
|
155
|
+
symbol.access_count += 1
|
|
156
|
+
symbol.last_accessed = time.time()
|
|
157
|
+
return symbol
|
|
158
|
+
|
|
159
|
+
self._cache_misses += 1
|
|
160
|
+
|
|
161
|
+
# Try to load from library
|
|
162
|
+
symbol = self._load_symbol_from_library(lib_id)
|
|
163
|
+
if symbol:
|
|
164
|
+
self._symbols[lib_id] = symbol
|
|
165
|
+
|
|
166
|
+
return symbol
|
|
167
|
+
|
|
168
|
+
def has_symbol(self, lib_id: str) -> bool:
|
|
169
|
+
"""Check if symbol exists in cache."""
|
|
170
|
+
if lib_id in self._symbols:
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
# Check if we can load it
|
|
174
|
+
return self._can_load_symbol(lib_id)
|
|
175
|
+
|
|
176
|
+
def add_library_path(self, library_path: Union[str, Path]) -> bool:
|
|
177
|
+
"""Add a library path to the cache."""
|
|
178
|
+
library_path = Path(library_path)
|
|
179
|
+
|
|
180
|
+
if not library_path.exists():
|
|
181
|
+
logger.warning(f"Library file not found: {library_path}")
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
if not library_path.suffix == ".kicad_sym":
|
|
185
|
+
logger.warning(f"Not a KiCAD symbol library: {library_path}")
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
if library_path in self._library_paths:
|
|
189
|
+
logger.debug(f"Library already in cache: {library_path}")
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
self._library_paths.add(library_path)
|
|
193
|
+
library_name = library_path.stem
|
|
194
|
+
self._library_index[library_name] = library_path
|
|
195
|
+
|
|
196
|
+
# Initialize library statistics
|
|
197
|
+
stat = library_path.stat()
|
|
198
|
+
self._lib_stats[library_name] = LibraryStats(
|
|
199
|
+
library_path=library_path,
|
|
200
|
+
file_size=stat.st_size,
|
|
201
|
+
last_modified=stat.st_mtime,
|
|
202
|
+
symbol_count=0, # Will be updated when library is loaded
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
logger.info(f"Added library: {library_name} ({library_path})")
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
def get_library_symbols(self, library_name: str) -> List[str]:
|
|
209
|
+
"""Get all symbol IDs from a specific library."""
|
|
210
|
+
if library_name not in self._library_index:
|
|
211
|
+
return []
|
|
212
|
+
|
|
213
|
+
# Load library if not already loaded
|
|
214
|
+
library_path = self._library_index[library_name]
|
|
215
|
+
symbols = []
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
with open(library_path, "r", encoding="utf-8") as f:
|
|
219
|
+
content = f.read()
|
|
220
|
+
|
|
221
|
+
parsed = sexpdata.loads(content, true=None, false=None, nil=None)
|
|
222
|
+
|
|
223
|
+
# Extract symbol names from parsed data
|
|
224
|
+
for item in parsed[1:]: # Skip first item which is 'kicad_symbol_lib'
|
|
225
|
+
if isinstance(item, list) and len(item) > 1:
|
|
226
|
+
if item[0] == sexpdata.Symbol("symbol"):
|
|
227
|
+
symbol_name = str(item[1]).strip('"')
|
|
228
|
+
lib_id = f"{library_name}:{symbol_name}"
|
|
229
|
+
symbols.append(lib_id)
|
|
230
|
+
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error(f"Error loading symbols from {library_name}: {e}")
|
|
233
|
+
|
|
234
|
+
return symbols
|
|
235
|
+
|
|
236
|
+
def clear_cache(self) -> None:
|
|
237
|
+
"""Clear all cached symbols."""
|
|
238
|
+
self._symbols.clear()
|
|
239
|
+
self._symbol_index.clear()
|
|
240
|
+
self._cache_hits = 0
|
|
241
|
+
self._cache_misses = 0
|
|
242
|
+
logger.info("Symbol cache cleared")
|
|
243
|
+
|
|
244
|
+
def get_cache_statistics(self) -> Dict[str, Any]:
|
|
245
|
+
"""Get cache performance statistics."""
|
|
246
|
+
total_requests = self._cache_hits + self._cache_misses
|
|
247
|
+
hit_rate = (self._cache_hits / total_requests * 100) if total_requests > 0 else 0
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
"symbols_cached": len(self._symbols),
|
|
251
|
+
"libraries_loaded": len(self._library_paths),
|
|
252
|
+
"cache_hits": self._cache_hits,
|
|
253
|
+
"cache_misses": self._cache_misses,
|
|
254
|
+
"hit_rate_percent": hit_rate,
|
|
255
|
+
"total_load_time": self._total_load_time,
|
|
256
|
+
"library_stats": {
|
|
257
|
+
name: {
|
|
258
|
+
"file_size": stats.file_size,
|
|
259
|
+
"symbols_count": stats.symbols_count,
|
|
260
|
+
"last_loaded": stats.last_loaded,
|
|
261
|
+
}
|
|
262
|
+
for name, stats in self._lib_stats.items()
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Private methods for implementation details
|
|
267
|
+
def _load_symbol_from_library(self, lib_id: str) -> Optional[SymbolDefinition]:
|
|
268
|
+
"""Load symbol from library file."""
|
|
269
|
+
if ":" not in lib_id:
|
|
270
|
+
logger.warning(f"Invalid lib_id format: {lib_id}")
|
|
271
|
+
return None
|
|
272
|
+
|
|
273
|
+
library_name, symbol_name = lib_id.split(":", 1)
|
|
274
|
+
|
|
275
|
+
if library_name not in self._library_index:
|
|
276
|
+
logger.debug(f"Library {library_name} not in cache")
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
library_path = self._library_index[library_name]
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
start_time = time.time()
|
|
283
|
+
|
|
284
|
+
with open(library_path, "r", encoding="utf-8") as f:
|
|
285
|
+
content = f.read()
|
|
286
|
+
|
|
287
|
+
parsed = sexpdata.loads(content, true=None, false=None, nil=None)
|
|
288
|
+
symbol_data = self._find_symbol_in_parsed_data(parsed, symbol_name)
|
|
289
|
+
|
|
290
|
+
if not symbol_data:
|
|
291
|
+
logger.debug(f"Symbol {symbol_name} not found in {library_name}")
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
# Create symbol definition without inheritance resolution
|
|
295
|
+
symbol = self._create_symbol_definition(symbol_data, lib_id, library_name)
|
|
296
|
+
|
|
297
|
+
load_time = time.time() - start_time
|
|
298
|
+
self._total_load_time += load_time
|
|
299
|
+
symbol.load_time = load_time
|
|
300
|
+
|
|
301
|
+
logger.debug(f"Loaded symbol {lib_id} in {load_time:.3f}s")
|
|
302
|
+
return symbol
|
|
303
|
+
|
|
304
|
+
except Exception as e:
|
|
305
|
+
logger.error(f"Error loading symbol {lib_id}: {e}")
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
def _find_symbol_in_parsed_data(self, parsed_data: List, symbol_name: str) -> Optional[List]:
|
|
309
|
+
"""Find symbol data in parsed library content."""
|
|
310
|
+
for item in parsed_data[1:]: # Skip first item which is 'kicad_symbol_lib'
|
|
311
|
+
if isinstance(item, list) and len(item) > 1:
|
|
312
|
+
if item[0] == sexpdata.Symbol("symbol"):
|
|
313
|
+
name = str(item[1]).strip('"')
|
|
314
|
+
if name == symbol_name:
|
|
315
|
+
return item
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
def _create_symbol_definition(
|
|
319
|
+
self, symbol_data: List, lib_id: str, library_name: str
|
|
320
|
+
) -> SymbolDefinition:
|
|
321
|
+
"""Create SymbolDefinition from parsed symbol data."""
|
|
322
|
+
symbol_name = str(symbol_data[1]).strip('"')
|
|
323
|
+
|
|
324
|
+
# Extract basic symbol properties
|
|
325
|
+
properties = self._extract_symbol_properties(symbol_data)
|
|
326
|
+
pins = self._extract_symbol_pins(symbol_data)
|
|
327
|
+
graphic_elements = self._extract_graphic_elements(symbol_data)
|
|
328
|
+
|
|
329
|
+
# Check for extends directive
|
|
330
|
+
extends = self._check_extends_directive(symbol_data)
|
|
331
|
+
|
|
332
|
+
return SymbolDefinition(
|
|
333
|
+
lib_id=lib_id,
|
|
334
|
+
name=symbol_name,
|
|
335
|
+
library=library_name,
|
|
336
|
+
reference_prefix=properties.get("reference_prefix", "U"),
|
|
337
|
+
description=properties.get("description", ""),
|
|
338
|
+
keywords=properties.get("keywords", ""),
|
|
339
|
+
datasheet=properties.get("datasheet", ""),
|
|
340
|
+
pins=pins,
|
|
341
|
+
units=properties.get("units", 1),
|
|
342
|
+
power_symbol=properties.get("power_symbol", False),
|
|
343
|
+
graphic_elements=graphic_elements,
|
|
344
|
+
raw_kicad_data=symbol_data,
|
|
345
|
+
extends=extends, # Store extends information for resolver
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
def _extract_symbol_properties(self, symbol_data: List) -> Dict[str, Any]:
|
|
349
|
+
"""Extract symbol properties from symbol data."""
|
|
350
|
+
properties = {
|
|
351
|
+
"reference_prefix": "U",
|
|
352
|
+
"description": "",
|
|
353
|
+
"keywords": "",
|
|
354
|
+
"datasheet": "",
|
|
355
|
+
"units": 1,
|
|
356
|
+
"power_symbol": False,
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
for item in symbol_data[1:]:
|
|
360
|
+
if isinstance(item, list) and len(item) >= 2:
|
|
361
|
+
key = str(item[0])
|
|
362
|
+
if key == "property":
|
|
363
|
+
prop_name = str(item[1]).strip('"')
|
|
364
|
+
prop_value = str(item[2]).strip('"') if len(item) > 2 else ""
|
|
365
|
+
|
|
366
|
+
if prop_name == "Reference":
|
|
367
|
+
# Extract prefix from reference like "R" from "R?"
|
|
368
|
+
ref = prop_value.rstrip("?")
|
|
369
|
+
if ref:
|
|
370
|
+
properties["reference_prefix"] = ref
|
|
371
|
+
elif prop_name == "ki_description":
|
|
372
|
+
properties["description"] = prop_value
|
|
373
|
+
elif prop_name == "ki_keywords":
|
|
374
|
+
properties["keywords"] = prop_value
|
|
375
|
+
elif prop_name == "ki_fp_filters":
|
|
376
|
+
properties["datasheet"] = prop_value
|
|
377
|
+
|
|
378
|
+
return properties
|
|
379
|
+
|
|
380
|
+
def _extract_symbol_pins(self, symbol_data: List) -> List:
|
|
381
|
+
"""Extract pins from symbol data."""
|
|
382
|
+
# For now, return empty list - pin extraction would be implemented here
|
|
383
|
+
# This would parse pin definitions from the symbol units
|
|
384
|
+
return []
|
|
385
|
+
|
|
386
|
+
def _extract_graphic_elements(self, symbol_data: List) -> List[Dict[str, Any]]:
|
|
387
|
+
"""Extract graphic elements from symbol data."""
|
|
388
|
+
# For now, return empty list - graphic element extraction would be implemented here
|
|
389
|
+
# This would parse rectangles, circles, arcs, etc. from symbol units
|
|
390
|
+
return []
|
|
391
|
+
|
|
392
|
+
def _check_extends_directive(self, symbol_data: List) -> Optional[str]:
|
|
393
|
+
"""Check if symbol has extends directive and return parent symbol name."""
|
|
394
|
+
if not isinstance(symbol_data, list):
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
for item in symbol_data[1:]:
|
|
398
|
+
if isinstance(item, list) and len(item) >= 2:
|
|
399
|
+
if str(item[0]) == "extends":
|
|
400
|
+
parent_name = str(item[1]).strip('"')
|
|
401
|
+
logger.debug(f"Found extends directive: {parent_name}")
|
|
402
|
+
return parent_name
|
|
403
|
+
return None
|
|
404
|
+
|
|
405
|
+
def _can_load_symbol(self, lib_id: str) -> bool:
|
|
406
|
+
"""Check if symbol can be loaded without actually loading it."""
|
|
407
|
+
if ":" not in lib_id:
|
|
408
|
+
return False
|
|
409
|
+
|
|
410
|
+
library_name, _ = lib_id.split(":", 1)
|
|
411
|
+
return library_name in self._library_index
|
|
412
|
+
|
|
413
|
+
def _load_persistent_index(self) -> None:
|
|
414
|
+
"""Load persistent index from disk."""
|
|
415
|
+
if not self._index_file or not self._index_file.exists():
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
with open(self._index_file, "r") as f:
|
|
420
|
+
index_data = json.load(f)
|
|
421
|
+
|
|
422
|
+
# Restore symbol index
|
|
423
|
+
self._symbol_index = index_data.get("symbol_index", {})
|
|
424
|
+
|
|
425
|
+
# Restore library paths that still exist
|
|
426
|
+
for lib_path_str in index_data.get("library_paths", []):
|
|
427
|
+
lib_path = Path(lib_path_str)
|
|
428
|
+
if lib_path.exists():
|
|
429
|
+
self._library_paths.add(lib_path)
|
|
430
|
+
self._library_index[lib_path.stem] = lib_path
|
|
431
|
+
|
|
432
|
+
logger.debug(f"Loaded persistent index with {len(self._symbol_index)} symbols")
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
logger.warning(f"Failed to load persistent index: {e}")
|
|
436
|
+
|
|
437
|
+
def save_persistent_index(self) -> None:
|
|
438
|
+
"""Save current index to disk."""
|
|
439
|
+
if not self._enable_persistence or not self._index_file:
|
|
440
|
+
return
|
|
441
|
+
|
|
442
|
+
try:
|
|
443
|
+
index_data = {
|
|
444
|
+
"symbol_index": self._symbol_index,
|
|
445
|
+
"library_paths": [str(path) for path in self._library_paths],
|
|
446
|
+
"created": time.time(),
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
with open(self._index_file, "w") as f:
|
|
450
|
+
json.dump(index_data, f, indent=2)
|
|
451
|
+
|
|
452
|
+
logger.debug("Saved persistent index")
|
|
453
|
+
|
|
454
|
+
except Exception as e:
|
|
455
|
+
logger.warning(f"Failed to save persistent index: {e}")
|
|
456
|
+
|
|
457
|
+
def _find_symbol_in_parsed_data(self, parsed_data: List, symbol_name: str) -> Optional[List]:
|
|
458
|
+
"""Find symbol data in parsed library data."""
|
|
459
|
+
if not isinstance(parsed_data, list):
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
for item in parsed_data:
|
|
463
|
+
if isinstance(item, list) and len(item) >= 2:
|
|
464
|
+
if str(item[0]) == "symbol" and str(item[1]) == symbol_name:
|
|
465
|
+
return item
|
|
466
|
+
|
|
467
|
+
return None
|