tenseleyflow/shtick / 7c214ef

Browse files

2 changed files

StatusFile+-
A src/shtick/py.typed 0 0
A src/shtick/shtick.py 509 0
src/shtick/py.typedadded
src/shtick/shtick.pyadded
@@ -0,0 +1,509 @@
1
+"""
2
+High-level API for shtick - provides a clean programmatic interface
3
+"""
4
+
5
+import os
6
+from typing import List, Dict, Optional, Union
7
+from .config import Config, GroupData
8
+from .generator import Generator
9
+from .commands import ShtickCommands
10
+
11
+
12
+class ShtickManager:
13
+    """
14
+    High-level interface for managing shtick configurations programmatically.
15
+
16
+    This class provides a clean Python API for all shtick operations without
17
+    needing to use the CLI. Perfect for integration into other tools, scripts,
18
+    or applications.
19
+
20
+    Example:
21
+        >>> from shtick import ShtickManager
22
+        >>> manager = ShtickManager()
23
+        >>> manager.add_persistent_alias('ll', 'ls -la')
24
+        True
25
+        >>> manager.activate_group('work')
26
+        True
27
+        >>> status = manager.get_status()
28
+        >>> print(status['active_groups'])
29
+        ['work']
30
+    """
31
+
32
+    def __init__(self, config_path: Optional[str] = None, debug: bool = False):
33
+        """
34
+        Initialize the ShtickManager.
35
+
36
+        Args:
37
+            config_path: Path to config file (uses default if None)
38
+            debug: Enable debug output
39
+        """
40
+        self.config_path = config_path or Config.get_default_config_path()
41
+        self.debug = debug
42
+        self._config = None
43
+        self._commands = ShtickCommands(debug=debug)
44
+
45
+    def _load_config(self, create_if_missing: bool = True) -> Config:
46
+        """Load or reload the configuration"""
47
+        try:
48
+            config = Config(self.config_path, debug=self.debug)
49
+            config.load()
50
+            self._config = config
51
+            return config
52
+        except FileNotFoundError:
53
+            if create_if_missing:
54
+                # Create empty config
55
+                config = Config(self.config_path, debug=self.debug)
56
+                config.ensure_config_dir()
57
+                config.save()
58
+                self._config = config
59
+                return config
60
+            else:
61
+                raise
62
+
63
+    def _get_config(self) -> Config:
64
+        """Get current config, loading if necessary"""
65
+        if self._config is None:
66
+            return self._load_config()
67
+        return self._config
68
+
69
+    def _save_and_regenerate(self) -> None:
70
+        """Save config and regenerate shell files"""
71
+        config = self._get_config()
72
+        config.save()
73
+
74
+        # Regenerate shell files
75
+        generator = Generator()
76
+        for group in config.groups:
77
+            generator.generate_for_group(group)
78
+        generator.generate_loader(config)
79
+
80
+    # Alias management
81
+    def add_persistent_alias(self, key: str, value: str) -> bool:
82
+        """
83
+        Add a persistent alias (always active).
84
+
85
+        Args:
86
+            key: Alias name
87
+            value: Alias command
88
+
89
+        Returns:
90
+            True if successful
91
+
92
+        Example:
93
+            >>> manager.add_persistent_alias('ll', 'ls -la')
94
+            True
95
+        """
96
+        try:
97
+            config = self._get_config()
98
+            config.add_item("alias", "persistent", key, value)
99
+            self._save_and_regenerate()
100
+            return True
101
+        except Exception as e:
102
+            if self.debug:
103
+                print(f"Error adding persistent alias: {e}")
104
+            return False
105
+
106
+    def add_alias(self, key: str, value: str, group: str) -> bool:
107
+        """
108
+        Add an alias to a specific group.
109
+
110
+        Args:
111
+            key: Alias name
112
+            value: Alias command
113
+            group: Group name
114
+
115
+        Returns:
116
+            True if successful
117
+        """
118
+        try:
119
+            config = self._get_config()
120
+            config.add_item("alias", group, key, value)
121
+            self._save_and_regenerate()
122
+            return True
123
+        except Exception as e:
124
+            if self.debug:
125
+                print(f"Error adding alias: {e}")
126
+            return False
127
+
128
+    def remove_alias(self, key: str, group: str = "persistent") -> bool:
129
+        """
130
+        Remove an alias from a group.
131
+
132
+        Args:
133
+            key: Alias name to remove
134
+            group: Group name (defaults to persistent)
135
+
136
+        Returns:
137
+            True if successful
138
+        """
139
+        try:
140
+            config = self._get_config()
141
+            success = config.remove_item("alias", group, key)
142
+            if success:
143
+                self._save_and_regenerate()
144
+            return success
145
+        except Exception as e:
146
+            if self.debug:
147
+                print(f"Error removing alias: {e}")
148
+            return False
149
+
150
+    # Environment variable management
151
+    def add_persistent_env(self, key: str, value: str) -> bool:
152
+        """
153
+        Add a persistent environment variable (always active).
154
+
155
+        Args:
156
+            key: Variable name
157
+            value: Variable value
158
+
159
+        Returns:
160
+            True if successful
161
+        """
162
+        try:
163
+            config = self._get_config()
164
+            config.add_item("env", "persistent", key, value)
165
+            self._save_and_regenerate()
166
+            return True
167
+        except Exception as e:
168
+            if self.debug:
169
+                print(f"Error adding persistent env var: {e}")
170
+            return False
171
+
172
+    def add_env(self, key: str, value: str, group: str) -> bool:
173
+        """
174
+        Add an environment variable to a specific group.
175
+
176
+        Args:
177
+            key: Variable name
178
+            value: Variable value
179
+            group: Group name
180
+
181
+        Returns:
182
+            True if successful
183
+        """
184
+        try:
185
+            config = self._get_config()
186
+            config.add_item("env", group, key, value)
187
+            self._save_and_regenerate()
188
+            return True
189
+        except Exception as e:
190
+            if self.debug:
191
+                print(f"Error adding env var: {e}")
192
+            return False
193
+
194
+    def remove_env(self, key: str, group: str = "persistent") -> bool:
195
+        """
196
+        Remove an environment variable from a group.
197
+
198
+        Args:
199
+            key: Variable name to remove
200
+            group: Group name (defaults to persistent)
201
+
202
+        Returns:
203
+            True if successful
204
+        """
205
+        try:
206
+            config = self._get_config()
207
+            success = config.remove_item("env", group, key)
208
+            if success:
209
+                self._save_and_regenerate()
210
+            return success
211
+        except Exception as e:
212
+            if self.debug:
213
+                print(f"Error removing env var: {e}")
214
+            return False
215
+
216
+    # Function management
217
+    def add_persistent_function(self, key: str, value: str) -> bool:
218
+        """
219
+        Add a persistent function (always active).
220
+
221
+        Args:
222
+            key: Function name
223
+            value: Function body
224
+
225
+        Returns:
226
+            True if successful
227
+        """
228
+        try:
229
+            config = self._get_config()
230
+            config.add_item("function", "persistent", key, value)
231
+            self._save_and_regenerate()
232
+            return True
233
+        except Exception as e:
234
+            if self.debug:
235
+                print(f"Error adding persistent function: {e}")
236
+            return False
237
+
238
+    def add_function(self, key: str, value: str, group: str) -> bool:
239
+        """
240
+        Add a function to a specific group.
241
+
242
+        Args:
243
+            key: Function name
244
+            value: Function body
245
+            group: Group name
246
+
247
+        Returns:
248
+            True if successful
249
+        """
250
+        try:
251
+            config = self._get_config()
252
+            config.add_item("function", group, key, value)
253
+            self._save_and_regenerate()
254
+            return True
255
+        except Exception as e:
256
+            if self.debug:
257
+                print(f"Error adding function: {e}")
258
+            return False
259
+
260
+    def remove_function(self, key: str, group: str = "persistent") -> bool:
261
+        """
262
+        Remove a function from a group.
263
+
264
+        Args:
265
+            key: Function name to remove
266
+            group: Group name (defaults to persistent)
267
+
268
+        Returns:
269
+            True if successful
270
+        """
271
+        try:
272
+            config = self._get_config()
273
+            success = config.remove_item("function", group, key)
274
+            if success:
275
+                self._save_and_regenerate()
276
+            return success
277
+        except Exception as e:
278
+            if self.debug:
279
+                print(f"Error removing function: {e}")
280
+            return False
281
+
282
+    # Group management
283
+    def activate_group(self, group: str) -> bool:
284
+        """
285
+        Activate a group.
286
+
287
+        Args:
288
+            group: Group name to activate
289
+
290
+        Returns:
291
+            True if successful
292
+        """
293
+        try:
294
+            config = self._get_config()
295
+            success = config.activate_group(group)
296
+            if success:
297
+                generator = Generator()
298
+                generator.generate_loader(config)
299
+            return success
300
+        except Exception as e:
301
+            if self.debug:
302
+                print(f"Error activating group: {e}")
303
+            return False
304
+
305
+    def deactivate_group(self, group: str) -> bool:
306
+        """
307
+        Deactivate a group.
308
+
309
+        Args:
310
+            group: Group name to deactivate
311
+
312
+        Returns:
313
+            True if successful
314
+        """
315
+        try:
316
+            config = self._get_config()
317
+            success = config.deactivate_group(group)
318
+            if success:
319
+                generator = Generator()
320
+                generator.generate_loader(config)
321
+            return success
322
+        except Exception as e:
323
+            if self.debug:
324
+                print(f"Error deactivating group: {e}")
325
+            return False
326
+
327
+    def get_active_groups(self) -> List[str]:
328
+        """
329
+        Get list of currently active groups.
330
+
331
+        Returns:
332
+            List of active group names
333
+        """
334
+        try:
335
+            config = self._get_config()
336
+            return config.load_active_groups()
337
+        except Exception:
338
+            return []
339
+
340
+    def get_groups(self) -> List[str]:
341
+        """
342
+        Get list of all available groups.
343
+
344
+        Returns:
345
+            List of all group names
346
+        """
347
+        try:
348
+            config = self._get_config()
349
+            return [group.name for group in config.groups]
350
+        except Exception:
351
+            return []
352
+
353
+    # Information and status
354
+    def get_status(self) -> Dict[str, Union[str, int, List[str], bool]]:
355
+        """
356
+        Get current shtick status.
357
+
358
+        Returns:
359
+            Dictionary with status information
360
+        """
361
+        try:
362
+            config = self._get_config()
363
+            persistent_group = config.get_persistent_group()
364
+            regular_groups = config.get_regular_groups()
365
+            active_groups = config.load_active_groups()
366
+
367
+            current_shell = os.path.basename(os.environ.get("SHELL", ""))
368
+            loader_exists = False
369
+            if current_shell:
370
+                loader_path = os.path.expanduser(
371
+                    f"~/.config/shtick/load_active.{current_shell}"
372
+                )
373
+                loader_exists = os.path.exists(loader_path)
374
+
375
+            persistent_count = 0
376
+            if persistent_group:
377
+                persistent_count = (
378
+                    len(persistent_group.aliases)
379
+                    + len(persistent_group.env_vars)
380
+                    + len(persistent_group.functions)
381
+                )
382
+
383
+            return {
384
+                "current_shell": current_shell,
385
+                "loader_exists": loader_exists,
386
+                "persistent_items": persistent_count,
387
+                "total_groups": len(regular_groups),
388
+                "active_groups": active_groups,
389
+                "available_groups": [g.name for g in regular_groups],
390
+                "config_path": self.config_path,
391
+            }
392
+        except Exception as e:
393
+            return {
394
+                "error": str(e),
395
+                "current_shell": "",
396
+                "loader_exists": False,
397
+                "persistent_items": 0,
398
+                "total_groups": 0,
399
+                "active_groups": [],
400
+                "available_groups": [],
401
+                "config_path": self.config_path,
402
+            }
403
+
404
+    def list_items(self, group: Optional[str] = None) -> List[Dict[str, str]]:
405
+        """
406
+        List all items, optionally filtered by group.
407
+
408
+        Args:
409
+            group: Optional group name to filter by
410
+
411
+        Returns:
412
+            List of dictionaries with item information
413
+        """
414
+        try:
415
+            config = self._get_config()
416
+            items = []
417
+
418
+            groups_to_check = [config.get_group(group)] if group else config.groups
419
+            groups_to_check = [g for g in groups_to_check if g is not None]
420
+
421
+            for g in groups_to_check:
422
+                # Add aliases
423
+                for key, value in g.aliases.items():
424
+                    items.append(
425
+                        {
426
+                            "group": g.name,
427
+                            "type": "alias",
428
+                            "key": key,
429
+                            "value": value,
430
+                            "active": config.is_group_active(g.name)
431
+                            or g.name == "persistent",
432
+                        }
433
+                    )
434
+
435
+                # Add env vars
436
+                for key, value in g.env_vars.items():
437
+                    items.append(
438
+                        {
439
+                            "group": g.name,
440
+                            "type": "env",
441
+                            "key": key,
442
+                            "value": value,
443
+                            "active": config.is_group_active(g.name)
444
+                            or g.name == "persistent",
445
+                        }
446
+                    )
447
+
448
+                # Add functions
449
+                for key, value in g.functions.items():
450
+                    items.append(
451
+                        {
452
+                            "group": g.name,
453
+                            "type": "function",
454
+                            "key": key,
455
+                            "value": value,
456
+                            "active": config.is_group_active(g.name)
457
+                            or g.name == "persistent",
458
+                        }
459
+                    )
460
+
461
+            return items
462
+        except Exception as e:
463
+            if self.debug:
464
+                print(f"Error listing items: {e}")
465
+            return []
466
+
467
+    def generate_shell_files(self) -> bool:
468
+        """
469
+        Regenerate all shell files.
470
+
471
+        Returns:
472
+            True if successful
473
+        """
474
+        try:
475
+            config = self._get_config()
476
+            generator = Generator()
477
+            for group in config.groups:
478
+                generator.generate_for_group(group)
479
+            generator.generate_loader(config)
480
+            return True
481
+        except Exception as e:
482
+            if self.debug:
483
+                print(f"Error generating shell files: {e}")
484
+            return False
485
+
486
+    def get_source_command(self, shell: Optional[str] = None) -> Optional[str]:
487
+        """
488
+        Get the source command for loading shtick in current session.
489
+
490
+        Args:
491
+            shell: Shell type (auto-detected if None)
492
+
493
+        Returns:
494
+            Source command string or None if not available
495
+        """
496
+        try:
497
+            current_shell = shell or os.path.basename(os.environ.get("SHELL", ""))
498
+            if not current_shell:
499
+                return None
500
+
501
+            loader_path = os.path.expanduser(
502
+                f"~/.config/shtick/load_active.{current_shell}"
503
+            )
504
+            if os.path.exists(loader_path):
505
+                return f"source {loader_path}"
506
+            else:
507
+                return None
508
+        except Exception:
509
+            return None