mirror of
https://github.com/89luca89/distrobox.git
synced 2026-08-19 01:14:49 -05:00
chore: introduce ui package (#31)
* move the Propter under screen package * add color formatters * add progress This module allows for printing a sequence of lines with check marks. Useful to show progress on a long running action (example: container init). * implement progess on create * implement progress on assemble * implement progress on enter * implement color * move internal/screen to pkg/ui * add printer * implement printer * fix progress finalize method * fix progress implementation for enter command
This commit is contained in:
committed by
Alessio Biancalana
parent
a738eaba81
commit
9b401c4543
@@ -9,10 +9,10 @@ import (
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/89luca89/distrobox/internal/prompt"
|
||||
"github.com/89luca89/distrobox/pkg/commands"
|
||||
"github.com/89luca89/distrobox/pkg/containermanager"
|
||||
"github.com/89luca89/distrobox/pkg/manifest"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
func newAssembleCommand() *cli.Command {
|
||||
@@ -108,9 +108,10 @@ func assembleAction(ctx context.Context, cmd *cli.Command, deleteFlag bool) erro
|
||||
opts.Replace = cmd.Bool("replace")
|
||||
}
|
||||
|
||||
prompter := prompt.NewPrompter(*bufio.NewReader(os.Stdin), os.Stdout)
|
||||
prompter := ui.NewPrompter(*bufio.NewReader(os.Stdin), os.Stdout)
|
||||
progress := ui.NewProgress(os.Stderr)
|
||||
|
||||
assembleCmd := commands.NewAssembleCommand(containerManager, prompter)
|
||||
assembleCmd := commands.NewAssembleCommand(containerManager, prompter, progress)
|
||||
|
||||
err = assembleCmd.Execute(ctx, opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,11 +4,13 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/89luca89/distrobox/pkg/commands"
|
||||
"github.com/89luca89/distrobox/pkg/containermanager"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
//nolint:funlen // function length is acceptable for CLI command definition
|
||||
@@ -208,7 +210,9 @@ func createAction(ctx context.Context, cmd *cli.Command) error {
|
||||
Rootful: cmd.Bool("root"),
|
||||
}
|
||||
|
||||
createCmd := commands.NewCreateCommand(containerManager)
|
||||
progress := ui.NewProgress(os.Stderr)
|
||||
|
||||
createCmd := commands.NewCreateCommand(containerManager, progress)
|
||||
err := createCmd.Execute(ctx, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create command failed: %w", err)
|
||||
|
||||
@@ -4,11 +4,13 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/89luca89/distrobox/pkg/commands"
|
||||
"github.com/89luca89/distrobox/pkg/containermanager"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
func newEnterCommand() *cli.Command {
|
||||
@@ -77,7 +79,10 @@ func enterAction(ctx context.Context, cmd *cli.Command) error {
|
||||
Verbose: cmd.Bool("verbose"),
|
||||
}
|
||||
|
||||
enterCmd := commands.NewEnterCommand(containerManager, options)
|
||||
progress := ui.NewProgress(os.Stderr)
|
||||
printer := ui.NewPrinter(os.Stderr, true)
|
||||
|
||||
enterCmd := commands.NewEnterCommand(containerManager, options, progress, printer)
|
||||
_, err := enterCmd.Execute(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute create command: %w", err)
|
||||
|
||||
@@ -10,12 +10,7 @@ import (
|
||||
|
||||
"github.com/89luca89/distrobox/pkg/commands"
|
||||
"github.com/89luca89/distrobox/pkg/containermanager"
|
||||
)
|
||||
|
||||
const (
|
||||
colorGreen = "\033[32m"
|
||||
colorYellow = "\033[33m"
|
||||
colorReset = "\033[0m"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
func newListCommand() *cli.Command {
|
||||
@@ -51,24 +46,24 @@ func listAction(ctx context.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
func printResult(result *commands.ListResult, noColor bool) {
|
||||
rowFormat := "%-12s | %-20s | %-18s | %-30s\n"
|
||||
|
||||
//nolint:forbidigo // Using fmt.Printf is acceptable here for CLI output
|
||||
fmt.Printf("%-12s | %-20s | %-18s | %-30s\n",
|
||||
"ID", "NAME", "STATUS", "IMAGE")
|
||||
fmt.Printf(rowFormat, "ID", "NAME", "STATUS", "IMAGE")
|
||||
|
||||
for _, c := range result.Containers {
|
||||
if noColor {
|
||||
//nolint:forbidigo // Using fmt.Printf is acceptable here for CLI output
|
||||
fmt.Printf("%-12s | %-20s | %-18s | %-30s\n",
|
||||
c.ID, c.Name, c.Status, c.Image)
|
||||
} else {
|
||||
color := colorYellow
|
||||
if c.IsRunning() {
|
||||
color = colorGreen
|
||||
}
|
||||
//nolint:forbidigo // Using fmt.Printf is acceptable here for CLI output
|
||||
fmt.Printf("%s%-12s | %-20s | %-18s | %-30s%s\n",
|
||||
color, c.ID, c.Name, c.Status, c.Image, colorReset)
|
||||
var line string
|
||||
switch {
|
||||
case noColor:
|
||||
line = rowFormat
|
||||
case c.IsRunning():
|
||||
line = ui.Green(rowFormat)
|
||||
default:
|
||||
line = ui.Yellow(rowFormat)
|
||||
}
|
||||
|
||||
//nolint:forbidigo // Using fmt.Printf is acceptable here for CLI output
|
||||
fmt.Printf(line, c.ID, c.Name, c.Status, c.Image)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/89luca89/distrobox/internal/prompt"
|
||||
"github.com/89luca89/distrobox/pkg/commands"
|
||||
"github.com/89luca89/distrobox/pkg/containermanager"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
func newRmCommand() *cli.Command {
|
||||
@@ -55,7 +55,7 @@ func rmAction(ctx context.Context, cmd *cli.Command) error {
|
||||
ContainerNames: cmd.Args().Slice(),
|
||||
}
|
||||
|
||||
prompter := prompt.NewPrompter(*bufio.NewReader(os.Stdin), os.Stdout)
|
||||
prompter := ui.NewPrompter(*bufio.NewReader(os.Stdin), os.Stdout)
|
||||
|
||||
rmCmd := commands.NewRmCommand(containerManager, prompter)
|
||||
_, err := rmCmd.Execute(ctx, options)
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/89luca89/distrobox/internal/prompt"
|
||||
"github.com/89luca89/distrobox/pkg/containermanager"
|
||||
"github.com/89luca89/distrobox/pkg/manifest"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
type AssembleOptions struct {
|
||||
@@ -30,13 +30,19 @@ type AssembleCommand struct {
|
||||
containermanager containermanager.ContainerManager
|
||||
createCmd *CreateCommand
|
||||
rmCmd *RmCommand
|
||||
progress *ui.Progress
|
||||
}
|
||||
|
||||
func NewAssembleCommand(cm containermanager.ContainerManager, prompter prompt.Prompter) *AssembleCommand {
|
||||
func NewAssembleCommand(
|
||||
cm containermanager.ContainerManager,
|
||||
prompter ui.Prompter,
|
||||
progress *ui.Progress,
|
||||
) *AssembleCommand {
|
||||
return &AssembleCommand{
|
||||
containermanager: cm,
|
||||
createCmd: NewCreateCommand(cm),
|
||||
createCmd: NewCreateCommand(cm, ui.NewDevNullProgress()),
|
||||
rmCmd: NewRmCommand(cm, prompter),
|
||||
progress: progress,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +81,7 @@ func (ac *AssembleCommand) Execute(ctx context.Context, opts AssembleOptions) er
|
||||
}
|
||||
|
||||
func (ac *AssembleCommand) deleteItem(ctx context.Context, item manifest.Item, dryRun bool) error {
|
||||
ac.progress.Next("Deleting %s...", item.Name)
|
||||
opts := RmOptions{
|
||||
NoTTY: dryRun,
|
||||
Force: true,
|
||||
@@ -85,8 +92,10 @@ func (ac *AssembleCommand) deleteItem(ctx context.Context, item manifest.Item, d
|
||||
|
||||
_, err := ac.rmCmd.Execute(ctx, opts)
|
||||
if err != nil {
|
||||
ac.progress.Fail()
|
||||
return fmt.Errorf("failed to execute delete item '%s': %w", item.Name, err)
|
||||
}
|
||||
ac.progress.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -100,6 +109,7 @@ func (ac *AssembleCommand) replaceItem(ctx context.Context, item manifest.Item,
|
||||
}
|
||||
|
||||
func (ac *AssembleCommand) createItem(ctx context.Context, item manifest.Item, dryRun bool) error {
|
||||
ac.progress.Next("Creating %s...", item.Name)
|
||||
opts := CreateOptions{
|
||||
ContainerClone: item.Clone,
|
||||
ContainerName: item.Name,
|
||||
@@ -126,7 +136,13 @@ func (ac *AssembleCommand) createItem(ctx context.Context, item manifest.Item, d
|
||||
// TODO: pull image if needed
|
||||
// https://github.com/89luca89/distrobox/blob/main/distrobox-create#L1016
|
||||
|
||||
return ac.createCmd.Execute(ctx, opts)
|
||||
err := ac.createCmd.Execute(ctx, opts)
|
||||
if err != nil {
|
||||
ac.progress.Fail()
|
||||
return err
|
||||
}
|
||||
ac.progress.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ac *AssembleCommand) joinHooks(hooks []string) string {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/89luca89/distrobox/pkg/containermanager"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -24,6 +25,7 @@ var ErrHostnameTooLong = fmt.Errorf("hostname too long, must be less than %d cha
|
||||
type CreateCommand struct {
|
||||
containerManager containermanager.ContainerManager
|
||||
generateEntryCmd *GenerateEntryCommand
|
||||
progress *ui.Progress
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
@@ -67,10 +69,11 @@ type CreateOptions struct {
|
||||
Rootful bool
|
||||
}
|
||||
|
||||
func NewCreateCommand(cm containermanager.ContainerManager) *CreateCommand {
|
||||
func NewCreateCommand(cm containermanager.ContainerManager, progress *ui.Progress) *CreateCommand {
|
||||
return &CreateCommand{
|
||||
containerManager: cm,
|
||||
generateEntryCmd: NewGenerateEntryCommand(NewListCommand(cm)),
|
||||
progress: progress,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +156,8 @@ func (c *CreateCommand) Execute(ctx context.Context, opts CreateOptions) error {
|
||||
// TODO: pull image if needed
|
||||
// https://github.com/89luca89/distrobox/blob/main/distrobox-create#L1016
|
||||
|
||||
c.progress.Next("Creating '%s' using image %s", containerName, containerImage)
|
||||
|
||||
err := c.containerManager.Create(
|
||||
ctx,
|
||||
containermanager.CreateOptions{
|
||||
@@ -180,9 +185,12 @@ func (c *CreateCommand) Execute(ctx context.Context, opts CreateOptions) error {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
c.progress.Fail()
|
||||
return fmt.Errorf("failed to create container: %w", err)
|
||||
}
|
||||
|
||||
c.progress.Done()
|
||||
|
||||
if opts.GenerateEntry && !opts.DryRun && !opts.Rootful {
|
||||
err := c.generateEntryCmd.Execute(
|
||||
ctx,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/89luca89/distrobox/pkg/containermanager"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
type EnterResult struct{}
|
||||
@@ -12,17 +13,26 @@ type EnterResult struct{}
|
||||
type EnterCommand struct {
|
||||
containerManager containermanager.ContainerManager
|
||||
options containermanager.EnterOptions
|
||||
progress *ui.Progress
|
||||
printer *ui.Printer
|
||||
}
|
||||
|
||||
func NewEnterCommand(cm containermanager.ContainerManager, options containermanager.EnterOptions) *EnterCommand {
|
||||
func NewEnterCommand(
|
||||
cm containermanager.ContainerManager,
|
||||
options containermanager.EnterOptions,
|
||||
progress *ui.Progress,
|
||||
printer *ui.Printer,
|
||||
) *EnterCommand {
|
||||
return &EnterCommand{
|
||||
containerManager: cm,
|
||||
options: options,
|
||||
progress: progress,
|
||||
printer: printer,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *EnterCommand) Execute(ctx context.Context) (*EnterResult, error) {
|
||||
err := c.containerManager.Enter(ctx, c.options)
|
||||
err := c.containerManager.Enter(ctx, c.options, c.progress, c.printer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to enter the container: %w", err)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/89luca89/distrobox/internal/prompt"
|
||||
"github.com/89luca89/distrobox/pkg/containermanager"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
type RmResult struct {
|
||||
@@ -17,7 +17,7 @@ type RmResult struct {
|
||||
type RmCommand struct {
|
||||
containerManager containermanager.ContainerManager
|
||||
listCmd *ListCommand
|
||||
prompter prompt.Prompter
|
||||
prompter ui.Prompter
|
||||
}
|
||||
|
||||
type RmOptions struct {
|
||||
@@ -30,7 +30,7 @@ type RmOptions struct {
|
||||
|
||||
func NewRmCommand(
|
||||
cm containermanager.ContainerManager,
|
||||
prompter prompt.Prompter,
|
||||
prompter ui.Prompter,
|
||||
) *RmCommand {
|
||||
return &RmCommand{
|
||||
containerManager: cm,
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/89luca89/distrobox/internal/prompt"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
@@ -68,8 +68,8 @@ type ContanerManagerType string
|
||||
|
||||
type ContainerManager interface {
|
||||
Name() string
|
||||
Enter(ctx context.Context, options EnterOptions) error
|
||||
Enter(ctx context.Context, options EnterOptions, progress *ui.Progress, printer *ui.Printer) error
|
||||
ListContainers(ctx context.Context) ([]Container, error)
|
||||
Create(ctx context.Context, opts CreateOptions) error
|
||||
Remove(ctx context.Context, containerName string, opts RmOptions, prompter prompt.Prompter) error
|
||||
Remove(ctx context.Context, containerName string, opts RmOptions, prompter ui.Prompter) error
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
"time"
|
||||
|
||||
insidedistrobox "github.com/89luca89/distrobox/internal/inside-distrobox"
|
||||
"github.com/89luca89/distrobox/internal/prompt"
|
||||
"github.com/89luca89/distrobox/internal/userenv"
|
||||
"github.com/89luca89/distrobox/pkg/containermanager"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -487,6 +487,8 @@ func (d *Docker) run(ctx context.Context, args []string, opts runOptions) (strin
|
||||
func (d *Docker) Enter(
|
||||
ctx context.Context,
|
||||
options containermanager.EnterOptions,
|
||||
progress *ui.Progress,
|
||||
printer *ui.Printer,
|
||||
) error {
|
||||
userEnv := userenv.LoadUserEnvironment(ctx)
|
||||
user := userEnv.User
|
||||
@@ -508,7 +510,18 @@ func (d *Docker) Enter(
|
||||
|
||||
inspectResult, err := d.InspectContainer(ctx, options.ContainerName)
|
||||
if err != nil || inspectResult.ContainerStatus != RunningStatus {
|
||||
_ = d.startContainer(ctx, options.ContainerName)
|
||||
logTimestamp := timestampNow()
|
||||
|
||||
_ = d.startContainer(ctx, options.ContainerName, progress)
|
||||
|
||||
// Monitor logs for setup completion
|
||||
if err := d.waitForSetup(ctx, options.ContainerName, logTimestamp, progress, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
progress.Finalize("Container Setup Complete!")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_, _ = d.run(ctx, append(command, commandArgs...), runOptions{Interactive: !options.NoTTY})
|
||||
@@ -520,7 +533,7 @@ func (d *Docker) Remove(
|
||||
ctx context.Context,
|
||||
containerName string,
|
||||
options containermanager.RmOptions,
|
||||
prompter prompt.Prompter,
|
||||
prompter ui.Prompter,
|
||||
) error {
|
||||
userEnv := userenv.LoadUserEnvironment(ctx)
|
||||
userHome := userEnv.Home
|
||||
@@ -908,9 +921,7 @@ func buildCommandArgs(customCommand string, user string, noTTY bool, unshareGrou
|
||||
return args
|
||||
}
|
||||
|
||||
func (d *Docker) startContainer(ctx context.Context, containerName string) error {
|
||||
logTimestamp := timestampNow()
|
||||
|
||||
func (d *Docker) startContainer(ctx context.Context, containerName string, progress *ui.Progress) error {
|
||||
// Start the container
|
||||
_, err := d.run(ctx, []string{"start", containerName}, runOptions{Interactive: true})
|
||||
if err != nil {
|
||||
@@ -927,7 +938,7 @@ func (d *Docker) startContainer(ctx context.Context, containerName string) error
|
||||
return fmt.Errorf("could not start entrypoint.\n%s", logs)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%-40s\t", "Starting container...")
|
||||
progress.Next("Starting container...")
|
||||
|
||||
userEnv := userenv.LoadUserEnvironment(ctx)
|
||||
|
||||
@@ -942,21 +953,21 @@ func (d *Docker) startContainer(ctx context.Context, containerName string) error
|
||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||
}
|
||||
|
||||
// Monitor logs for setup completion
|
||||
if err := d.waitForSetup(ctx, containerName, logTimestamp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "\nContainer Setup Complete!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Docker) waitForSetup(ctx context.Context, containerName string, since string) error {
|
||||
func (d *Docker) waitForSetup(
|
||||
ctx context.Context,
|
||||
containerName string,
|
||||
since string,
|
||||
progress *ui.Progress,
|
||||
printer *ui.Printer,
|
||||
) error {
|
||||
for {
|
||||
// Check container is still running
|
||||
inspectResult, err := d.InspectContainer(ctx, containerName)
|
||||
if err != nil || inspectResult.ContainerStatus != RunningStatus {
|
||||
fmt.Fprintln(os.Stderr, "\nContainer Setup Failure!")
|
||||
printer.PrintError("\nContainer Setup Failure!")
|
||||
return fmt.Errorf("container stopped during setup: %w", err)
|
||||
}
|
||||
|
||||
@@ -979,20 +990,21 @@ func (d *Docker) waitForSetup(ctx context.Context, containerName string, since s
|
||||
continue
|
||||
|
||||
case strings.HasPrefix(line, "Error:"):
|
||||
fmt.Fprintf(os.Stderr, "\033[31m %s\n\033[0m", line)
|
||||
progress.Fail()
|
||||
printer.PrintError(line)
|
||||
return fmt.Errorf("container setup error: %s", line)
|
||||
|
||||
case strings.HasPrefix(line, "Warning:"):
|
||||
fmt.Fprintf(os.Stderr, "\n\033[33m %s\033[0m", line)
|
||||
printer.PrintWarning(line)
|
||||
|
||||
case strings.HasPrefix(line, "distrobox:"):
|
||||
parts := strings.SplitN(line, " ", Two)
|
||||
if len(parts) > 1 {
|
||||
fmt.Fprintf(os.Stderr, "\033[32m [ OK ]\n\033[0m%-40s\t", parts[1])
|
||||
progress.Done()
|
||||
progress.Next("%s", parts[1])
|
||||
}
|
||||
|
||||
case strings.HasPrefix(line, "container_setup_done"):
|
||||
fmt.Fprintf(os.Stderr, "\033[32m [ OK ]\n\033[0m")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package ui
|
||||
|
||||
const (
|
||||
colorRed = "\033[31m"
|
||||
colorGreen = "\033[32m"
|
||||
colorYellow = "\033[33m"
|
||||
colorReset = "\033[0m"
|
||||
)
|
||||
|
||||
func Red(text string) string {
|
||||
return colorRed + text + colorReset
|
||||
}
|
||||
|
||||
func Green(text string) string {
|
||||
return colorGreen + text + colorReset
|
||||
}
|
||||
|
||||
func Yellow(text string) string {
|
||||
return colorYellow + text + colorReset
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Printer struct {
|
||||
writer io.Writer
|
||||
colorful bool
|
||||
}
|
||||
|
||||
func NewPrinter(writer io.Writer, colorful bool) *Printer {
|
||||
return &Printer{
|
||||
writer: writer,
|
||||
colorful: colorful,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Printer) Print(msg string, a ...any) {
|
||||
fmt.Fprintf(p.writer, msg, a...)
|
||||
}
|
||||
|
||||
func (p *Printer) Println(msg string, a ...any) {
|
||||
p.Print(msg+"\n", a...)
|
||||
}
|
||||
|
||||
func (p *Printer) PrintWarning(msg string, a ...any) {
|
||||
if p.colorful {
|
||||
msg = Yellow(msg)
|
||||
}
|
||||
p.Print(msg, a...)
|
||||
}
|
||||
|
||||
func (p *Printer) PrintWarningln(msg string, a ...any) {
|
||||
if p.colorful {
|
||||
msg = Yellow(msg)
|
||||
}
|
||||
p.Println(msg, a...)
|
||||
}
|
||||
|
||||
func (p *Printer) PrintError(msg string, a ...any) {
|
||||
if p.colorful {
|
||||
msg = Red(msg)
|
||||
}
|
||||
p.Print(msg, a...)
|
||||
}
|
||||
|
||||
func (p *Printer) PrintErrorln(msg string, a ...any) {
|
||||
if p.colorful {
|
||||
msg = Red(msg)
|
||||
}
|
||||
p.Println(msg, a...)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Progress struct {
|
||||
pending bool
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func NewProgress(writer io.Writer) *Progress {
|
||||
return &Progress{
|
||||
pending: false,
|
||||
writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDevNullProgress() *Progress {
|
||||
return &Progress{
|
||||
pending: false,
|
||||
writer: io.Discard,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Progress) Next(message string, a ...any) {
|
||||
if p.pending {
|
||||
p.Done()
|
||||
}
|
||||
|
||||
p.pending = true
|
||||
msg := fmt.Sprintf(message, a...)
|
||||
fmt.Fprintf(p.writer, "%-40s\t", msg)
|
||||
}
|
||||
|
||||
func (p *Progress) Finalize(message string, a ...any) {
|
||||
p.Done()
|
||||
p.Next(message, a...)
|
||||
fmt.Fprintf(p.writer, "\n")
|
||||
p.pending = false
|
||||
}
|
||||
|
||||
func (p *Progress) Done() {
|
||||
if !p.pending {
|
||||
return
|
||||
}
|
||||
|
||||
p.pending = false
|
||||
fmt.Fprintf(p.writer, "%s\n", Green("[ OK ] "))
|
||||
}
|
||||
|
||||
func (p *Progress) Fail() {
|
||||
if !p.pending {
|
||||
return
|
||||
}
|
||||
|
||||
p.pending = false
|
||||
fmt.Fprintf(p.writer, "%s\n", Red("[ ERR ]"))
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package prompt
|
||||
package ui
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -1,4 +1,4 @@
|
||||
package prompt_test
|
||||
package ui_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/89luca89/distrobox/internal/prompt"
|
||||
"github.com/89luca89/distrobox/pkg/ui"
|
||||
)
|
||||
|
||||
func TestPrompt_YesReturnsTrue(t *testing.T) {
|
||||
reader := bufio.NewReader(strings.NewReader("yes\n"))
|
||||
writer := &bytes.Buffer{}
|
||||
p := prompt.NewPrompter(*reader, writer)
|
||||
p := ui.NewPrompter(*reader, writer)
|
||||
|
||||
result := p.Prompt("Continue?", false)
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestPrompt_YesReturnsTrue(t *testing.T) {
|
||||
func TestPrompt_YReturnsTrue(t *testing.T) {
|
||||
reader := bufio.NewReader(strings.NewReader("y\n"))
|
||||
writer := &bytes.Buffer{}
|
||||
p := prompt.NewPrompter(*reader, writer)
|
||||
p := ui.NewPrompter(*reader, writer)
|
||||
|
||||
result := p.Prompt("Continue?", false)
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestPrompt_YReturnsTrue(t *testing.T) {
|
||||
func TestPrompt_NoReturnsFalse(t *testing.T) {
|
||||
reader := bufio.NewReader(strings.NewReader("no\n"))
|
||||
writer := &bytes.Buffer{}
|
||||
p := prompt.NewPrompter(*reader, writer)
|
||||
p := ui.NewPrompter(*reader, writer)
|
||||
|
||||
result := p.Prompt("Continue?", true)
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestPrompt_NoReturnsFalse(t *testing.T) {
|
||||
func TestPrompt_NReturnsFalse(t *testing.T) {
|
||||
reader := bufio.NewReader(strings.NewReader("n\n"))
|
||||
writer := &bytes.Buffer{}
|
||||
p := prompt.NewPrompter(*reader, writer)
|
||||
p := ui.NewPrompter(*reader, writer)
|
||||
|
||||
result := p.Prompt("Continue?", true)
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestPrompt_NReturnsFalse(t *testing.T) {
|
||||
func TestPrompt_InvalidInputReturnsDefaultTrue(t *testing.T) {
|
||||
reader := bufio.NewReader(strings.NewReader("maybe\n"))
|
||||
writer := &bytes.Buffer{}
|
||||
p := prompt.NewPrompter(*reader, writer)
|
||||
p := ui.NewPrompter(*reader, writer)
|
||||
|
||||
result := p.Prompt("Continue?", true)
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestPrompt_InvalidInputReturnsDefaultTrue(t *testing.T) {
|
||||
func TestPrompt_InvalidInputReturnsDefaultFalse(t *testing.T) {
|
||||
reader := bufio.NewReader(strings.NewReader("maybe\n"))
|
||||
writer := &bytes.Buffer{}
|
||||
p := prompt.NewPrompter(*reader, writer)
|
||||
p := ui.NewPrompter(*reader, writer)
|
||||
|
||||
result := p.Prompt("Continue?", false)
|
||||
|
||||
@@ -84,7 +84,7 @@ func TestPrompt_InvalidInputReturnsDefaultFalse(t *testing.T) {
|
||||
func TestPrompt_EmptyInputReturnsDefault(t *testing.T) {
|
||||
reader := bufio.NewReader(strings.NewReader("\n"))
|
||||
writer := &bytes.Buffer{}
|
||||
p := prompt.NewPrompter(*reader, writer)
|
||||
p := ui.NewPrompter(*reader, writer)
|
||||
|
||||
result := p.Prompt("Continue?", true)
|
||||
|
||||
@@ -96,7 +96,7 @@ func TestPrompt_EmptyInputReturnsDefault(t *testing.T) {
|
||||
func TestPrompt_WritesPromptToWriter(t *testing.T) {
|
||||
reader := bufio.NewReader(strings.NewReader("y\n"))
|
||||
writer := &bytes.Buffer{}
|
||||
p := prompt.NewPrompter(*reader, writer)
|
||||
p := ui.NewPrompter(*reader, writer)
|
||||
|
||||
p.Prompt("Continue?", true)
|
||||
|
||||
@@ -109,7 +109,7 @@ func TestPrompt_WritesPromptToWriter(t *testing.T) {
|
||||
func TestPrompt_WritesPromptWithDefaultNo(t *testing.T) {
|
||||
reader := bufio.NewReader(strings.NewReader("n\n"))
|
||||
writer := &bytes.Buffer{}
|
||||
p := prompt.NewPrompter(*reader, writer)
|
||||
p := ui.NewPrompter(*reader, writer)
|
||||
|
||||
p.Prompt("Delete file?", false)
|
||||
|
||||
Reference in New Issue
Block a user