commit 29cf307f93c19325ff09f0e324624abd4f6bb7ba Author: Thomas Dy Date: Sun Jan 15 18:05:45 2023 +0900 nix-build: add initial support diff --git a/completers/nix-build_completer/cmd/root.go b/completers/nix-build_completer/cmd/root.go new file mode 100644 index 00000000..65381edd --- /dev/null +++ b/completers/nix-build_completer/cmd/root.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "github.com/rsteube/carapace" + "github.com/rsteube/carapace-bin/pkg/actions/tools/nix" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "nix-build", + Short: "build a Nix expression", + Long: "https://nixos.org/manual/nix/stable/command-ref/nix-build.html", + Run: func(cmd *cobra.Command, args []string) {}, +} + +func Execute() error { + return rootCmd.Execute() +} +func init() { + carapace.Gen(rootCmd).Standalone() + + rootCmd.Flags().StringP("include", "I", "", "Include paths") + rootCmd.Flags().Bool("no-out-link", false, "Do not create a symlink to the output path") + rootCmd.Flags().StringP("out-link", "o", "", "Change the name of the symlink to the output path") + rootCmd.Flags().Bool("dry-run", false, "Show what store paths would be built or downloaded") + rootCmd.Flags().StringP("attr", "A", "", "Attribute to build") + // TODO support --arg and --argstr + + carapace.Gen(rootCmd).PositionalAnyCompletion(nix.ActionPaths()) + carapace.Gen(rootCmd).FlagCompletion(carapace.ActionMap{ + "attr": nix.ActionAttr(rootCmd, func(_ *cobra.Command, c carapace.Context) string { + if len(c.Args) > 0 { + return c.Args[0] + } else { + return "default.nix" + } + }), + }) +} diff --git a/completers/nix-build_completer/main.go b/completers/nix-build_completer/main.go new file mode 100644 index 00000000..a733fad2 --- /dev/null +++ b/completers/nix-build_completer/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/rsteube/carapace-bin/completers/nix-build_completer/cmd" + +func main() { + cmd.Execute() +} diff --git a/pkg/actions/tools/nix/attr.go b/pkg/actions/tools/nix/attr.go new file mode 100644 index 00000000..921bf2b6 --- /dev/null +++ b/pkg/actions/tools/nix/attr.go @@ -0,0 +1,82 @@ +package nix + +import ( + _ "embed" + "encoding/json" + "fmt" + "path" + "strings" + + "github.com/rsteube/carapace" + "github.com/spf13/cobra" +) + +//go:embed attrComplete.nix +var attrCompleteScript string + +func ActionAttr(cmd *cobra.Command, getSource func(*cobra.Command, carapace.Context) string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + attrPath := c.CallbackValue + nixPath := "" + if cmd.Flag("include").Changed { + nixPath = cmd.Flag("include").Value.String() + } + + source := getSource(cmd, c) + + if strings.HasPrefix(source, "<") { + // expression is a local channel, use as-is + } else if strings.HasPrefix(source, "http:") || strings.HasPrefix(source, "https:") { + // expression is a url, wrap in fetchTarball + source = fmt.Sprintf("(fetchTarball %s)", source) + } else if strings.HasPrefix(source, "channel:") { + // expression is a channel alias, convert to url + channelName := source[8:] + source = fmt.Sprintf("(fetchTarball https://nixos.org/channels/%s/nixexprs.tar.xz)", channelName) + } else { + // expression is a local file + filePath := path.Clean(source) + if path.Base(filePath) == filePath { + // paths must have at least one `/` + filePath = fmt.Sprintf("./%s", filePath) + } + source = filePath + } + expression := fmt.Sprintf("import %s", source) + + // nix itself handles quotes weirdly so we just avoid them altogether + if strings.Contains(attrPath, "\"") { + return carapace.ActionMessage("attrPath may not contain double-quotes") + } + + // strip right-most attr + lastDotIndex := strings.LastIndex(attrPath, ".") + if lastDotIndex >= 0 { + attrPath = attrPath[0:lastDotIndex] + } else { + attrPath = "" + } + + args := []string{ + "--eval", "--json", + "--expr", attrCompleteScript, + "--arg", "__carapaceInput__", expression, + "--argstr", "__carapaceAttrPath__", attrPath, + } + if nixPath != "" { + args = append(args, "-I", nixPath) + } + // TODO handle passing through --arg and --argstr from original command + + return carapace.ActionExecCommand("nix-instantiate", args...)(func(output []byte) carapace.Action { + var data []string + json.Unmarshal(output, &data) + if attrPath != "" { + for i, value := range data { + data[i] = fmt.Sprintf("%s.%s", attrPath, value) + } + } + return carapace.ActionValues(data...) + }) + }) +} diff --git a/pkg/actions/tools/nix/attrComplete.nix b/pkg/actions/tools/nix/attrComplete.nix new file mode 100644 index 00000000..4f98d858 --- /dev/null +++ b/pkg/actions/tools/nix/attrComplete.nix @@ -0,0 +1,25 @@ +all@{ __carapaceInput__, __carapaceAttrPath__, ... }: +let + inputArgs = builtins.removeAttrs all ["__carapaceInput__" "__carapaceAttrPath__"]; + + input = + if builtins.isFunction __carapaceInput__ then + __carapaceInput__ inputArgs + else + __carapaceInput__; + + autocall = fnOrAttrset: + if builtins.isFunction fnOrAttrset then + fnOrAttrset {} + else + fnOrAttrset; + + paths = builtins.filter (path: (builtins.isString path) && path != "") (builtins.split "\\." __carapaceAttrPath__); + + reducer = attrset: name: autocall (builtins.getAttr name attrset); + result = builtins.foldl' reducer input paths; +in + if builtins.isAttrs result then + builtins.attrNames result + else + [] diff --git a/pkg/actions/tools/nix/path.go b/pkg/actions/tools/nix/path.go new file mode 100644 index 00000000..e3dbc8b2 --- /dev/null +++ b/pkg/actions/tools/nix/path.go @@ -0,0 +1,29 @@ +package nix + +import ( + "strings" + + "github.com/rsteube/carapace" +) + +// ActionPaths completes paths +// +// A path can be one of: +// - a local file path (default.nix) +// - an http/https URL (https://releases.nixos.org/../nixexprs.tar.xz +// - a channel: specifier (channel:nixos-22.05) +// - a local channel () +func ActionPaths() carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + arg := c.CallbackValue + if strings.HasPrefix(arg, "<") { + return ActionLocalChannels().Invoke(c).Prefix("<").Suffix(">").ToA() + } else if strings.HasPrefix(arg, "http:") || strings.HasPrefix(arg, "https:") { + return carapace.ActionValues() + } else if strings.HasPrefix(arg, "channel:") { + return ActionRemoteChannels().Invoke(c).Prefix("channel:").ToA() + } else { + return carapace.ActionFiles() + } + }) +}