From e7ec17916b59e4e5d7790c610ea0708f059f40a7 Mon Sep 17 00:00:00 2001 From: Daylin Morgan Date: Mon, 15 Jul 2024 16:24:46 -0500 Subject: [PATCH] cli: consolidate and improve cli --- README.md | 4 +- pkgs/oizys/cmd/boot.go | 19 ---- pkgs/oizys/cmd/build.go | 3 +- pkgs/oizys/cmd/checks.go | 3 +- pkgs/oizys/cmd/os.go | 38 +++++++ pkgs/oizys/cmd/root.go | 1 - pkgs/oizys/cmd/switch.go | 19 ---- pkgs/oizys/cmd/update.go | 24 ++++- pkgs/oizys/default.nix | 17 +--- pkgs/oizys/go.mod | 2 + pkgs/oizys/go.sum | 8 ++ pkgs/oizys/internal/git/main.go | 33 ++++++- pkgs/oizys/internal/github/main.go | 154 +++++++++++++++++++++++++++++ pkgs/oizys/internal/oizys/main.go | 70 ++++++------- pkgs/oizys/internal/oizys/nix.go | 17 ++-- pkgs/oizys/internal/ui/main.go | 24 +++++ 16 files changed, 317 insertions(+), 119 deletions(-) delete mode 100644 pkgs/oizys/cmd/boot.go create mode 100644 pkgs/oizys/cmd/os.go delete mode 100644 pkgs/oizys/cmd/switch.go create mode 100644 pkgs/oizys/internal/github/main.go diff --git a/README.md b/README.md index 300fedc..c7356a5 100644 --- a/README.md +++ b/README.md @@ -52,15 +52,14 @@ Usage: oizys [command] Available Commands: - boot nixos rebuild boot build nix build cache build and push to cachix checks nix build checks ci offload build to GHA dry poor man's nix flake check help Help about any command + os nixos-rebuild wrapper output show nixosConfiguration attr - switch nixos rebuild switch update update and run nixos rebuild Flags: @@ -84,3 +83,4 @@ Oizys was birthed by the goddess Nyx/Nix and embodies suffering and misery. Whic > I don't use home-manager to manager my shell/user configs. You can find those in my separate `chezmoi`-managed [`dotfiles`](https://git.dayl.in/daylin/dotfiles) repository. + diff --git a/pkgs/oizys/cmd/boot.go b/pkgs/oizys/cmd/boot.go deleted file mode 100644 index d862f33..0000000 --- a/pkgs/oizys/cmd/boot.go +++ /dev/null @@ -1,19 +0,0 @@ -package cmd - -import ( - "oizys/internal/oizys" - - "github.com/spf13/cobra" -) - -var bootCmd = &cobra.Command{ - Use: "boot", - Short: "nixos rebuild boot", - Run: func(cmd *cobra.Command, args []string) { - oizys.NixosRebuild("boot", args...) - }, -} - -func init() { - rootCmd.AddCommand(bootCmd) -} diff --git a/pkgs/oizys/cmd/build.go b/pkgs/oizys/cmd/build.go index 31c021e..f648676 100644 --- a/pkgs/oizys/cmd/build.go +++ b/pkgs/oizys/cmd/build.go @@ -10,12 +10,11 @@ var buildCmd = &cobra.Command{ Use: "build", Short: "nix build", Run: func(cmd *cobra.Command, args []string) { - oizys.NixBuild(nom, minimal, args...) + oizys.NixBuild(minimal, args...) }, } func init() { rootCmd.AddCommand(buildCmd) - buildCmd.Flags().BoolVar(&nom, "nom", false, "display result with nom") buildCmd.Flags().BoolVar(&minimal, "minimal", false, "use system dry-run to make build args") } diff --git a/pkgs/oizys/cmd/checks.go b/pkgs/oizys/cmd/checks.go index cc55837..cd7727b 100644 --- a/pkgs/oizys/cmd/checks.go +++ b/pkgs/oizys/cmd/checks.go @@ -10,11 +10,10 @@ var checksCmd = &cobra.Command{ Use: "checks", Short: "nix build checks", Run: func(cmd *cobra.Command, args []string) { - oizys.Checks(nom, args...) + oizys.Checks(args...) }, } func init() { rootCmd.AddCommand(checksCmd) - checksCmd.Flags().BoolVar(&nom, "nom", false, "display result with nom") } diff --git a/pkgs/oizys/cmd/os.go b/pkgs/oizys/cmd/os.go new file mode 100644 index 0000000..54863ec --- /dev/null +++ b/pkgs/oizys/cmd/os.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + "oizys/internal/oizys" + "slices" + "strings" + + "github.com/spf13/cobra" +) + +var validArgs = []string{ + "switch", "boot", "test", "build", "dry-build", "dry-activate", "edit", "repl", + "build-vm", "build-vm-with-bootloader", + "list-generations", +} +var osCmd = &cobra.Command{ + Use: "os [subcmd]", + Short: "nixos-rebuild wrapper", + Args: func(cmd *cobra.Command, args []string) error { + if err := cobra.MinimumNArgs(1)(cmd, args); err != nil { + return err + } + // Run the custom validation logic + if slices.Contains(validArgs, args[0]) { + return nil + } + return fmt.Errorf("unexpected arg: %s\nexpected one of:\n %s", args[0], strings.Join(validArgs, ", ")) + }, + Run: func(cmd *cobra.Command, args []string) { + subcmd := args[0] + oizys.NixosRebuild(subcmd, args[1:]...) + }, +} + +func init() { + rootCmd.AddCommand(osCmd) +} diff --git a/pkgs/oizys/cmd/root.go b/pkgs/oizys/cmd/root.go index a202ec2..0061137 100644 --- a/pkgs/oizys/cmd/root.go +++ b/pkgs/oizys/cmd/root.go @@ -33,7 +33,6 @@ var ( host string debug bool verbose bool - nom bool systemPath bool resetCache bool minimal bool diff --git a/pkgs/oizys/cmd/switch.go b/pkgs/oizys/cmd/switch.go deleted file mode 100644 index 2fe49b5..0000000 --- a/pkgs/oizys/cmd/switch.go +++ /dev/null @@ -1,19 +0,0 @@ -package cmd - -import ( - "oizys/internal/oizys" - - "github.com/spf13/cobra" -) - -var switchCmd = &cobra.Command{ - Use: "switch", - Short: "nixos rebuild switch", - Run: func(cmd *cobra.Command, args []string) { - oizys.NixosRebuild("switch", args...) - }, -} - -func init() { - rootCmd.AddCommand(switchCmd) -} diff --git a/pkgs/oizys/cmd/update.go b/pkgs/oizys/cmd/update.go index 0eaaf89..2b66e50 100644 --- a/pkgs/oizys/cmd/update.go +++ b/pkgs/oizys/cmd/update.go @@ -1,8 +1,13 @@ package cmd import ( + "fmt" + "oizys/internal/github" "oizys/internal/oizys" + "oizys/internal/ui" + "os" + "github.com/charmbracelet/log" "github.com/spf13/cobra" ) @@ -10,14 +15,25 @@ var updateCmd = &cobra.Command{ Use: "update", Short: "update and run nixos rebuild", Run: func(cmd *cobra.Command, args []string) { - oizys.GitPull() - oizys.NixosRebuild("switch", args...) + run := github.GetLastUpdateRun() + if preview { + md, err := github.GetUpateSummary(run.GetID()) + if err != nil { + log.Fatal(err) + } + fmt.Println(md) + if !ui.Confirm("proceed with system update?") { + os.Exit(0) + } + } + oizys.UpdateRepo() + oizys.NixosRebuild("switch") }, } -var boot bool +var preview bool func init() { rootCmd.AddCommand(updateCmd) - updateCmd.Flags().BoolVarP(&boot, "boot", "b", false, "run nixos-rebuild boot") + updateCmd.Flags().BoolVar(&preview, "preview", false, "confirm nix store diff") } diff --git a/pkgs/oizys/default.nix b/pkgs/oizys/default.nix index 5fd0741..8559cee 100644 --- a/pkgs/oizys/default.nix +++ b/pkgs/oizys/default.nix @@ -3,19 +3,17 @@ installShellFiles, buildGoModule, makeWrapper, - gh, - nix-output-monitor, ... }: let - inherit (lib) cleanSource makeBinPath; + inherit (lib) cleanSource; in buildGoModule { pname = "oizys"; version = "unstable"; src = cleanSource ./.; - vendorHash = "sha256-/JVXhXrU2np/ty7AGFy+LPZCo1NaLYl9NAyD9+FJYBI="; + vendorHash = "sha256-+4OtpcKHfomBAXRrJOvkhQdCSwU0W6+5OJuS4o12r5E="; nativeBuildInputs = [ installShellFiles @@ -27,13 +25,4 @@ buildGoModule { --zsh <(OIZYS_SKIP_CHECK=true $out/bin/oizys completion zsh) ''; - postFixup = '' - wrapProgram $out/bin/oizys \ - --prefix PATH ':' ${ - makeBinPath [ - gh - nix-output-monitor - ] - } - ''; -} + } diff --git a/pkgs/oizys/go.mod b/pkgs/oizys/go.mod index 4d683b1..9aaa162 100644 --- a/pkgs/oizys/go.mod +++ b/pkgs/oizys/go.mod @@ -6,6 +6,7 @@ require ( github.com/briandowns/spinner v1.23.0 github.com/charmbracelet/lipgloss v0.11.0 github.com/charmbracelet/log v0.4.0 + github.com/google/go-github/v63 v63.0.0 github.com/ivanpirog/coloredcobra v1.0.1 github.com/spf13/cobra v1.8.0 golang.org/x/term v0.21.0 @@ -16,6 +17,7 @@ require ( github.com/charmbracelet/x/ansi v0.1.2 // indirect github.com/fatih/color v1.17.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/pkgs/oizys/go.sum b/pkgs/oizys/go.sum index fffa560..1051ff6 100644 --- a/pkgs/oizys/go.sum +++ b/pkgs/oizys/go.sum @@ -17,6 +17,13 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v63 v63.0.0 h1:13xwK/wk9alSokujB9lJkuzdmQuVn2QCPeck76wR3nE= +github.com/google/go-github/v63 v63.0.0/go.mod h1:IqbcrgUmIcEaioWrGYei/09o+ge5vhffGOcxrO0AfmA= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -60,6 +67,7 @@ golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkgs/oizys/internal/git/main.go b/pkgs/oizys/internal/git/main.go index 5afae70..ae13e85 100644 --- a/pkgs/oizys/internal/git/main.go +++ b/pkgs/oizys/internal/git/main.go @@ -1,18 +1,24 @@ -package oizys +package git import ( "fmt" + "oizys/internal/ui" "os" "os/exec" "github.com/charmbracelet/log" - "oizys/internal/ui" ) type GitRepo struct { path string } +func NewRepo(path string) *GitRepo { + repo := new(GitRepo) + repo.path = path + return repo +} + func (g *GitRepo) git(rest ...string) *exec.Cmd { args := []string{"-C", g.path} args = append(args, rest...) @@ -21,8 +27,22 @@ func (g *GitRepo) git(rest ...string) *exec.Cmd { return cmd } -func GitPull(workDir string) { - g := GitRepo{workDir} +func (g *GitRepo) Fetch() { + err := g.git("fetch").Run() + if err != nil { + log.Fatal(err) + } +} + +func (g *GitRepo) Rebase(ref string) { + g.Status() + err := g.git("rebase", ref).Run() + if err != nil { + log.Fatal(err) + } +} + +func (g *GitRepo) Status() { cmdOutput, err := g.git("status", "--porcelain").Output() if err != nil { log.Fatal(err) @@ -33,8 +53,11 @@ func GitPull(workDir string) { ui.ShowFailedOutput(cmdOutput) os.Exit(1) } +} - cmdOutput, err = g.git("pull").CombinedOutput() +func (g *GitRepo) Pull() { + g.Status() + cmdOutput, err := g.git("pull").CombinedOutput() if err != nil { ui.ShowFailedOutput(cmdOutput) log.Fatal(err) diff --git a/pkgs/oizys/internal/github/main.go b/pkgs/oizys/internal/github/main.go new file mode 100644 index 0000000..008ad5d --- /dev/null +++ b/pkgs/oizys/internal/github/main.go @@ -0,0 +1,154 @@ +package github + +import ( + "archive/zip" + "bytes" + "context" + "fmt" + "io" + "net/http" + "net/url" + "oizys/internal/oizys" + + "github.com/charmbracelet/log" + "github.com/google/go-github/v63/github" +) + +var client *github.Client + +func init() { + client = github.NewClient(nil).WithAuthToken(oizys.GithubToken()) +} + +func ListWorkflows() { + workflowRuns, resp, err := client.Actions.ListWorkflowRunsByFileName( + context.Background(), + "daylinmorgan", + "oizys", + "update.yml", + nil, + ) + if err != nil { + log.Fatal("Failed to get a list of workflows", "err", err, "resp", resp) + } + for _, w := range workflowRuns.WorkflowRuns { + fmt.Println(w.GetID()) + fmt.Println(w.GetConclusion()) + } +} + +func ListUpdateRuns() (*github.WorkflowRuns, *github.Response) { + workflowRuns, resp, err := client.Actions.ListWorkflowRunsByFileName( + context.Background(), + "daylinmorgan", + "oizys", + "update.yml", + nil, + ) + if err != nil { + log.Fatal("failed to get last update run", "resp", resp, "err", err) + } + + return workflowRuns, resp +} + +func GetArtifacts(runID int64) (*github.ArtifactList, *github.Response) { + artifactList, resp, err := client.Actions.ListWorkflowRunArtifacts(context.Background(), "daylinmorgan", "oizys", runID, nil) + if err != nil { + log.Fatal("failed to get artifacts for run", "id", runID, "err", err) + } + return artifactList, resp +} + +func GetUpdateSummaryArtifact(runID int64) *github.Artifact { + artifactList, _ := GetArtifacts(runID) + for _, artifact := range artifactList.Artifacts { + if artifact.GetName() == "summary" { + return artifact + } + } + log.Fatal("failed to find summary for run", "id", runID) + return nil +} + +func GetUpdateSummaryUrl(runID int64) *url.URL { + artifact := GetUpdateSummaryArtifact(runID) + url, resp, err := client.Actions.DownloadArtifact(context.Background(), "daylinmorgan", "oizys", artifact.GetID(), 4) + if err != nil { + log.Fatal("failed to get update summary URL", "artifact", artifact.GetID(), "resp", resp) + } + return url +} + +func GetUpdateSummaryFromUrl(url *url.URL) []byte { + log.Debug(url.String()) + res, err := http.Get(url.String()) + if err != nil { + log.Fatal("failed to get update summary zip", "err", err) + } + body, err := io.ReadAll(res.Body) + res.Body.Close() + if res.StatusCode > 299 { + log.Fatalf("Response failed with status code: %d and\nbody: %s\n", res.StatusCode, body) + } + if err != nil { + log.Fatal(err) + } + return body +} + +func GetLastUpdateRun() *github.WorkflowRun { + workflowRuns, _ := ListUpdateRuns() + run := workflowRuns.WorkflowRuns[0] + if run.GetConclusion() == "failure" { + log.Fatal("Most recent run was not successful", "runId", run.GetID(), "conclusion", run.GetConclusion()) + } + if run.GetStatus() == "in_progress" { + log.Fatalf("Most recent run is not finished\nview workflow run at: %s", run.GetHTMLURL()) + } + return run +} + +func GetUpateSummary(runID int64) (string, error) { + url := GetUpdateSummaryUrl(runID) + bytes := GetUpdateSummaryFromUrl(url) + md, err := ReadMarkdownFromZip(bytes, "summary.md") + return md, err +} + +func ReadMarkdownFromZip(zipData []byte, fileName string) (string, error) { + // Open the zip reader from the in-memory byte slice + reader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData))) + if err != nil { + return "", err + } + + // Search for the target file + var markdownFile *zip.File + for _, f := range reader.File { + if f.Name == fileName { + markdownFile = f + break + } + } + + if markdownFile == nil { + return "", fmt.Errorf("file %s not found in zip archive", fileName) + } + + // Open the markdown file reader + fileReader, err := markdownFile.Open() + if err != nil { + return "", err + } + defer fileReader.Close() + + // Read the markdown content + content, err := io.ReadAll(fileReader) + if err != nil { + return "", err + } + + // Return the markdown content as string + return string(content), nil +} diff --git a/pkgs/oizys/internal/oizys/main.go b/pkgs/oizys/internal/oizys/main.go index 78fe8b7..a9d3347 100644 --- a/pkgs/oizys/internal/oizys/main.go +++ b/pkgs/oizys/internal/oizys/main.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "io/fs" + "oizys/internal/git" + "oizys/internal/ui" "os" "os/exec" "strings" @@ -13,7 +15,6 @@ import ( "github.com/charmbracelet/log" e "oizys/internal/exec" - "oizys/internal/ui" ) var o *Oizys @@ -24,10 +25,13 @@ func init() { // verbose vs debug? type Oizys struct { + repo *git.GitRepo flake string host string cache string githubSummary string + githubToken string + local bool inCI bool verbose bool systemPath bool @@ -52,21 +56,29 @@ func New() *Oizys { } o.githubSummary = os.Getenv("GITHUB_STEP_SUMMARY") o.inCI = o.githubSummary != "" - + o.githubToken = os.Getenv("GITHUB_TOKEN") + o.repo = git.NewRepo(o.flake) return o } +func GithubToken() string { + return o.githubToken +} + func SetFlake(path string) { // Check path exists if path != "" { o.flake = path } - // check local path exists + + // check if path is local and exists if !strings.HasPrefix(o.flake, "github") && !strings.HasPrefix(o.flake, "git+") { if _, ok := os.LookupEnv("OIZYS_SKIP_CHECK"); !ok { if _, err := os.Stat(o.flake); errors.Is(err, fs.ErrNotExist) { log.Warnf("path to flake %s does not exist, using remote as fallback", o.flake) o.flake = "github:daylinmorgan/oizys" + } else { + o.local = true } } } @@ -127,33 +139,6 @@ func Output() { } } -func git(rest ...string) *exec.Cmd { - args := []string{"-C", o.flake} - args = append(args, rest...) - cmd := exec.Command("git", args...) - e.LogCmd(cmd) - return cmd -} - -func GitPull() { - cmdOutput, err := git("status", "--porcelain").Output() - if err != nil { - log.Fatal(err) - } - - if len(cmdOutput) > 0 { - fmt.Println("unstaged commits, cowardly exiting...") - ui.ShowFailedOutput(cmdOutput) - os.Exit(1) - } - - cmdOutput, err = git("pull").CombinedOutput() - if err != nil { - ui.ShowFailedOutput(cmdOutput) - log.Fatal(err) - } -} - func parseDryRun(buf string) (*ui.Packages, *ui.Packages) { lines := strings.Split(strings.TrimSpace(buf), "\n") var parts [2][]string @@ -260,14 +245,8 @@ func NixosRebuild(subcmd string, rest ...string) { e.ExitWithCommand(cmd) } -func NixBuild(nom bool, minimal bool, rest ...string) { - var cmdName string - if nom { - cmdName = "nom" - } else { - cmdName = "nix" - } - cmd := exec.Command(cmdName, "build") +func NixBuild(minimal bool, rest ...string) { + cmd := exec.Command("nix", "build") if o.resetCache { cmd.Args = append(cmd.Args, "--narinfo-cache-negative-ttl", "0") } @@ -318,10 +297,10 @@ func (o *Oizys) checkPath(name string) string { return fmt.Sprintf("%s#checks.x86_64-linux.%s", o.flake, name) } -func Checks(nom bool, rest ...string) { +func Checks(rest ...string) { checks := o.getChecks() for _, check := range checks { - NixBuild(nom, false, o.checkPath(check)) + NixBuild(false, o.checkPath(check)) } } @@ -338,8 +317,17 @@ func CacheBuild(rest ...string) { } func CI(rest ...string) { - args := []string{"workflow", "run", "build.yml", "-F", fmt.Sprintf("hosts=%s", o.host)} + args := []string{ + "workflow", "run", "build.yml", + "-F", fmt.Sprintf("hosts=%s", o.host), + } args = append(args, rest...) cmd := exec.Command("gh", args...) e.ExitWithCommand(cmd) } + +func UpdateRepo() { + log.Info("rebasing HEAD on origin/flake-lock") + o.repo.Fetch() + o.repo.Rebase("origin/flake-lock") +} diff --git a/pkgs/oizys/internal/oizys/nix.go b/pkgs/oizys/internal/oizys/nix.go index 03d2455..696d30c 100644 --- a/pkgs/oizys/internal/oizys/nix.go +++ b/pkgs/oizys/internal/oizys/nix.go @@ -7,8 +7,9 @@ import ( "os/exec" "strings" - "github.com/charmbracelet/log" e "oizys/internal/exec" + + "github.com/charmbracelet/log" ) var ignoredMap = stringSliceToMap( @@ -16,16 +17,12 @@ var ignoredMap = stringSliceToMap( // nix "ld-library-path", "builder.pl", "profile", "system-path", // nixos - "nixos-install", - "nixos-version", - "nixos-manual-html", - "nixos-configuration-reference-manpage", - "nixos-rebuild", "nixos-help", - "nixos-generate-config", - "nixos-enter", - "nixos-container", - "nixos-build-vms", + "nixos-install", "nixos-version", + "nixos-manual-html", "nixos-rebuild", + "nixos-configuration-reference-manpage", + "nixos-generate-config", "nixos-enter", + "nixos-container", "nixos-build-vms", "nixos-wsl-version", "nixos-wsl-welcome-message", "nixos-wsl-welcome", // trivial packages "restic-gdrive", "gitea", "lock", "code", diff --git a/pkgs/oizys/internal/ui/main.go b/pkgs/oizys/internal/ui/main.go index 7cc31b5..75243db 100644 --- a/pkgs/oizys/internal/ui/main.go +++ b/pkgs/oizys/internal/ui/main.go @@ -1,6 +1,7 @@ package ui import ( + "bufio" "fmt" "os" "sort" @@ -101,3 +102,26 @@ func (p *Packages) summary() { Render(fmt.Sprint(len(p.names))), ) } + +// Confirm asks the user for confirmation. +// valid inputs are: y/yes,n/no case insensitive. +func Confirm(s string) bool { + reader := bufio.NewReader(os.Stdin) + + for { + fmt.Printf("%s [y/n]: ", s) + + response, err := reader.ReadString('\n') + if err != nil { + log.Fatal(err) + } + + response = strings.ToLower(strings.TrimSpace(response)) + switch response { + case "y", "yes": + return true + case "n", "no": + return false + } + } +}