tenseleyflow/shtick / f9006e1

Browse files

bug fixes

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
f9006e167885bdc2fce9f227f4eaeec347dad1ca
Parents
6f78635
Tree
babcdae

2 changed files

StatusFile+-
M src/shtick/config.py 120 29
M src/shtick/generator.py 77 8
src/shtick/config.pymodified
@@ -36,6 +36,63 @@ class Config:
3636
         """Get the output directory for generated shell files"""
3737
         return os.path.expanduser("~/.config/shtick")
3838
 
39
+    @staticmethod
40
+    def get_active_groups_file() -> str:
41
+        """Get the active groups state file path"""
42
+        return os.path.expanduser("~/.config/shtick/active_groups")
43
+
44
+    def load_active_groups(self) -> List[str]:
45
+        """Load list of currently active groups"""
46
+        active_file = self.get_active_groups_file()
47
+        if not os.path.exists(active_file):
48
+            return []
49
+
50
+        with open(active_file, "r") as f:
51
+            return [line.strip() for line in f if line.strip()]
52
+
53
+    def save_active_groups(self, active_groups: List[str]) -> None:
54
+        """Save list of active groups to state file"""
55
+        self.ensure_config_dir()
56
+        active_file = self.get_active_groups_file()
57
+
58
+        with open(active_file, "w") as f:
59
+            for group in active_groups:
60
+                f.write(f"{group}\n")
61
+
62
+    def activate_group(self, group_name: str) -> bool:
63
+        """Activate a group. Returns True if successful."""
64
+        # Check if group exists
65
+        if not self.get_group(group_name):
66
+            return False
67
+
68
+        active_groups = self.load_active_groups()
69
+        if group_name not in active_groups:
70
+            active_groups.append(group_name)
71
+            self.save_active_groups(active_groups)
72
+
73
+        return True
74
+
75
+    def deactivate_group(self, group_name: str) -> bool:
76
+        """Deactivate a group. Returns True if was active."""
77
+        active_groups = self.load_active_groups()
78
+        if group_name in active_groups:
79
+            active_groups.remove(group_name)
80
+            self.save_active_groups(active_groups)
81
+            return True
82
+        return False
83
+
84
+    def is_group_active(self, group_name: str) -> bool:
85
+        """Check if a group is currently active"""
86
+        return group_name in self.load_active_groups()
87
+
88
+    def get_persistent_group(self) -> Optional[GroupData]:
89
+        """Get the special 'persistent' group if it exists"""
90
+        return self.get_group("persistent")
91
+
92
+    def get_regular_groups(self) -> List[GroupData]:
93
+        """Get all groups except 'persistent'"""
94
+        return [group for group in self.groups if group.name != "persistent"]
95
+
3996
     def ensure_config_dir(self) -> None:
4097
         """Ensure the config directory exists"""
4198
         config_dir = os.path.dirname(self.config_path)
@@ -46,42 +103,76 @@ class Config:
46103
         if not os.path.exists(self.config_path):
47104
             raise FileNotFoundError(f"Config file not found: {self.config_path}")
48105
 
106
+        print(f"Debug: Loading config from: {self.config_path}")
107
+
49108
         with open(self.config_path, "rb") as f:
50109
             data = tomllib.load(f)
51110
 
111
+        print(f"Debug: Raw TOML data keys: {list(data.keys())}")
112
+        print(f"Debug: Raw TOML data structure: {data}")
113
+
52114
         self.groups = []
53115
 
