Python · 6726 bytes Raw Blame History
1 """
2 Settings management for shtick
3 """
4
5 import os
6 import tomllib
7 import logging
8 from pathlib import Path
9 from typing import Dict, List, Optional, Any
10 from dataclasses import dataclass, field
11
12 logger = logging.getLogger("shtick")
13
14
15 @dataclass
16 class GenerationSettings:
17 """Settings for file generation"""
18
19 shells: List[str] = field(default_factory=list) # Empty = auto-detect
20 parallel: bool = False # Threading not implemented yet
21 consolidate_files: bool = True
22
23
24 @dataclass
25 class BehaviorSettings:
26 """Settings for shtick behavior"""
27
28 auto_source_prompt: bool = True
29 check_conflicts: bool = True
30 backup_on_save: bool = False
31 interactive_mode: bool = True
32
33
34 @dataclass
35 class PerformanceSettings:
36 """Settings for performance optimization"""
37
38 cache_ttl: int = 300 # 5 minutes
39 lazy_load: bool = False # Not implemented yet
40 batch_operations: bool = True
41
42
43 class Settings:
44 """Manages shtick settings and preferences"""
45
46 # Singleton instance
47 _instance = None
48 _loaded = False
49
50 def __new__(cls):
51 if cls._instance is None:
52 cls._instance = super().__new__(cls)
53 return cls._instance
54
55 def __init__(self):
56 if not self._loaded:
57 self.generation = GenerationSettings()
58 self.behavior = BehaviorSettings()
59 self.performance = PerformanceSettings()
60 self._settings_path = self.get_settings_path()
61 self._load()
62 Settings._loaded = True
63
64 @staticmethod
65 def get_settings_path() -> str:
66 """Get the settings file path"""
67 return os.path.expanduser("~/.config/shtick/settings.toml")
68
69 def _load(self) -> None:
70 """Load settings from file if it exists"""
71 if not os.path.exists(self._settings_path):
72 logger.debug("No settings file found, using defaults")
73 return
74
75 try:
76 logger.debug(f"Loading settings from {self._settings_path}")
77 with open(self._settings_path, "rb") as f:
78 data = tomllib.load(f)
79
80 # Load generation settings
81 if "generation" in data:
82 gen_data = data["generation"]
83 self.generation.shells = gen_data.get("shells", [])
84 self.generation.parallel = gen_data.get("parallel", False)
85 self.generation.consolidate_files = gen_data.get(
86 "consolidate_files", True
87 )
88
89 # Load behavior settings
90 if "behavior" in data:
91 beh_data = data["behavior"]
92 self.behavior.auto_source_prompt = beh_data.get(
93 "auto_source_prompt", True
94 )
95 self.behavior.check_conflicts = beh_data.get("check_conflicts", True)
96 self.behavior.backup_on_save = beh_data.get("backup_on_save", False)
97 self.behavior.interactive_mode = beh_data.get("interactive_mode", True)
98
99 # Load performance settings
100 if "performance" in data:
101 perf_data = data["performance"]
102 self.performance.cache_ttl = perf_data.get("cache_ttl", 300)
103 self.performance.lazy_load = perf_data.get("lazy_load", False)
104 self.performance.batch_operations = perf_data.get(
105 "batch_operations", True
106 )
107
108 logger.debug("Settings loaded successfully")
109
110 except Exception as e:
111 logger.warning(f"Failed to load settings: {e}, using defaults")
112
113 def save(self) -> None:
114 """Save current settings to file"""
115 # Ensure directory exists
116 os.makedirs(os.path.dirname(self._settings_path), exist_ok=True)
117
118 # Build settings dictionary
119 settings_dict = {
120 "generation": {
121 "shells": self.generation.shells,
122 "parallel": self.generation.parallel,
123 "consolidate_files": self.generation.consolidate_files,
124 },
125 "behavior": {
126 "auto_source_prompt": self.behavior.auto_source_prompt,
127 "check_conflicts": self.behavior.check_conflicts,
128 "backup_on_save": self.behavior.backup_on_save,
129 "interactive_mode": self.behavior.interactive_mode,
130 },
131 "performance": {
132 "cache_ttl": self.performance.cache_ttl,
133 "lazy_load": self.performance.lazy_load,
134 "batch_operations": self.performance.batch_operations,
135 },
136 }
137
138 # Write TOML file
139 with open(self._settings_path, "w") as f:
140 f.write("# Shtick settings file\n")
141 f.write("# Generated automatically - edit as needed\n\n")
142
143 for section, values in settings_dict.items():
144 f.write(f"[{section}]\n")
145 for key, value in values.items():
146 if isinstance(value, bool):
147 f.write(f"{key} = {str(value).lower()}\n")
148 elif isinstance(value, list):
149 if value: # Non-empty list
150 f.write(f"{key} = {repr(value)}\n")
151 else:
152 f.write(f"{key} = [] # Empty = auto-detect\n")
153 else:
154 f.write(f"{key} = {value}\n")
155 f.write("\n")
156
157 def create_default_settings_file(self) -> None:
158 """Create a default settings file with comments"""
159 os.makedirs(os.path.dirname(self._settings_path), exist_ok=True)
160
161 content = """# Shtick settings file
162 # This file controls various shtick behaviors and optimizations
163
164 [generation]
165 # Shells to generate files for. Empty list = auto-detect based on current shell
166 shells = []
167 # Enable parallel generation (not implemented yet)
168 parallel = false
169 # Consolidate all items into single files per shell
170 consolidate_files = true
171
172 [behavior]
173 # Prompt to source changes after modifications
174 auto_source_prompt = true
175 # Check for conflicts when adding items
176 check_conflicts = true
177 # Create backups before saving config
178 backup_on_save = false
179 # Enable interactive prompts
180 interactive_mode = true
181
182 [performance]
183 # Cache time-to-live in seconds
184 cache_ttl = 300
185 # Enable lazy loading (not implemented yet)
186 lazy_load = false
187 # Enable batch operations for better performance
188 batch_operations = true
189 """
190
191 with open(self._settings_path, "w") as f:
192 f.write(content)
193
194 logger.info(f"Created default settings file at {self._settings_path}")
195
196 @classmethod
197 def reset(cls):
198 """Reset the singleton instance (useful for testing)"""
199 cls._instance = None
200 cls._loaded = False