diff --git a/src/goldfish.hpp b/src/goldfish.hpp index dd739ab25..c4a61b4bd 100644 --- a/src/goldfish.hpp +++ b/src/goldfish.hpp @@ -3940,21 +3940,67 @@ find_gfproject_json (const char* gf_lib) { static json load_gfproject_config (const char* gf_lib) { - fs::path config_path= find_gfproject_json (gf_lib); - if (config_path.empty ()) { + // Find both gfproject.json files if they exist + std::error_code ec; + fs::path cwd= fs::current_path (ec); + fs::path local_path= cwd / "gfproject.json"; + bool local_exists= !ec && fs::exists (local_path, ec) && fs::is_regular_file (local_path, ec); + + fs::path lib_path= fs::path (gf_lib) / "gfproject.json"; + bool lib_exists= fs::exists (lib_path, ec) && fs::is_regular_file (lib_path, ec); + + // If neither exists, return empty object + if (!local_exists && !lib_exists) { return json::object (); } - try { - std::ifstream file (config_path); - if (!file.is_open ()) { - return json::object (); - } - json config; - file >> config; - return config; - } catch (...) { - return json::object (); + + // Start with lib config as base (only tools section will be merged) + json merged= json::object (); + json lib_tools= json::object (); + if (lib_exists) { + try { + std::ifstream lib_file (lib_path); + if (lib_file.is_open ()) { + json lib_config; + lib_file >> lib_config; + if (lib_config.contains ("tools")) { + lib_tools= lib_config["tools"]; + } + } + } catch (...) { + // Ignore parse errors for lib config + } + } + + // Get local tools (local takes precedence) + json local_tools= json::object (); + if (local_exists) { + try { + std::ifstream local_file (local_path); + if (local_file.is_open ()) { + json local_config; + local_file >> local_config; + if (local_config.contains ("tools")) { + local_tools= local_config["tools"]; + } + } + } catch (...) { + // Ignore parse errors for local config + } + } + + // Merge tools: lib as base, local overrides (except "help" which is always internal) + json merged_tools= lib_tools; + for (auto& [key, value] : local_tools.items ()) { + // "help" tool is never merged - always use internal implementation + if (key == "help") { + continue; + } + merged_tools[key]= value; } + merged["tools"]= merged_tools; + + return merged; } static string @@ -3989,13 +4035,9 @@ goldfish_run_tool (s7_scheme* sc, const char* gf_lib, const string& command, json tool_config= config["tools"][command]; // Check if tool has implementation (organization and module) + // If not complete, fall back to internal implementation (return -1) if (!tool_config.contains ("organization") || !tool_config.contains ("module")) { - cerr << "Error: Tool '" << command << "' is not fully implemented (missing organization or module)." - << endl; - s7_close_output_port (sc, s7_current_error_port (sc)); - s7_set_current_error_port (sc, old_port); - if (gc_loc != -1) s7_gc_unprotect_at (sc, gc_loc); - return 1; + return -1; // Fall back to internal implementation } string org = tool_config["organization"]; @@ -4003,11 +4045,7 @@ goldfish_run_tool (s7_scheme* sc, const char* gf_lib, const string& command, string tool_root= find_tool_root_by_command (gf_lib, command); if (tool_root.empty ()) { - cerr << "Error: tools/" << command << "/" << org << " directory not found." << endl; - s7_close_output_port (sc, s7_current_error_port (sc)); - s7_set_current_error_port (sc, old_port); - if (gc_loc != -1) s7_gc_unprotect_at (sc, gc_loc); - return 1; + return -1; // Fall back to internal implementation } s7_add_to_load_path (sc, tool_root.c_str ()); @@ -5315,8 +5353,9 @@ repl_for_community_edition (s7_scheme* sc, int argc, char** argv) { } // 处理动态注册的工具(从 gfproject.json 加载) + // 注意:"help" 命令始终使用内部实现,不从 gfproject.json 加载 json gfproject_config = load_gfproject_config (gf_lib); - if (gfproject_config.contains ("tools") && gfproject_config["tools"].contains (command)) { + if (command != "help" && gfproject_config.contains ("tools") && gfproject_config["tools"].contains (command)) { int tool_ret = goldfish_run_tool (sc, gf_lib, command, errmsg, old_port, gc_loc); if (tool_ret != -1) { // Tool was found and executed (or failed with an error) @@ -5325,6 +5364,15 @@ repl_for_community_edition (s7_scheme* sc, int argc, char** argv) { // If tool_ret == -1, tool config exists but execution failed, fall through to check other commands } + // 处理 help 命令(始终使用内部实现) + if (command == "help") { + display_help (); + s7_close_output_port (sc, s7_current_error_port (sc)); + s7_set_current_error_port (sc, old_port); + if (gc_loc != -1) s7_gc_unprotect_at (sc, gc_loc); + return 0; + } + // 处理 eval 子命令 if (command == "eval") { if (argc < command_index + 1) { diff --git a/tools/help/liii/goldhelp.scm b/tools/help/liii/goldhelp.scm index a791c0358..2df9ceaed 100644 --- a/tools/help/liii/goldhelp.scm +++ b/tools/help/liii/goldhelp.scm @@ -35,23 +35,79 @@ ) ;export (begin - (define (find-gfproject-path) - "Search for gfproject.json in current directory" + (define (find-gfproject-path kind) + "Search for gfproject.json - 'local or 'lib" (let ((cwd (getcwd))) - (if cwd - (let ((test-path (path->string (path-join (path cwd) (path "gfproject.json"))))) - (if (file-exists? test-path) - test-path - #f)) - #f))) + (cond + ((eq? kind 'local) + (if cwd + (let ((test-path (path->string (path-join (path cwd) (path "gfproject.json"))))) + (if (file-exists? test-path) + test-path + #f)) + #f)) + ((eq? kind 'lib) + ;; First try GF_LIB environment variable + (let ((gf-lib (getenv "GF_LIB"))) + (if gf-lib + (let ((lib-path (path->string (path-join (path gf-lib) (path "gfproject.json"))))) + (if (file-exists? lib-path) + lib-path + #f)) + ;; Fallback: search from executable location upward + (let ((exe-path (executable))) + (if exe-path + (let search ((dir (path-dirname (path exe-path)))) + (if dir + (let ((gfproject-path (path->string (path-join dir (path "gfproject.json"))))) + (if (file-exists? gfproject-path) + gfproject-path + (search (path-dirname dir)))) + #f)) + #f))))) + (else #f)))) + + (define (json-merge-tools lib-tools local-tools) + "Merge two tools JSON objects, local takes precedence (except 'help')" + (cond + ((json-null? lib-tools) + (if (json-null? local-tools) + (make-json) + local-tools)) + ((json-null? local-tools) + lib-tools) + (else + ;; Start with lib tools as base, then add local tools (except "help") + (let ((merged (make-json))) + ;; Add lib tools (except "help") + (for-each + (lambda (key) + (when (not (string=? key "help")) + (json-set! merged key (json-ref lib-tools key)))) + (json-keys lib-tools)) + ;; Add local tools (except "help"), overwriting lib values + (for-each + (lambda (key) + (when (not (string=? key "help")) + (json-set! merged key (json-ref local-tools key)))) + (json-keys local-tools)) + merged))))) (define (load-gfproject) - "Load and parse gfproject.json, return tools object" - (let ((path (find-gfproject-path))) - (if path - (let ((content (path-read-text path))) - (string->json content)) - (string->json "{}")))) + "Load and merge gfproject.json from local and lib, local takes precedence" + (let ((local-path (find-gfproject-path 'local)) + (lib-path (find-gfproject-path 'lib))) + (let ((lib-config (if lib-path + (let ((content (path-read-text lib-path))) + (string->json content)) + (make-json))) + (local-config (if local-path + (let ((content (path-read-text local-path))) + (string->json content)) + (make-json)))) + (let ((lib-tools (json-ref lib-config "tools")) + (local-tools (json-ref local-config "tools"))) + (json-merge-tools lib-tools local-tools))))) (define (get-tool-description tools tool-name lang) "Get description for a tool in specified language"