54
-        # Parse groups from TOML structure like [group1.aliases]
55
-        group_data = {}
56
-
57
-        for key, value in data.items():
58
-            if "." in key:
59
-                group_name, data_type = key.split(".", 1)
60
-
61
-                if group_name not in group_data:
62
-                    group_data[group_name] = {
63
-                        "aliases": {},
64
-                        "env_vars": {},
65
-                        "functions": {},
66
-                    }
67
-
68
-                if data_type == "aliases":
69
-                    group_data[group_name]["aliases"] = value
70
-                elif data_type == "env_vars":
71
-                    group_data[group_name]["env_vars"] = value
72
-                elif data_type == "functions":
73
-                    group_data[group_name]["functions"] = value
74
-
75
-        # Convert to GroupData objects
76
-        for group_name, data in group_data.items():
77
-            self.groups.append(
78
-                GroupData(
79
-                    name=group_name,
80
-                    aliases=data["aliases"],
81
-                    env_vars=data["env_vars"],
82
-                    functions=data["functions"],
116
+        # Parse groups from nested TOML structure
117
+        for group_name, group_config in data.items():
118
+            print(f"Debug: Processing group '{group_name}' with config: {group_config}")
119
+
120
+            if not isinstance(group_config, dict):
121
+                print(f"Debug: Skipping non-dict value for key '{group_name}'")
122
+                continue
123
+
124
+            # Initialize group data
125
+            group_data = {"aliases": {}, "env_vars": {}, "functions": {}}
126
+
127
+            # Extract each section from the group
128
+            for section_name, section_data in group_config.items():
129
+                print(
130
+                    f"Debug: Processing section '{section_name}' in group '{group_name}': {section_data}"
83131
                 )
132
+
133
+                if section_name == "aliases" and isinstance(section_data, dict):
134
+                    group_data["aliases"] = section_data
135
+                    print(f"Debug: Added {len(section_data)} aliases to '{group_name}'")
136
+                elif section_name == "env_vars" and isinstance(section_data, dict):
137
+                    group_data["env_vars"] = section_data
138
+                    print(
139
+                        f"Debug: Added {len(section_data)} env_vars to '{group_name}'"
140
+                    )
141
+                elif section_name == "functions" and isinstance(section_data, dict):
142
+                    group_data["functions"] = section_data
143
+                    print(
144
+                        f"Debug: Added {len(section_data)} functions to '{group_name}'"
145
+                    )
146
+                else:
147
+                    print(
148
+                        f"Debug: Unknown or invalid section '{section_name}' in group '{group_name}'"
149
+                    )
150
+
151
+            # Create GroupData object if group has any items
152
+            total_items = (
153
+                len(group_data["aliases"])
154
+                + len(group_data["env_vars"])
155
+                + len(group_data["functions"])
84156
             )
157
+            print(f"Debug: Group '{group_name}' has {total_items} total items")
158
+
159
+            if total_items > 0:
160
+                new_group = GroupData(
161
+                    name=group_name,
162
+                    aliases=group_data["aliases"],
163
+                    env_vars=group_data["env_vars"],
164
+                    functions=group_data["functions"],
165
+                )
166
+                self.groups.append(new_group)
167
+                print(
168
+                    f"Debug: Created group '{group_name}' with {len(group_data['aliases'])} aliases, {len(group_data['env_vars'])} env_vars, {len(group_data['functions'])} functions"
169
+                )
170
+            else:
171
+                print(f"Warning: Group '{group_name}' has no items, skipping")
172
+
173
+        print(
174
+            f"Debug: Final groups loaded: {[g.name for g in self.groups]} (total: {len(self.groups)})"
175
+        )
85176
 
86177
     def save(self) -> None:
87178
         """Save the current configuration back to TOML file"""
src/shtick/generator.pymodified
@@ -83,15 +83,84 @@ class Generator:
8383
         for group in config.groups:
8484
             self.generate_for_group(group)
8585
 
86
+        # Generate the dynamic loader for all shells
87
+        self.generate_loader(config)
88
+
8689
         print(f"All done! Files generated in {self.output_base_dir}")
87
-        print("\nTo use these files, add lines like this to your shell config:")
88
-        print("  # For bash/zsh:")
89
-        print("  source ~/.config/shtick/GROUP_NAME/alias/aliases.bash")
90
-        print("  source ~/.config/shtick/GROUP_NAME/env/envvars.bash")
91
-        print("  source ~/.config/shtick/GROUP_NAME/function/functions.bash")
92
-        print("\n  # For fish:")
93
-        print("  source ~/.config/shtick/GROUP_NAME/alias/aliases.fish")
94
-        print("  # etc...")
90
+        self._print_usage_instructions(config)
91
+
92
+    def generate_loader(self, config: Config) -> None:
93
+        """Generate dynamic loader files that source persistent + active groups"""
94
+        print("Generating dynamic loader files...")
95
+
96
+        active_groups = config.load_active_groups()
97
+        persistent_group = config.get_persistent_group()
98
+
99
+        # Generate for each supported shell + default
100
+        all_shells = get_supported_shells() + ["default"]
101
+
102
+        for shell_name in all_shells:
103
+            loader_path = os.path.join(
104
+                self.output_base_dir, f"load_active.{shell_name}"
105
+            )
106
+
107
+            with open(loader_path, "w") as f:
108
+                f.write(f"# Shtick dynamic loader for {shell_name}\n")
109
+                f.write("# This file is auto-generated - do not edit\n\n")
110
+
111
+                # Source persistent group first (always active)
112
+                if persistent_group:
113
+                    f.write("# Load persistent configuration\n")
114
+                    for item_type in ["alias", "env", "function"]:
115
+                        file_path = f"$HOME/.config/shtick/persistent/{item_type}"
116
+                        f.write(f'[ -f "{file_path}/{item_type}s.{shell_name}" ] && ')
117
+                        f.write(f'source "{file_path}/{item_type}s.{shell_name}"\n')
118
+                    f.write("\n")
119
+
120
+                # Source active groups
121
+                if active_groups:
122
+                    f.write("# Load active groups\n")
123
+                    for group_name in active_groups:
124
+                        f.write(f"# Group: {group_name}\n")
125
+                        for item_type in ["alias", "env", "function"]:
126
+                            file_path = f"$HOME/.config/shtick/{group_name}/{item_type}"
127
+                            f.write(
128
+                                f'[ -f "{file_path}/{item_type}s.{shell_name}" ] && '
129
+                            )
130
+                            f.write(f'source "{file_path}/{item_type}s.{shell_name}"\n')
131
+                        f.write("\n")
132
+                else:
133
+                    f.write("# No active groups\n")
134
+
135
+    def _print_usage_instructions(self, config: Config) -> None:
136
+        """Print usage instructions for the user"""
137
+        active_groups = config.load_active_groups()
138
+        persistent_group = config.get_persistent_group()
139
+
140
+        print("\nTo use these configurations, add this line to your shell config:")
141
+        print("  # For bash/zsh (~/.bashrc or ~/.zshrc):")
142
+        print("  source ~/.config/shtick/load_active.bash")
143
+        print("\n  # For fish (~/.config/fish/config.fish):")
144
+        print("  source ~/.config/shtick/load_active.fish")
145
+
146
+        if persistent_group:
147
+            total_persistent = (
148
+                len(persistent_group.aliases)
149
+                + len(persistent_group.env_vars)
150
+                + len(persistent_group.functions)
151
+            )
152
+            print(f"\nPersistent items (always active): {total_persistent} total")
153
+
154
+        if active_groups:
155
+            print(f"Active groups: {', '.join(active_groups)}")
156
+        else:
157
+            print(
158
+                "No groups currently active. Use 'shtick activate <group>' to activate groups."
159
+            )
160
+
161
+        available_groups = [g.name for g in config.get_regular_groups()]
162
+        if available_groups:
163
+            print(f"Available groups: {', '.join(available_groups)}")
95164
 
96165
     def get_shell_files_for_group(self, group_name: str) -> Dict[str, List[str]]:
97166
         """Get list of generated shell files for a group"""