-
Notifications
You must be signed in to change notification settings - Fork 2
Add directory-level configuration support and debugging #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThe changes introduce new directory configuration testing capabilities and debugging aids. A new Makefile target is added to run directory configuration tests, and a debug script is provided to set environment variables for debugging. The shell and executor modules now initialize, retrieve, update, and clean up configuration settings—including theme management—while reloading configurations on directory changes. Utility functions have been enhanced to search, reload, and free configuration resources. Additional tests and documentation updates, including a changelog entry, support these modifications. Changes
Sequence Diagram(s)sequenceDiagram
participant S as Shell Loop
participant C as Config System
S->>C: init_config_system()
S->>C: get_config_theme()
alt Theme differs from current
S->>C: load_theme(saved_theme)
end
S->>C: cleanup_config_system()
sequenceDiagram
participant U as User
participant E as Executor
participant C as Config System
U->>E: Execute "cd <directory>" command
E->>FS: chdir(<directory>)
alt chdir successful
E->>C: reload_directory_config()
end
Poem
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🔭 Outside diff range comments (1)
src/utils/config.c (1)
525-690:⚠️ Potential issueDuplicate code block detected.
Lines 525 onward appear to reintroduce previously declared functions (add_config_package,add_config_alias, etc.) with the same logic. This is likely accidental duplication. Removing or consolidating this repeated code will reduce confusion and potential maintenance issues.- // Lines 525-690 duplicate existing function implementations - // Consider removing duplicated block in its entirety
🧹 Nitpick comments (21)
README.md (1)
225-258: Well-documented directory configuration feature.The documentation clearly explains:
- The configuration hierarchy and precedence rules
- How to create directory-specific configurations
- Benefits of using directory-level configurations
- JSON structure with examples
There seems to be a stylistic issue in line 232 where the transition between sentences uses a hyphen. Consider replacing:
-Nutshell searches for a `.nutshell.json` configuration file in the current directory -If not found, it looks in parent directories until reaching your home directory -- Directory configs take precedence over user configs which take precedence over system configs +Nutshell searches for a `.nutshell.json` configuration file in the current directory +If not found, it looks in parent directories until reaching your home directory +- Directory configs take precedence over user configs which take precedence over system configs🧰 Tools
🪛 LanguageTool
[duplication] ~232-~232: Possible typo: you repeated a word.
Context: ...nt directories until reaching your home directory - Directory configs take precedence over user confi...(ENGLISH_WORD_REPEAT_RULE)
tests/test_config.c (4)
42-62: Check for potential race conditions when creating an empty config.
Though these tests run in a single-threaded environment, consider concurrency scenarios where multiple processes or threads might try to read/write the config simultaneously. A more robust approach might employ file locking if concurrency is expected.
64-82: Add additional assertions for error-prone scenarios.
While thetest_init_cleanup()function validatesglobal_config != NULLafter initialization andglobal_config == NULLafter cleanup, consider adding checks for possible failures ininit_config_system()(e.g., memory allocation errors) to strengthen the test coverage.
139-254: Optimize repeated calls tosave_config().
Functions likeset_config_theme(),add_config_package(), etc. are invoked frequently. Each final call triggers a disk write. Consider consolidating changes in memory and writing once at the end to reduce I/O overhead.
306-327: Consider grouping test suites more granularly.
While themain()function runs all tests in sequence, you might leverage a larger testing framework (like CUnit or Unity) for more transparent reporting, skipping, or filtering individual tests.tests/test_directory_config.c (3)
15-40: Check for truncated strings fromsnprintf.
When building paths (e.g.,snprintf(test_root, sizeof(test_root), "%s/.nutshell/test_dirs", home);), confirm there’s no overflow from extremely long HOME variables. Consider adding a safety check on the return value ofsnprintf.
97-123: Ensure backup behavior is consistent across platforms.
backup_user_config()renames the config file as.bakbut does not check for rename success or potential conflicts (e.g., if a.bakfile already exists).
251-267: Consider using a well-known testing framework.
Themain()function runs the directory config tests directly. For maintainability, a dedicated testing library can handle test fixtures, setup, teardown, and reporting.Would you like me to suggest or draft migration to a C-based testing framework (e.g., Unity, CUnit)?
src/utils/config.c (13)
28-55: Handle potential truncated strings when creating config directory paths.
snprintf(config_dir, sizeof(config_dir), "%s%s", home, USER_CONFIG_DIR);might be truncated ifhomeis large. Also, ensure to handle any errors creating or accessing the directory; it currently just returns if it fails.
57-81: Consider a short-circuit ifglobal_configis NULL at cleanup.
Currently, the code doesif (!global_config) return;but still runs cleanup logic for theme and other fields. The check is correct, but if the function grows more complex, you might short-circuit earlier or place additional protective checks to avoid future confusion.
83-220: Improve JSON parsing error messaging.
Whileload_config_from_file()logs JSON errors viaCONFIG_DEBUG, a user-friendly message is not surfaced to the user. Consider providing a clearer or optional user-facing error message if parsing fails.
222-255: Clarify merging strategy for multiple config files.
load_config_files()sequentially loads directory, user, and then system config, seemingly overriding earlier fields. If desired behavior is partial merging, more advanced handling might be necessary.
333-343: Check if immediate save is needed.
set_config_theme()callssave_config()directly. This might be inefficient if multiple changes happen in quick succession. Consider deferred writes or an explicit “apply config” approach once all changes are made.
379-399: Consider a deeper check to confirm package removal.
remove_config_package()returnsfalseif it is not found, but there’s no feedback to the caller that the package was never removed in practice, besides the boolean. Possibly add logging or an error code to clarify.
401-426: Alias collision policy.
When callingadd_config_alias()for an existing alias, old command is overwritten. If you want to preserve the user’s existing alias, or warn them, you could optionally log a message or perform some fallback.
428-450: Balancing performance with repeated saves.
Similar to packages, each alias add/remove triggers a full config save. Consider batching or delaying these writes if the config can grow large or if minimal I/O usage is desired.
452-470: Check for script path validity.
Before simply adding the script path, you could confirm that the file is executable or at least exists. This might reduce broken scripts in config.
472-492: Log when removing a non-existent script.
remove_config_script()silently returnsfalseif the script isn’t found. For diagnostics, consider logging a debug message like “Script [%s] not found for removal.”
495-497: Return a default theme if none is set?
get_config_theme()returnsNULLif no theme is configured. If a default fallback is desired (e.g. "default_theme"), consider returning that instead.
500-510: Enhance package checks to be case-insensitive.
If wanting to treat package names case-insensitively, consider normalizing them on add/remove and inis_package_enabled().
513-523: Avoid repeated linear scans for aliases.
Inget_alias_command(), consider using a dictionary or hash map to reduce O(n) searches each time. This might significantly improve performance with large alias counts.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
Makefile(2 hunks)scripts/debug_config.sh(1 hunks)src/core/shell.c(3 hunks)src/utils/config.c(1 hunks)src/utils/theme.c(2 hunks)tests/test_config.c(1 hunks)tests/test_theme.c(3 hunks)Makefile(2 hunks)src/core/executor.c(2 hunks)src/utils/config.c(2 hunks)tests/test_directory_config.c(1 hunks)README.md(1 hunks)CHANGELOG.md(1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md
[duplication] ~232-~232: Possible typo: you repeated a word.
Context: ...nt directories until reaching your home directory - Directory configs take precedence over user confi...
(ENGLISH_WORD_REPEAT_RULE)
🔇 Additional comments (19)
scripts/debug_config.sh (1)
1-11: Simple and effective debugging script.This script sets up debug environment variables and provides a way to run configuration tests with debugging enabled. The commented-out alternative command is a helpful example of how to run the shell with debugging.
src/core/executor.c (2)
6-6: LGTM! New include added for directory config functionality.The new include for
nutshell/config.his necessary for the configuration reload functionality.
75-80: Good implementation of config reloading on directory change.The code now reloads directory-specific configuration when successfully changing directories with the
cdcommand. This is essential for the directory-level configuration support mentioned in the PR objectives.src/utils/theme.c (2)
12-12: Appropriate inclusion of config header.Adding the config header inclusion is necessary for using the
set_config_themefunction.
930-935: Good integration of theme persistence with configuration system.The code now saves the selected theme to the configuration file and provides debug logging for success or failure. This enhances user experience by persisting theme preferences across sessions and implements part of the PR objectives for theme support.
CHANGELOG.md (1)
1-19: Well-structured changelog with all relevant information.The changelog properly documents the new features (directory-level configuration, configuration hierarchy, automatic config reloading, project-specific settings, and theme support per project) and fixed issues (memory leak and config loading order). This follows the Keep a Changelog format and provides clear information to users about what's changed.
Makefile (2)
43-43: Good addition to the PHONY targets.The addition of
test-dirconfigto the PHONY targets list is correct and maintains the pattern used for other test targets.
124-127: Correctly implements the directory configuration test target.The added target follows the established pattern of other test targets in the Makefile, with appropriate echo messaging and test execution.
src/core/shell.c (5)
7-7: Properly includes the config header.This header inclusion is necessary for accessing the configuration system functions.
63-67: Correctly initializes the configuration system.The code initializes the configuration system with appropriate debug logging. This is placed at the beginning of the shell_loop function, which is the right location for setup.
70-72: Good debug logging for theme initialization.Adding debug logging for theme system initialization enhances debugging capabilities, consistent with the PR objectives.
75-94: Theme loading from configuration is well-implemented.The implementation correctly:
- Retrieves the saved theme configuration
- Checks if it differs from the current theme
- Attempts to load the theme with proper error handling
- Provides detailed debug logs
279-280: Proper cleanup of configuration system.Ensures resources are released when the shell exits, maintaining symmetry with the initialization.
tests/test_theme.c (5)
4-4: Correctly includes the configuration header.Adding the config.h include is necessary for testing the integration between themes and configuration.
235-235: Properly initializes the configuration system for testing.Ensures the configuration system is set up before running theme command tests.
257-260: Good verification of theme persistence in configuration.The test now properly verifies that:
- The theme is saved to the configuration
- The saved theme is not null
- The saved theme matches the expected value
This ensures the integration between the theme and configuration systems works correctly.
270-270: Properly cleans up the configuration system.Maintains symmetry with initialization and prevents resource leaks.
349-349: Good addition of configuration debugging.Enables debugging for the configuration system alongside theme debugging, which helps with troubleshooting.
tests/test_config.c (1)
84-137: Verify partial JSON data and edge cases.
Intest_load_config(), the JSON is complete and valid. Consider adding tests for partially valid JSON, missing fields, or corrupted files to ensure robust error handling ininit_config_system().
| // Helper function to create a temporary config file for testing | ||
| static void create_test_config_file(const char *content) { | ||
| char *home = getenv("HOME"); | ||
| if (!home) { | ||
| printf("ERROR: HOME environment variable not set\n"); | ||
| return; | ||
| } | ||
|
|
||
| char test_config_dir[512]; | ||
| snprintf(test_config_dir, sizeof(test_config_dir), "%s/.nutshell", home); | ||
|
|
||
| struct stat st = {0}; | ||
| if (stat(test_config_dir, &st) == -1) { | ||
| mkdir(test_config_dir, 0700); | ||
| } | ||
|
|
||
| char test_config_path[512]; | ||
| snprintf(test_config_path, sizeof(test_config_path), "%s/.nutshell/config.json", home); | ||
|
|
||
| FILE *fp = fopen(test_config_path, "w"); | ||
| if (fp) { | ||
| fputs(content, fp); | ||
| fclose(fp); | ||
| printf("Created test config file: %s\n", test_config_path); | ||
| } else { | ||
| printf("ERROR: Failed to create test config file\n"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider error handling if directory creation fails.
The current logic calls mkdir(test_config_dir, 0700); without verifying success (e.g., checking errno). If the directory creation fails, subsequent operations might still proceed, potentially leading to silent errors or misleading debug messages.
26 if (stat(test_config_dir, &st) == -1) {
- mkdir(test_config_dir, 0700);
+ if (mkdir(test_config_dir, 0700) != 0) {
+ perror("ERROR: Failed to create test_config_dir");
+ return;
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Helper function to create a temporary config file for testing | |
| static void create_test_config_file(const char *content) { | |
| char *home = getenv("HOME"); | |
| if (!home) { | |
| printf("ERROR: HOME environment variable not set\n"); | |
| return; | |
| } | |
| char test_config_dir[512]; | |
| snprintf(test_config_dir, sizeof(test_config_dir), "%s/.nutshell", home); | |
| struct stat st = {0}; | |
| if (stat(test_config_dir, &st) == -1) { | |
| mkdir(test_config_dir, 0700); | |
| } | |
| char test_config_path[512]; | |
| snprintf(test_config_path, sizeof(test_config_path), "%s/.nutshell/config.json", home); | |
| FILE *fp = fopen(test_config_path, "w"); | |
| if (fp) { | |
| fputs(content, fp); | |
| fclose(fp); | |
| printf("Created test config file: %s\n", test_config_path); | |
| } else { | |
| printf("ERROR: Failed to create test config file\n"); | |
| } | |
| } | |
| // Helper function to create a temporary config file for testing | |
| static void create_test_config_file(const char *content) { | |
| char *home = getenv("HOME"); | |
| if (!home) { | |
| printf("ERROR: HOME environment variable not set\n"); | |
| return; | |
| } | |
| char test_config_dir[512]; | |
| snprintf(test_config_dir, sizeof(test_config_dir), "%s/.nutshell", home); | |
| struct stat st = {0}; | |
| if (stat(test_config_dir, &st) == -1) { | |
| if (mkdir(test_config_dir, 0700) != 0) { | |
| perror("ERROR: Failed to create test_config_dir"); | |
| return; | |
| } | |
| } | |
| char test_config_path[512]; | |
| snprintf(test_config_path, sizeof(test_config_path), "%s/.nutshell/config.json", home); | |
| FILE *fp = fopen(test_config_path, "w"); | |
| if (fp) { | |
| fputs(content, fp); | |
| fclose(fp); | |
| printf("Created test config file: %s\n", test_config_path); | |
| } else { | |
| printf("ERROR: Failed to create test config file\n"); | |
| } | |
| } |
| // Test update functions | ||
| static void test_update_functions() { | ||
| printf("Testing config update functions...\n"); | ||
|
|
||
| // Reset to an empty config file before starting this test | ||
| printf("DEBUG: Resetting to empty config file\n"); | ||
| create_empty_config_file(); | ||
|
|
||
| // Initialize with empty config | ||
| init_config_system(); | ||
|
|
||
| // Print initial state for debugging | ||
| printf("DEBUG: After initialization: script_count = %d\n", global_config->script_count); | ||
|
|
||
| // Test theme setting and getting | ||
| set_config_theme("new_theme"); | ||
| assert(strcmp(get_config_theme(), "new_theme") == 0); | ||
|
|
||
| // Test package functions | ||
| assert(is_package_enabled("test_pkg") == false); | ||
| add_config_package("test_pkg"); | ||
| assert(is_package_enabled("test_pkg") == true); | ||
| remove_config_package("test_pkg"); | ||
| assert(is_package_enabled("test_pkg") == false); | ||
|
|
||
| // Test alias functions | ||
| assert(get_alias_command("ta") == NULL); | ||
| add_config_alias("ta", "touch all"); | ||
| assert(strcmp(get_alias_command("ta"), "touch all") == 0); | ||
| add_config_alias("ta", "touch any"); // Update existing | ||
| assert(strcmp(get_alias_command("ta"), "touch any") == 0); | ||
| remove_config_alias("ta"); | ||
| assert(get_alias_command("ta") == NULL); | ||
|
|
||
| // Test script functions | ||
| assert(global_config->script_count == 0); | ||
| add_config_script("/test/script.sh"); | ||
| assert(global_config->script_count == 1); | ||
| add_config_script("/test/script.sh"); // Add again, should be ignored | ||
| assert(global_config->script_count == 1); | ||
| add_config_script("/test/script2.sh"); | ||
| assert(global_config->script_count == 2); | ||
| remove_config_script("/test/script.sh"); | ||
| assert(global_config->script_count == 1); | ||
| assert(strcmp(global_config->scripts[0], "/test/script2.sh") == 0); | ||
|
|
||
| cleanup_config_system(); | ||
| printf("Config update functions test passed!\n"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Validate capacity growth in alias and package arrays.
In test_update_functions(), multiple items are added and removed. Ensure that realloc usage in real code (i.e., config.c) handles repeated expansions effectively and gracefully handles memory fragmentation or reallocation failures.
+// Example snippet for more robust reallocation check:
+char **new_aliases = realloc(global_config->aliases, (newCount) * sizeof(char*));
+if (!new_aliases) {
+ // handle failure
+ return false;
+}Committable suggestion skipped: line range outside the PR's diff.
| static void test_directory_config_loading() { | ||
| printf("Testing directory config loading...\n"); | ||
|
|
||
| // Back up any existing user config | ||
| backup_user_config(false); | ||
|
|
||
| // Create the test directory structure | ||
| create_test_directory_structure(); | ||
|
|
||
| char *home = getenv("HOME"); | ||
| char test_root[512], parent_dir[512], child_dir[512], grandchild_dir[512]; | ||
| snprintf(test_root, sizeof(test_root), "%s/.nutshell/test_dirs", home); | ||
| snprintf(parent_dir, sizeof(parent_dir), "%s/parent", test_root); | ||
| snprintf(child_dir, sizeof(child_dir), "%s/parent/child", test_root); | ||
| snprintf(grandchild_dir, sizeof(grandchild_dir), "%s/parent/child/grandchild", test_root); | ||
|
|
||
| // Save current directory | ||
| char cwd[512]; | ||
| getcwd(cwd, sizeof(cwd)); | ||
|
|
||
| // Test parent directory config | ||
| printf("Testing in parent directory: %s\n", parent_dir); | ||
|
|
||
| // Make sure the config file exists | ||
| char parent_config[512]; | ||
| snprintf(parent_config, sizeof(parent_config), "%s/.nutshell.json", parent_dir); | ||
| printf("DEBUG: Checking parent config file: %s\n", parent_config); | ||
|
|
||
| FILE *check = fopen(parent_config, "r"); | ||
| if (check) { | ||
| char buffer[512] = {0}; | ||
| size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1, check); | ||
| fclose(check); | ||
| printf("DEBUG: Parent config content (%zu bytes):\n%s\n", bytes_read, buffer); | ||
| } else { | ||
| printf("DEBUG: Cannot open parent config for reading!\n"); | ||
| perror("Reason"); | ||
| } | ||
|
|
||
| // Execute the actual test | ||
| if (chdir(parent_dir) != 0) { | ||
| printf("DEBUG: Failed to chdir to %s\n", parent_dir); | ||
| perror("chdir"); | ||
| return; | ||
| } | ||
|
|
||
| printf("DEBUG: Current directory after chdir: "); | ||
| system("pwd"); | ||
| system("ls -la"); | ||
|
|
||
| // Initialize with debug enabled | ||
| setenv("NUT_DEBUG_CONFIG", "1", 1); | ||
| init_config_system(); | ||
|
|
||
| printf("DEBUG: After init_config_system()\n"); | ||
|
|
||
| assert(global_config != NULL); | ||
| printf("DEBUG: global_config is not NULL\n"); | ||
|
|
||
| // Check if theme was loaded | ||
| printf("DEBUG: global_config->theme = '%s'\n", | ||
| global_config->theme ? global_config->theme : "NULL"); | ||
|
|
||
| // Make sure the theme is not NULL | ||
| assert(global_config->theme != NULL); | ||
|
|
||
| // Check if it matches what we expect | ||
| assert(strcmp(global_config->theme, "parent_theme") == 0); | ||
|
|
||
| // Verify the alias from parent config | ||
| const char *parent_alias = get_alias_command("parent_alias"); | ||
| assert(parent_alias != NULL); | ||
| assert(strcmp(parent_alias, "echo parent") == 0); | ||
| cleanup_config_system(); | ||
|
|
||
| // Test child directory config | ||
| printf("Testing in child directory: %s\n", child_dir); | ||
| chdir(child_dir); | ||
| init_config_system(); | ||
| assert(global_config != NULL); | ||
| assert(global_config->theme != NULL); | ||
| assert(strcmp(global_config->theme, "child_theme") == 0); | ||
|
|
||
| // Verify the alias from child config | ||
| const char *child_alias = get_alias_command("child_alias"); | ||
| assert(child_alias != NULL); | ||
| assert(strcmp(child_alias, "echo child") == 0); | ||
| cleanup_config_system(); | ||
|
|
||
| // Test grandchild directory - should inherit from child | ||
| printf("Testing in grandchild directory (should inherit from child): %s\n", grandchild_dir); | ||
| chdir(grandchild_dir); | ||
| init_config_system(); | ||
| assert(global_config != NULL); | ||
| assert(global_config->theme != NULL); | ||
| assert(strcmp(global_config->theme, "child_theme") == 0); | ||
| cleanup_config_system(); | ||
|
|
||
| // Test config reload on directory change | ||
| // Start in child directory | ||
| printf("Testing config reload on directory change\n"); | ||
| chdir(child_dir); | ||
| init_config_system(); | ||
| assert(global_config != NULL); | ||
| assert(strcmp(global_config->theme, "child_theme") == 0); | ||
|
|
||
| // Change to parent directory and reload | ||
| chdir(parent_dir); | ||
| reload_directory_config(); | ||
| assert(strcmp(global_config->theme, "parent_theme") == 0); | ||
|
|
||
| // Change to grandchild directory and reload | ||
| chdir(grandchild_dir); | ||
| reload_directory_config(); | ||
| assert(strcmp(global_config->theme, "child_theme") == 0); | ||
|
|
||
| cleanup_config_system(); | ||
|
|
||
| // Restore original directory and user config | ||
| chdir(cwd); | ||
| backup_user_config(true); | ||
|
|
||
| printf("Directory config loading test passed!\n"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Inherited configuration logic.
The tests confirm directory-based configs override parent-level settings. Consider adding merges or partial overrides if required by the design. Right now, the code appears to simply load child config last, without merging both parent and child settings.
| char buffer[256]; | ||
| size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1, check); | ||
| buffer[bytes_read] = '\0'; | ||
| fclose(check); | ||
| CONFIG_DEBUG("First %zu bytes of config: %.100s...", bytes_read, buffer); | ||
| } else { | ||
| CONFIG_DEBUG("WARNING: Found config but cannot open for reading: %s", strerror(errno)); | ||
| } | ||
|
|
||
| strncpy(result_path, config_path, max_size); | ||
| free(dir_path); | ||
| return true; | ||
| } else { | ||
| CONFIG_DEBUG("Config file not found at: %s (%s)", config_path, strerror(errno)); | ||
| } | ||
|
|
||
| // Go up one directory level - completely rewritten to avoid memory issues | ||
| char *parent_dir = strdup(dir_path); | ||
| if (!parent_dir) { | ||
| CONFIG_DEBUG("Failed to allocate memory for parent_dir"); | ||
| free(dir_path); | ||
| return false; | ||
| } | ||
|
|
||
| // Use dirname() on the copy | ||
| char *dirname_result = dirname(parent_dir); | ||
|
|
||
| // If we've reached the root directory | ||
| if (strcmp(dir_path, dirname_result) == 0 || | ||
| strcmp(dirname_result, "/") == 0) { | ||
| CONFIG_DEBUG("Reached root directory or can't go up further"); | ||
| free(parent_dir); | ||
| free(dir_path); | ||
| return false; | ||
| } | ||
|
|
||
| // Replace current path with parent | ||
| free(dir_path); | ||
| dir_path = strdup(dirname_result); | ||
| free(parent_dir); | ||
|
|
||
| if (!dir_path) { | ||
| CONFIG_DEBUG("Failed to allocate memory for new dir_path"); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| CONFIG_DEBUG("No directory config found in path"); | ||
| if (dir_path) { | ||
| free(dir_path); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| // Load configuration from files with improved directory hierarchy search | ||
| bool load_config_files() { | ||
| bool loaded_any = false; | ||
|
|
||
| // Track which configuration sources were loaded | ||
| bool dir_loaded = false; | ||
| bool user_loaded = false; | ||
| bool system_loaded = false; | ||
|
|
||
| // Load in reverse precedence order: system (lowest) -> user -> directory (highest) | ||
|
|
||
| // Check for system config first (lowest precedence) | ||
| if (load_config_from_file(SYSTEM_CONFIG_FILE)) { | ||
| CONFIG_DEBUG("Loaded system config from: %s", SYSTEM_CONFIG_FILE); | ||
| system_loaded = true; | ||
| loaded_any = true; | ||
| } | ||
|
|
||
| // Check for user config next (medium precedence) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against race conditions in file writes.
save_config() repeatedly opens and writes the config. If multiple threads or processes write concurrently, you can lose changes or corrupt the file. Consider file locking or a last-writer-wins approach.
| if (load_config_from_file(dir_config_path)) { | ||
| CONFIG_DEBUG("Loaded directory-specific config from: %s", dir_config_path); | ||
| dir_loaded = true; | ||
| loaded_any = true; | ||
|
|
||
| // Store the directory where we found the config | ||
| if (global_config) { | ||
| char *dir_name = strdup(dirname(dir_config_path)); | ||
| CONFIG_DEBUG("Setting active config directory to: %s", dir_name); | ||
| // Store this path for later reference | ||
| free(dir_name); // Free the temporary string | ||
| } | ||
| } | ||
| } | ||
|
|
||
| CONFIG_DEBUG("Config loading summary: directory=%s, user=%s, system=%s", | ||
| dir_loaded ? "yes" : "no", | ||
| user_loaded ? "yes" : "no", | ||
| system_loaded ? "yes" : "no"); | ||
|
|
||
| return loaded_any; | ||
| } | ||
|
|
||
| // Force reload of configuration based on current directory | ||
| bool reload_directory_config() { | ||
| CONFIG_DEBUG("Reloading configuration for current directory"); | ||
|
|
||
| // Temporarily store current theme to preserve it if not overridden | ||
| char *current_theme = global_config && global_config->theme ? | ||
| strdup(global_config->theme) : NULL; | ||
|
|
||
| // Clear existing configuration but don't free the struct | ||
| cleanup_config_values(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Validate memory allocation before modifying global_config->enabled_packages.
When adding packages, ensure realloc success is properly handled to avoid potential null pointer dereferences on memory allocation failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
include/nutshell/config.h (4)
19-19: Consider thread safety implications of global configurationUsing a global configuration variable can simplify access across the codebase but may introduce thread safety concerns if Nutshell becomes multi-threaded in the future.
If multi-threading becomes necessary, consider implementing thread-local storage or a mutex-protected access pattern for this global configuration.
21-27: Good organization of configuration lifecycle functionsThe separation of initialization, cleanup, and loading functions shows good design. The comment about renaming
load_config_filesis helpful for understanding potential conflicts.Consider adding brief documentation about the expected return values and error handling approach for these functions.
44-47: Consider expanding accessor functions for complete configuration accessThe getter functions are a good start, but they only cover theme, package status, and alias commands. As the configuration system grows, you might need additional accessor functions for scripts and other properties.
Consider adding functions like
get_script_path_by_indexorget_script_countto provide complete access to all configuration elements.
1-49: Overall architecture supports the PR objectives wellThe header file provides a comprehensive foundation for the directory-level configuration system described in the PR objectives. The configuration structure and functions enable project-specific settings and themes as intended.
Consider adding a brief comment at the top of the file describing the overall purpose of the configuration system and how it integrates with the rest of Nutshell.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.gitignore(1 hunks)include/nutshell/config.h(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🔇 Additional comments (2)
include/nutshell/config.h (2)
40-42:✅ Verification successful
Good support for directory-level configuration
The addition of directory-level configuration support aligns well with the PR objectives. The
reload_directory_configandcleanup_config_valuesfunctions provide the necessary functionality to handle configuration changes when navigating between directories.It would be helpful to verify how these functions interact with the main configuration:
🏁 Script executed:
#!/bin/bash # Find calls to these functions to understand their usage in the codebase rg -A2 -B2 "reload_directory_config|cleanup_config_values" --type cLength of output: 2334
Action Confirmed: Directory-Level Configuration Integration Verified
The new functions
reload_directory_config()andcleanup_config_values()have been thoroughly validated:
Usage Verification:
- Tests in
tests/test_directory_config.cshow thatreload_directory_config()is invoked after directory changes, and configuration parameters update as expected.- In
src/core/executor.c, the correct call toreload_directory_config()is made after a successful directory change.Implementation Confirmation:
- The implementations in
src/utils/config.cproperly utilize these functions to clear and reload configuration values.This confirms that the directory-level configuration support aligns with the PR objectives and is well-integrated.
7-16:❓ Verification inconclusive
Config structure looks well-defined but consider memory management implications
The configuration structure is well-organized with clear comments. Since it uses multiple dynamically allocated arrays (enabled_packages, aliases, alias_commands, scripts), ensure proper memory management in the implementation to avoid memory leaks when adding/removing elements.
Consider whether a more structured approach might be better for aliases and their commands rather than parallel arrays:
🌐 Web query:
C struct containing array of structs vs parallel arrays💡 Result:
The choice between using a C struct containing an array of structs (array of structures, AoS) and parallel arrays (structure of arrays, SoA) depends on performance requirements, memory access patterns, and code maintainability. Here’s a breakdown:
1. Key Differences
Aspect Array of Structs (AoS) Struct of Arrays (SoA) Memory Layout Fields of a struct are interleaved in memory. Fields are stored in separate homogeneous arrays. Cache Locality Better for accessing multiple fields per element. Better for SIMD/vectorized operations on single fields. Maintainability Easier to reason about and debug (logical grouping). Error-prone due to index synchronization across arrays. Performance Worse for bulk operations on a single field. Faster for homogeneous data processing (e.g., GPUs).
2. When to Use Each Approach
Array of Structs (AoS)
- Use Case: Ideal when:
- Multiple fields of a struct are accessed together (e.g.,
x,y,zcoordinates of a point).- Code readability and maintainability are prioritized[1][2][5].
struct Point { float x, y, z; }; struct Point points[N];- Pros:
- Intuitive for programmers (logical grouping of related data)[6].
- Simplifies sorting and algorithms that act on entire records[5].
- Cons:
- Poor cache locality when iterating over a single field (e.g., all
xvalues)[2][4].Struct of Arrays (SoA)
- Use Case: Optimal for:
- SIMD/GPU workloads requiring homogeneous data processing (e.g., physics simulations)[2][4].
- Scenarios where a single field is accessed repeatedly across many elements.
struct Points { float x[N], y[N], z[N]; };- Pros:
- Enables vectorized operations and memory coalescing on GPUs[2][4].
- Reduces cache misses for bulk field operations[1][4].
- Cons:
- Complex to maintain (manual index management)[1][5].
- Inefficient for accessing multiple fields of a single element[1].
3. Performance Benchmarks
- In tests on Intel UHD Graphics GPUs, SoA was up to 40x faster than AoS for large datasets (16M elements)[4].
- For small datasets (<10k elements), differences are negligible[4].
4. Hybrid Approaches
- AoSoA (Array of Structs of Arrays):
- Balances AoS and SoA by grouping data into tiles (e.g., 8 elements per struct)[2].
- Example:
struct PointBlock { float x[8], y[8], z[8]; }; struct PointBlock blocks[N/8];- Improves cache locality while enabling SIMD-friendly access patterns[2].
5. Practical Recommendations
- Default to AoS for most applications due to readability and reduced error risk[1][5].
- Use SoA only when profiling confirms performance bottlenecks in field-specific loops[1][4].
- Consider AoSoA for high-performance computing (e.g., game engines, scientific simulations)[2].
Example Tradeoff
- AoS: Easy to sort points by
xusingqsort:qsort(points, N, sizeof(struct Point), compare_x);- SoA: Requires custom sorting logic to synchronize
x,y,zarrays, increasing code complexity[5].
In summary, prefer AoS for general use and SoA/AoSoA for performance-critical, field-specific operations. Always profile before optimizing[4].
Citations:
- 1: https://www.gamedevblog.com/2007/07/is-there-a-refe.html
- 2: https://en.wikipedia.org/wiki/AoS_and_SoA
- 3: https://discourse.julialang.org/t/array-of-structures-vs-structures-of-arrays/56936
- 4: https://community.intel.com/t5/Intel-oneAPI-Data-Parallel-C/Array-of-struct-vs-struct-of-arrays-performance-doesn-t-match-up/td-p/1415522
- 5: https://cboard.cprogramming.com/cplusplus-programming/147449-parallel-arrays-vs-array-structs.html
- 6: https://cplusplus.com/forum/beginner/145833/
Memory Management & Alias Design Improvement
The current
Configstructure is clearly defined, with appropriate comments regarding its dynamic arrays. Still, please ensure that all dynamic allocations (forenabled_packages,aliases,alias_commands, andscripts) are fully managed—properly freed or reallocated—to avoid memory leaks.Additionally, for the alias functionality, consider refactoring away from parallel arrays. For better maintainability and to reduce potential index synchronization issues, it might be beneficial to consolidate
aliasesandalias_commandsinto a single array of structs. For example:typedef struct { char *alias; char *command; } AliasEntry; typedef struct { char *theme; char **enabled_packages; int package_count; AliasEntry *aliases; // Replaces parallel arrays: aliases & alias_commands int alias_count; char **scripts; int script_count; } Config;This approach aligns with common practices—using an array of structs (AoS) for enhanced code clarity and reduced error risks unless specific performance optimizations (like SoA) are required.
Introduce a configuration system that supports directory-specific configurations, allowing project-specific settings and themes. Enhance debugging capabilities for configuration loading and provide documentation updates in the README and changelog.
Summary by CodeRabbit
New Features
Documentation
Bug Fixes