From faee2e4e21a19b81d88df8291fa13b54487e791c Mon Sep 17 00:00:00 2001 From: eureka-cpu Date: Fri, 6 Feb 2026 21:11:00 -0800 Subject: [PATCH 1/2] feat: Add `--force/-f` flag to force experimental features --- bin/main.ml | 42 +++++++++++++++++++++++------------------- lib/cli.ml | 25 ++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/bin/main.ml b/bin/main.ml index f2a2594..ccd6046 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -2,29 +2,33 @@ open Ns open Ns.Cmd open Ns.Util -let main ({ installables; target_info; printcmd } : Cli.strategy) = +let main + ({ installables; target_info; printcmd; force_experimental_features } : + Cli.strategy) + = let ({ entrypoint; attribute; subshell_dir } : Cli.target_info) = target_info in Option.value ~default:(Option.value ~default:(Sys.getcwd ()) entrypoint) subshell_dir |> Unix.cd; let cmd = - match entrypoint with - | Some entrypoint -> - (match Unix.flake_exists_at entrypoint with - | true -> - Cmd.builder "nix" - |>+ [ "develop" ] @ [ Uri.sprintf_uri_attr_opt entrypoint attribute ] - | false -> - (match Unix.shell_exists_at entrypoint with - | true -> - Cmd.builder "nix-shell" - |>+ Option.value - ~default:[] - (Option.map (fun attr -> [ "--attr"; attr ]) attribute) - |>+ [ entrypoint ] - | false -> - Error.handle_ns_error "no available devshell entrypoint: %s\n%!" entrypoint)) - |>+ [ "--command"; Unix.shell ] - | None -> Cmd.builder "nix" |>+ [ "shell" ] |>+ installables + (match entrypoint with + | Some entrypoint -> + (match Unix.flake_exists_at entrypoint with + | true -> + Cmd.builder "nix" + |>+ [ "develop" ] @ [ Uri.sprintf_uri_attr_opt entrypoint attribute ] + | false -> + (match Unix.shell_exists_at entrypoint with + | true -> + Cmd.builder "nix-shell" + |>+ Option.value + ~default:[] + (Option.map (fun attr -> [ "--attr"; attr ]) attribute) + |>+ [ entrypoint ] + | false -> + Error.handle_ns_error "no available devshell entrypoint: %s\n%!" entrypoint)) + |>+ [ "--command"; Unix.shell ] + | None -> Cmd.builder "nix" |>+ [ "shell" ] |>+ installables) + |>+ Option.value ~default:[] force_experimental_features in if printcmd then print_endline (Cmd.to_string cmd) else ignore (Cmd.run cmd) ;; diff --git a/lib/cli.ml b/lib/cli.ml index ab0b812..116dea6 100644 --- a/lib/cli.ml +++ b/lib/cli.ml @@ -18,6 +18,7 @@ module Cli = struct { installables : string list ; target_info : target_info ; printcmd : bool + ; force_experimental_features : string list option } (** Positional URI arguments passed to the program via the command line. *) @@ -28,9 +29,14 @@ module Cli = struct Arg.(value & flag & info [ "printcmd" ] ~doc) ;; + let force_experimental_features = + let doc = "Force the use of flakes and nix-command experimental features." in + Arg.(value & flag & info [ "force"; "f" ] ~doc) + ;; + (** Processes the args so that the main function knows what to do with them. *) let make_strategy = - let build original_args printcmd = + let build original_args printcmd force_experimental_features = let installables, target_info = let default_installables = [] and default_target = @@ -87,9 +93,22 @@ module Cli = struct , { entrypoint = None; attribute = None; subshell_dir = Some subshell_dir } ) | _ -> Uri.parse_targets_tr original_args, default_target) in - { installables; target_info; printcmd } + { installables + ; target_info + ; printcmd + ; force_experimental_features = + (if force_experimental_features + then + Some + [ "--extra-experimental-features" + ; "flakes" + ; "--extra-experimental-features" + ; "nix-command" + ] + else None) + } in - Term.(const build $ uris $ printcmd) + Term.(const build $ uris $ printcmd $ force_experimental_features) ;; let cmd entrypoint = From e3a59d60742acb92bd5f7e7183f3800f049088e8 Mon Sep 17 00:00:00 2001 From: eureka-cpu Date: Fri, 6 Feb 2026 23:32:44 -0800 Subject: [PATCH 2/2] fix errors and tests --- bin/main.ml | 42 +++-- test/test_ns.rs | 470 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 493 insertions(+), 19 deletions(-) diff --git a/bin/main.ml b/bin/main.ml index ccd6046..c016d04 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -10,25 +10,29 @@ let main Option.value ~default:(Option.value ~default:(Sys.getcwd ()) entrypoint) subshell_dir |> Unix.cd; let cmd = - (match entrypoint with - | Some entrypoint -> - (match Unix.flake_exists_at entrypoint with - | true -> - Cmd.builder "nix" - |>+ [ "develop" ] @ [ Uri.sprintf_uri_attr_opt entrypoint attribute ] - | false -> - (match Unix.shell_exists_at entrypoint with - | true -> - Cmd.builder "nix-shell" - |>+ Option.value - ~default:[] - (Option.map (fun attr -> [ "--attr"; attr ]) attribute) - |>+ [ entrypoint ] - | false -> - Error.handle_ns_error "no available devshell entrypoint: %s\n%!" entrypoint)) - |>+ [ "--command"; Unix.shell ] - | None -> Cmd.builder "nix" |>+ [ "shell" ] |>+ installables) - |>+ Option.value ~default:[] force_experimental_features + match entrypoint with + | Some entrypoint -> + (match Unix.flake_exists_at entrypoint with + | true -> + Cmd.builder "nix" + |>+ [ "develop" ] @ [ Uri.sprintf_uri_attr_opt entrypoint attribute ] + |>+ Option.value ~default:[] force_experimental_features + | false -> + (match Unix.shell_exists_at entrypoint with + | true -> + Cmd.builder "nix-shell" + |>+ Option.value + ~default:[] + (Option.map (fun attr -> [ "--attr"; attr ]) attribute) + |>+ [ entrypoint ] + | false -> + Error.handle_ns_error "no available devshell entrypoint: %s\n%!" entrypoint)) + |>+ [ "--command"; Unix.shell ] + | None -> + Cmd.builder "nix" + |>+ [ "shell" ] + |>+ installables + |>+ Option.value ~default:[] force_experimental_features in if printcmd then print_endline (Cmd.to_string cmd) else ignore (Cmd.run cmd) ;; diff --git a/test/test_ns.rs b/test/test_ns.rs index 11a5dbe..d4f8ff1 100644 --- a/test/test_ns.rs +++ b/test/test_ns.rs @@ -70,6 +70,34 @@ mod nix_develop_tests { ); } + #[test] + fn single_arg_no_attr_force() { + let output = process::Command::new(ns()) + .args([ + flake_dir().as_os_str(), + "--printcmd".as_ref(), + "--force".as_ref(), + ]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "develop", + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + "--command", + env_shell(), + ], + ); + } + #[test] fn single_arg_single_attr() { let flake_uri = format!("{}#default", flake_dir().display()); @@ -85,6 +113,31 @@ mod nix_develop_tests { ); } + #[test] + fn single_arg_single_attr_force() { + let flake_uri = format!("{}#default", flake_dir().display()); + let output = process::Command::new(ns()) + .args([&flake_uri, "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "develop", + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + "--command", + env_shell(), + ], + ); + } + #[test] fn two_args_no_attrs() { let output = process::Command::new(ns()) @@ -105,6 +158,35 @@ mod nix_develop_tests { ); } + #[test] + fn two_args_no_attrs_force() { + let output = process::Command::new(ns()) + .args([ + flake_dir().as_os_str(), + ".".as_ref(), + "--printcmd".as_ref(), + "--force".as_ref(), + ]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "develop", + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + "--command", + env_shell(), + ], + ); + } + #[test] fn two_args_single_attr_lhs() { let flake_uri = format!("{}#default", flake_dir().display()); @@ -119,6 +201,31 @@ mod nix_develop_tests { &["nix", "develop", &flake_uri, "--command", env_shell()], ); } + + #[test] + fn two_args_single_attr_lhs_force() { + let flake_uri = format!("{}#default", flake_dir().display()); + let output = process::Command::new(ns()) + .args([&flake_uri, ".", "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "develop", + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + "--command", + env_shell(), + ], + ); + } } mod legacy_nix_shell_tests { @@ -143,6 +250,29 @@ mod legacy_nix_shell_tests { ); } + #[test] + fn single_arg_no_attr_force() { + let output = process::Command::new(ns()) + .args([ + shell_dir().as_os_str(), + "--printcmd".as_ref(), + "--force".as_ref(), + ]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix-shell", + &shell_dir().to_string_lossy(), + "--command", + env_shell(), + ], + ); + } + #[test] fn single_arg_single_attr() { let shell_uri = format!("{}#default", shell_dir().display()); @@ -164,6 +294,28 @@ mod legacy_nix_shell_tests { ], ); } + + #[test] + fn single_arg_single_attr_force() { + let shell_uri = format!("{}#default", shell_dir().display()); + let output = process::Command::new(ns()) + .args([&shell_uri, "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix-shell", + "--attr", + "default", + &shell_dir().to_string_lossy(), + "--command", + env_shell(), + ], + ); + } } mod nix_shell_tests { @@ -181,6 +333,29 @@ mod nix_shell_tests { assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri]); } + #[test] + fn single_arg_multi_attr_force() { + let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); + let output = process::Command::new(ns()) + .args([&flake_uri, "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn two_args_single_attr_rhs() { let flake_uri = format!("{}#default", flake_dir().display()); @@ -200,6 +375,35 @@ mod nix_shell_tests { ); } + #[test] + fn two_args_single_attr_rhs_force() { + let flake_uri = format!("{}#default", flake_dir().display()); + let output = process::Command::new(ns()) + .args([ + flake_dir().as_os_str(), + flake_uri.as_ref(), + "--printcmd".as_ref(), + "--force".as_ref(), + ]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + &flake_uri, + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn two_args_single_attrs() { let flake_uri = format!("{}#default", flake_dir().display()); @@ -212,6 +416,30 @@ mod nix_shell_tests { assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri, &flake_uri]); } + #[test] + fn two_args_single_attrs_force() { + let flake_uri = format!("{}#default", flake_dir().display()); + let output = process::Command::new(ns()) + .args([&flake_uri, &flake_uri, "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + &flake_uri, + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn two_args_multi_attr_lhs() { let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); @@ -224,6 +452,29 @@ mod nix_shell_tests { assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri]); } + #[test] + fn two_args_multi_attr_lhs_force() { + let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); + let output = process::Command::new(ns()) + .args([&flake_uri, ".", "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn two_args_multi_attr_rhs() { let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); @@ -243,6 +494,35 @@ mod nix_shell_tests { ); } + #[test] + fn two_args_multi_attr_rhs_force() { + let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); + let output = process::Command::new(ns()) + .args([ + flake_dir().as_os_str(), + flake_uri.as_ref(), + "--printcmd".as_ref(), + "--force".as_ref(), + ]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + &flake_uri, + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn two_args_multi_attrs() { let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); @@ -255,6 +535,30 @@ mod nix_shell_tests { assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri, &flake_uri]); } + #[test] + fn two_args_multi_attrs_force() { + let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); + let output = process::Command::new(ns()) + .args([&flake_uri, &flake_uri, "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + &flake_uri, + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn three_args_single_attrs_lhs() { let flake_uri = format!("{}#default", flake_dir().display()); @@ -268,6 +572,31 @@ mod nix_shell_tests { assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri, remote_uri]); } + #[test] + fn three_args_single_attrs_lhs_force() { + let flake_uri = format!("{}#default", flake_dir().display()); + let remote_uri = "github:NixOS/nixpkgs#hello"; + let output = process::Command::new(ns()) + .args([&flake_uri, remote_uri, ".", "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + &flake_uri, + remote_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn three_args_single_attrs_rhs() { let flake_uri = format!("{}#default", flake_dir().display()); @@ -295,6 +624,38 @@ mod nix_shell_tests { ); } + #[test] + fn three_args_single_attrs_rhs_force() { + let flake_uri = format!("{}#default", flake_dir().display()); + let remote_uri = "github:NixOS/nixpkgs#hello"; + let output = process::Command::new(ns()) + .args([ + flake_dir().as_os_str(), + flake_uri.as_ref(), + remote_uri.as_ref(), + "--printcmd".as_ref(), + "--force".as_ref(), + ]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + remote_uri, + &flake_uri, + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn three_args_single_attrs() { let flake_uri = format!("{}#default", flake_dir().display()); @@ -311,6 +672,32 @@ mod nix_shell_tests { ); } + #[test] + fn three_args_single_attrs_force() { + let flake_uri = format!("{}#default", flake_dir().display()); + let remote_uri = "github:NixOS/nixpkgs#hello"; + let output = process::Command::new(ns()) + .args([&flake_uri, &flake_uri, remote_uri, "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + remote_uri, + &flake_uri, + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn three_args_multi_attrs_lhs() { let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); @@ -324,6 +711,31 @@ mod nix_shell_tests { assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri, remote_uri]); } + #[test] + fn three_args_multi_attrs_lhs_force() { + let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); + let remote_uri = "github:NixOS/nixpkgs#{hello,cowsay}"; + let output = process::Command::new(ns()) + .args([&flake_uri, remote_uri, ".", "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + &flake_uri, + remote_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn three_args_multi_attrs_rhs() { let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); @@ -351,6 +763,38 @@ mod nix_shell_tests { ); } + #[test] + fn three_args_multi_attrs_rhs_force() { + let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); + let remote_uri = "github:NixOS/nixpkgs#{hello,cowsay}"; + let output = process::Command::new(ns()) + .args([ + flake_dir().as_os_str(), + flake_uri.as_ref(), + remote_uri.as_ref(), + "--printcmd".as_ref(), + "--force".as_ref(), + ]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + remote_uri, + &flake_uri, + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } + #[test] fn three_args_multi_attrs() { let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); @@ -366,4 +810,30 @@ mod nix_shell_tests { &["nix", "shell", remote_uri, &flake_uri, &flake_uri], ); } + + #[test] + fn three_args_multi_attrs_force() { + let flake_uri = format!("{}#{{hello,cowsay}}", flake_dir().display()); + let remote_uri = "github:NixOS/nixpkgs#{hello,cowsay}"; + let output = process::Command::new(ns()) + .args([&flake_uri, &flake_uri, remote_uri, "--printcmd", "--force"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + "nix", + "shell", + remote_uri, + &flake_uri, + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + ); + } }