diff --git a/master_changes.md b/master_changes.md index a39d475e315..7589177e6c0 100644 --- a/master_changes.md +++ b/master_changes.md @@ -69,6 +69,7 @@ users) ## Clean ## Env + * [NEW] Add support for nushell in the opam env command ## Opamfile * The `url` file now only supports the legacy opam 1.2 fields [#6827 @kit-ty-kate] diff --git a/src/client/opamArg.ml b/src/client/opamArg.ml index a2783006409..e8905644fb9 100644 --- a/src/client/opamArg.ml +++ b/src/client/opamArg.ml @@ -1155,6 +1155,7 @@ let shell_opt ?section cli validity = None,"csh",SH_csh; None,"zsh",SH_zsh; None,"fish",SH_fish; + None,"nu",SH_nu; Some cli2_2,"pwsh",SH_pwsh Powershell_pwsh; Some cli2_2,"cmd",SH_cmd; Some cli2_2,"powershell",SH_pwsh Powershell diff --git a/src/client/opamClient.ml b/src/client/opamClient.ml index 16d9368a35f..db9ba3b1355 100644 --- a/src/client/opamClient.ml +++ b/src/client/opamClient.ml @@ -1137,7 +1137,7 @@ let rec cygwin_menu ~bypass_checks ~interactive header = options, internal_option, Some warning | None -> match OpamStd.Sys.guess_shell_compat () with - | SH_sh | SH_bash | SH_zsh | SH_csh | SH_fish -> + | SH_sh | SH_bash | SH_zsh | SH_csh | SH_fish | SH_nu -> let default, warning = if kind = `Msys2 && OpamSystem.resolve_command pacman = None then internal_option, Printf.sprintf diff --git a/src/client/opamCommands.ml b/src/client/opamCommands.ml index cac6ffbeb8d..0ef13e2a324 100644 --- a/src/client/opamCommands.ml +++ b/src/client/opamCommands.ml @@ -1448,7 +1448,7 @@ let config cli = `Ok (OpamConfigCommand.env gt sw ~set_opamroot ~set_opamswitch ~csh:(shell=SH_csh) ~sexp ~fish:(shell=SH_fish) - ~pwsh ~cmd:(shell=SH_cmd) + ~pwsh ~cmd:(shell=SH_cmd) ~nu:(shell=SH_nu) ~inplace_path)) | Some `revert_env, [] -> OpamGlobalState.with_ `Lock_none @@ fun gt -> @@ -1458,7 +1458,7 @@ let config cli = `Ok (OpamConfigCommand.ensure_env gt sw; OpamConfigCommand.print_eval_env ~csh:(shell=SH_csh) ~sexp ~fish:(shell=SH_fish) - ~pwsh ~cmd:(shell=SH_cmd) + ~pwsh ~cmd:(shell=SH_cmd) ~nu:(shell=SH_nu) (OpamEnv.add [] []))) | Some `list, [] -> OpamGlobalState.with_ `Lock_none @@ fun gt -> @@ -1763,12 +1763,12 @@ let env cli = OpamConfigCommand.env gt sw ~set_opamroot ~set_opamswitch ~csh:(shell=SH_csh) ~sexp ~fish:(shell=SH_fish) - ~pwsh ~cmd:(shell=SH_cmd) + ~pwsh ~cmd:(shell=SH_cmd) ~nu:(shell=SH_nu) ~inplace_path); | true -> OpamConfigCommand.print_eval_env ~csh:(shell=SH_csh) ~sexp ~fish:(shell=SH_fish) - ~pwsh ~cmd:(shell=SH_cmd) + ~pwsh ~cmd:(shell=SH_cmd) ~nu:(shell=SH_nu) (OpamEnv.add [] []) in let open Common_config_flags in diff --git a/src/client/opamConfigCommand.ml b/src/client/opamConfigCommand.ml index 1fd67fef36c..13cd86e3811 100644 --- a/src/client/opamConfigCommand.ml +++ b/src/client/opamConfigCommand.ml @@ -131,6 +131,10 @@ let print_sexp_env output env = aux env; output ")\n" +let print_nu_env output env = + let json = `O (List.map (fun (k,v,_) -> (k, `String v)) env) in + output @@ OpamJson.to_string json + let rec print_fish_env output env = let set_arr_cmd ?(modf=fun x -> x) k v = let v = modf @@ OpamStd.String.split v ':' in @@ -183,7 +187,7 @@ let print_without_cr s = output_string stdout s; flush stdout -let print_eval_env ~csh ~sexp ~fish ~pwsh ~cmd env = +let print_eval_env ~csh ~sexp ~fish ~pwsh ~cmd ~nu env = let env = (env : OpamTypes.env :> (string * string * string option) list) in let output_normally = OpamConsole.msg "%s" in let never_with_cr = @@ -202,6 +206,9 @@ let print_eval_env ~csh ~sexp ~fish ~pwsh ~cmd env = print_pwsh_env output_normally env else if cmd then print_cmd_env output_normally env + else if nu then begin + print_nu_env never_with_cr env; + end else print_env never_with_cr env @@ -331,7 +338,7 @@ let ensure_env gt switch = ignore (ensure_env_aux gt switch) let env gt switch ?(set_opamroot=false) ?(set_opamswitch=false) - ~csh ~sexp ~fish ~pwsh ~cmd ~inplace_path = + ~csh ~sexp ~fish ~pwsh ~cmd ~nu ~inplace_path = log "config-env"; let opamroot_not_current = let current = gt.root in @@ -371,7 +378,7 @@ let env gt switch ?(set_opamroot=false) ?(set_opamswitch=false) let env = ensure_env_aux ~set_opamroot ~set_opamswitch ~force_path gt switch in - print_eval_env ~csh ~sexp ~fish ~pwsh ~cmd env + print_eval_env ~csh ~sexp ~fish ~pwsh ~cmd ~nu env [@@ocaml.warning "-16"] let subst gt fs = diff --git a/src/client/opamConfigCommand.mli b/src/client/opamConfigCommand.mli index a205bd376a5..d38f1a4b222 100644 --- a/src/client/opamConfigCommand.mli +++ b/src/client/opamConfigCommand.mli @@ -23,7 +23,7 @@ open OpamStateTypes val env: 'a global_state -> switch -> ?set_opamroot:bool -> ?set_opamswitch:bool -> - csh:bool -> sexp:bool -> fish:bool -> pwsh:bool -> cmd:bool -> + csh:bool -> sexp:bool -> fish:bool -> pwsh:bool -> cmd:bool -> nu:bool -> inplace_path:bool -> unit (** Ensures that the environment file exists in the given switch, regenerating @@ -32,7 +32,7 @@ val ensure_env: 'a global_state -> switch -> unit (** Like [env] but allows one to specify the precise env to print rather than compute it from a switch state *) -val print_eval_env: csh:bool -> sexp:bool -> fish:bool -> pwsh:bool -> cmd:bool -> env -> unit +val print_eval_env: csh:bool -> sexp:bool -> fish:bool -> pwsh:bool -> cmd:bool -> nu:bool -> env -> unit (** Display the content of all available packages variables *) val list: 'a switch_state -> name list -> unit diff --git a/src/core/opamStd.ml b/src/core/opamStd.ml index 253abf7fc7f..9649f7933d3 100644 --- a/src/core/opamStd.ml +++ b/src/core/opamStd.ml @@ -936,7 +936,7 @@ module OpamSys = struct fun () -> Lazy.force os type powershell_host = Powershell_pwsh | Powershell - type shell = SH_sh | SH_bash | SH_zsh | SH_csh | SH_fish + type shell = SH_sh | SH_bash | SH_zsh | SH_csh | SH_fish | SH_nu | SH_pwsh of powershell_host | SH_cmd let all_shells = @@ -944,6 +944,7 @@ module OpamSys = struct SH_zsh; SH_csh; SH_fish; + SH_nu; SH_pwsh Powershell_pwsh; SH_pwsh Powershell; SH_cmd] @@ -958,6 +959,7 @@ module OpamSys = struct | "zsh" -> Some SH_zsh | "bash" -> Some SH_bash | "fish" -> Some SH_fish + | "nu" -> Some SH_nu | "pwsh" -> Some (SH_pwsh Powershell_pwsh) | "dash" | "sh" -> Some SH_sh @@ -1078,6 +1080,16 @@ module OpamSys = struct match shell with | SH_fish -> Some (List.fold_left Filename.concat (home ".config") ["fish"; "config.fish"]) + | SH_nu -> + (* nu checks XDG_CONFIG_HOME on all operating systems, + and then falls back to the system-dependent "dirs" crate *) + let config_home = try Env.get "XDG_CONFIG_HOME" + with Not_found -> match os () with + | Darwin -> home "Library/Application Support" + | Win32 -> (try Env.get "APPDATA" with Not_found -> home ".config") + | _ -> home ".config" + in + Some (List.fold_left Filename.concat config_home ["nushell"; "autoload"; "opam.nu"]) | SH_zsh -> let zsh_home f = try Filename.concat (Env.get "ZDOTDIR") f diff --git a/src/core/opamStd.mli b/src/core/opamStd.mli index d219de17640..971b2b637ad 100644 --- a/src/core/opamStd.mli +++ b/src/core/opamStd.mli @@ -503,7 +503,7 @@ module Sys : sig (** The different families of shells we know about *) type powershell_host = Powershell_pwsh | Powershell - type shell = SH_sh | SH_bash | SH_zsh | SH_csh | SH_fish + type shell = SH_sh | SH_bash | SH_zsh | SH_csh | SH_fish | SH_nu | SH_pwsh of powershell_host | SH_cmd (** List of all supported shells *) diff --git a/src/format/opamTypes.mli b/src/format/opamTypes.mli index e7bf8036128..0b8f174f7f8 100644 --- a/src/format/opamTypes.mli +++ b/src/format/opamTypes.mli @@ -321,7 +321,7 @@ type pin_kind = [ `version | OpamUrl.backend ] (** Shell compatibility modes *) type powershell_host = OpamStd.Sys.powershell_host = Powershell_pwsh | Powershell type shell = OpamStd.Sys.shell = - | SH_sh | SH_bash | SH_zsh | SH_csh | SH_fish | SH_pwsh of powershell_host + | SH_sh | SH_bash | SH_zsh | SH_csh | SH_fish | SH_nu | SH_pwsh of powershell_host | SH_cmd (** {2 Generic command-line definitions with filters} *) diff --git a/src/format/opamTypesBase.ml b/src/format/opamTypesBase.ml index 9e3d09a47a8..6a5126f90c1 100644 --- a/src/format/opamTypesBase.ml +++ b/src/format/opamTypesBase.ml @@ -42,6 +42,7 @@ let all_std_paths = let string_of_shell = function | SH_fish -> "fish" + | SH_nu -> "nu" | SH_csh -> "csh" | SH_zsh -> "zsh" | SH_sh -> "sh" diff --git a/src/state/dune b/src/state/dune index 3f4338b003b..2dd14a30fe6 100644 --- a/src/state/dune +++ b/src/state/dune @@ -13,7 +13,7 @@ (rule (targets opamScript.ml) - (deps ../../shell/crunch.ml (glob_files shellscripts/*.*sh)) + (deps ../../shell/crunch.ml (glob_files shellscripts/*.*sh) (glob_files shellscripts/*.nu)) (action (with-stdout-to %{targets} (run ocaml %{deps})))) ; Embedded Cygwin mechanism (done in the configure script on Windows if requested) diff --git a/src/state/opamEnv.ml b/src/state/opamEnv.ml index 7a671c30666..b5427602035 100644 --- a/src/state/opamEnv.ml +++ b/src/state/opamEnv.ml @@ -830,6 +830,8 @@ let shell_eval_invocation shell cmd = Printf.sprintf "(& %s) -split '\\r?\\n' | ForEach-Object { Invoke-Expression $_ }" cmd | SH_fish -> Printf.sprintf "eval (%s)" cmd + | SH_nu -> + Printf.sprintf "load-env (%s | from-json)" cmd | SH_csh -> Printf.sprintf "eval `%s`" cmd | SH_cmd -> @@ -877,7 +879,7 @@ let opam_env_invocation ?root ?switch ?(set_opamswitch=false) shell = let quoted = match shell with | SH_cmd | SH_pwsh _ -> Printf.sprintf " \"--%s=%s\"" argname - | SH_sh | SH_bash | SH_zsh | SH_csh | SH_fish -> + | SH_sh | SH_bash | SH_zsh | SH_csh | SH_fish | SH_nu -> Printf.sprintf " '--%s=%s'" argname in if filepath_needs_quote pathval then @@ -928,24 +930,26 @@ let eval_string gt ?(set_opamswitch=false) switch = (** The shells for which we generate init scripts (bash and sh are the same entry) *) -let shells_list = [ SH_sh; SH_zsh; SH_csh; SH_fish; SH_pwsh Powershell; SH_cmd ] +let shells_list = [ SH_sh; SH_zsh; SH_csh; SH_fish; SH_pwsh Powershell; SH_cmd ; SH_nu] let complete_file = function | SH_sh | SH_bash -> Some "complete.sh" | SH_zsh -> Some "complete.zsh" - | SH_csh | SH_fish | SH_pwsh _ | SH_cmd -> None + | SH_csh | SH_fish | SH_pwsh _ | SH_cmd | SH_nu-> None let env_hook_file = function | SH_sh | SH_bash -> Some "env_hook.sh" | SH_zsh -> Some "env_hook.zsh" | SH_csh -> Some "env_hook.csh" | SH_fish -> Some "env_hook.fish" + | SH_nu -> Some "env_hook.nu" | SH_pwsh _ | SH_cmd -> None let variables_file = function | SH_sh | SH_bash | SH_zsh -> "variables.sh" | SH_csh -> "variables.csh" | SH_fish -> "variables.fish" + | SH_nu -> "variables.nu" | SH_pwsh _ -> "variables.ps1" | SH_cmd -> "variables.cmd" @@ -954,13 +958,14 @@ let init_file = function | SH_zsh -> "init.zsh" | SH_csh -> "init.csh" | SH_fish -> "init.fish" + | SH_nu -> "init.nu" | SH_pwsh _ -> "init.ps1" | SH_cmd -> "init.cmd" let complete_script = function | SH_sh | SH_bash -> Some OpamScript.complete | SH_zsh -> Some OpamScript.complete_zsh - | SH_csh | SH_fish -> None + | SH_csh | SH_fish | SH_nu -> None | SH_pwsh _ | SH_cmd -> None let env_hook_script_base = function @@ -968,6 +973,7 @@ let env_hook_script_base = function | SH_zsh -> Some OpamScript.env_hook_zsh | SH_csh -> Some OpamScript.env_hook_csh | SH_fish -> Some OpamScript.env_hook_fish + | SH_nu -> Some OpamScript.env_hook_nu | SH_pwsh _ | SH_cmd -> None let export_in_shell ~unconditional shell = @@ -1008,6 +1014,9 @@ let export_in_shell ~unconditional shell = Printf.sprintf "%sset -gx %s %s;\n" (make_comment comment) k v in + let nu (k,v,comment) = + Printf.sprintf "$env.%s = %s %s\n" k v (make_comment comment) + in let pwsh (k,v,comment) = Printf.sprintf "%s$env:%s=%s\n" (make_comment comment) k v in @@ -1020,19 +1029,24 @@ let export_in_shell ~unconditional shell = match shell with | SH_zsh | SH_bash | SH_sh -> sh | SH_fish -> fish + | SH_nu -> nu | SH_csh -> csh | SH_pwsh _ -> pwsh | SH_cmd -> cmd let env_hook_script shell = Option.map (fun script -> - export_in_shell shell ~unconditional:true ("OPAMNOENVNOTICE", "true", None) + let var = match shell with + | SH_nu -> "'true'" + | _ -> "true" + in + export_in_shell shell ~unconditional:true ("OPAMNOENVNOTICE", var, None) ^ script) (env_hook_script_base shell) let abort_if_set var shell = (* Each of these incantations aborts the currently running script if [var] is - unset or empty ("null") *) + set to something non-empty *) match shell with | SH_zsh | SH_bash | SH_sh -> Printf.sprintf "test -z \"${%s:+x}\" || return\n" var @@ -1044,6 +1058,8 @@ let abort_if_set var shell = var var | SH_fish -> Printf.sprintf "test -z \"$%s\"; or return\n" var + | SH_nu -> + Printf.sprintf "if ($env.%s? | default \"\") != \"\" { return }\n" var | SH_cmd -> Printf.sprintf "if defined %s if \"%%%s%%\" neq \"\" goto :EOF\n" var var @@ -1062,6 +1078,8 @@ let source root shell f = | SH_fish -> let fname = unix_transform ~using_backslashes:true () in Printf.sprintf "test -r '%s' && source '%s' > /dev/null 2> /dev/null; or true\n" fname fname + | SH_nu -> + Printf.sprintf "const source_path = if ('%s' | path exists) { '%s' } \nsource $source_path\n" fname fname | SH_sh | SH_bash -> let fname = unix_transform () in Printf.sprintf "test -r '%s' && . '%s' > /dev/null 2> /dev/null || true\n" @@ -1088,6 +1106,7 @@ let if_interactive_script shell t e = | None -> "" | Some e -> Printf.sprintf "} else {\n %s" e in + let ielse_nu = ielse_pwsh in match shell with | SH_sh| SH_bash -> Printf.sprintf "if [ -t 0 ]; then\n %s%sfi\n" t @@ ielse e @@ -1097,6 +1116,8 @@ let if_interactive_script shell t e = Printf.sprintf "if ( $?prompt ) then\n %s%sendif\n" t @@ ielse e | SH_fish -> Printf.sprintf "if status is-interactive\n %s%send\n" t @@ ielse e + | SH_nu -> + Printf.sprintf "if $nu.is-interactive {\n %s%s}\n" t @@ ielse_nu e | SH_cmd -> Printf.sprintf "echo %%cmdcmdline%% | find /i \"%%~0\" >nul\nif errorlevel 1 (\n%s%s)\n" t @@ ielse_cmd e | SH_pwsh _ -> @@ -1143,11 +1164,13 @@ let string_of_update st shell updates = Printf.sprintf "'%s%c' + \"$env:%s\"" (OpamStd.Env.escape_powershell string) sep envu_var | SH_cmd -> Printf.sprintf "%s%c%%%s%%" string sep envu_var + | SH_nu -> Printf.sprintf "$env.%s? | default [] | prepend '%s'" envu_var string | _ -> Printf.sprintf "'%s':\"$%s\"" string envu_var) | EqColon | EqPlus -> (match shell with | SH_pwsh _ -> Printf.sprintf "\"$env:%s\" + '%c%s'" envu_var sep string | SH_cmd -> Printf.sprintf "%%%s%%%c%s" envu_var sep string + | SH_nu -> Printf.sprintf "$env.%s? | default [] | append '%s'" envu_var string | _ -> Printf.sprintf "\"$%s\":'%s'" envu_var string) in export_in_shell shell ~unconditional:(envu_op = Eq) (key, value, envu_comment) in @@ -1243,7 +1266,7 @@ let write_dynamic_init_scripts st = (variables_file shell, abort_if_set "OPAM_SWITCH_PREFIX" shell ^ string_of_update st shell updates)) - [SH_sh; SH_csh; SH_fish; SH_pwsh Powershell; SH_cmd] + [SH_sh; SH_csh; SH_fish; SH_nu; SH_pwsh Powershell; SH_cmd] with OpamSystem.Locked -> OpamConsole.warning "Global shell init scripts not installed (could not acquire lock)" diff --git a/src/state/opamScript.mli b/src/state/opamScript.mli index 8bfe74d0e3e..78f99c5eb5e 100644 --- a/src/state/opamScript.mli +++ b/src/state/opamScript.mli @@ -20,3 +20,4 @@ val env_hook : string val env_hook_zsh : string val env_hook_csh : string val env_hook_fish : string +val env_hook_nu: string diff --git a/src/state/shellscripts/env_hook.nu b/src/state/shellscripts/env_hook.nu new file mode 100644 index 00000000000..413a2b1db7b --- /dev/null +++ b/src/state/shellscripts/env_hook.nu @@ -0,0 +1,5 @@ +$env.config = ($env.config | upsert hooks.pre_prompt { + default [] | append { + load-env (opam env --shell=nu | from json) + } +})