diff --git a/go.mod b/go.mod index a39d3c4..7ce5265 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/catppuccin/go v0.2.0 // indirect + github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/charmbracelet/x/ansi v0.4.5 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/go.sum b/go.sum index 5f78580..0e03fbe 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQW github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= +github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8= github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= diff --git a/main.go b/main.go index a7c1d13..23df596 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,8 @@ import ( "runtime" "strings" - "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/progress" + "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" @@ -76,7 +77,6 @@ func downloadGolang(golangVersion string) error { } else { link = "https://go.dev/dl/go" + golangVersion + ".linux-amd64.tar.gz" } - resp, err := http.Get(link) if err != nil { return fmt.Errorf("Fehler beim Herunterladen von Go: %v", err) @@ -137,32 +137,120 @@ func installSpecialSoftware() error { } type model struct { - list list.Model packages []string + index int + width int + height int + spinner spinner.Model + progress progress.Model + done bool +} + +var ( + currentPkgNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211")) + doneStyle = lipgloss.NewStyle().Margin(1, 2) + checkMark = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).SetString("✓") +) + +func newModel(packages []string) model { + p := progress.New( + progress.WithDefaultGradient(), + progress.WithWidth(40), + progress.WithoutPercentage(), + ) + s := spinner.New() + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63")) + return model{ + packages: packages, + spinner: s, + progress: p, + } } func (m model) Init() tea.Cmd { - return nil + return tea.Batch(installPackageCmd(m.packages[m.index]), m.spinner.Tick) } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width, m.height = msg.Width, msg.Height case tea.KeyMsg: - if msg.String() == "q" { + switch msg.String() { + case "ctrl+c", "esc", "q": return m, tea.Quit } - case tea.WindowSizeMsg: - h, v := lipgloss.NewStyle().Margin(1, 2).GetFrameSize() - m.list.SetSize(msg.Width-h, msg.Height-v) - } + case installedPkgMsg: + pkg := m.packages[m.index] + if m.index >= len(m.packages)-1 { + m.done = true + return m, tea.Sequence( + tea.Printf("%s %s", checkMark, pkg), + tea.Quit, + ) + } - var cmd tea.Cmd - m.list, cmd = m.list.Update(msg) - return m, cmd + m.index++ + progressCmd := m.progress.SetPercent(float64(m.index) / float64(len(m.packages))) + + return m, tea.Batch( + progressCmd, + tea.Printf("%s %s", checkMark, pkg), + installPackageCmd(m.packages[m.index]), + ) + case spinner.TickMsg: + var cmd tea.Cmd + m.spinner, cmd = m.spinner.Update(msg) + return m, cmd + case progress.FrameMsg: + newModel, cmd := m.progress.Update(msg) + if newModel, ok := newModel.(progress.Model); ok { + m.progress = newModel + } + return m, cmd + } + return m, nil } func (m model) View() string { - return "\n" + m.list.View() + n := len(m.packages) + w := lipgloss.Width(fmt.Sprintf("%d", n)) + + if m.done { + return doneStyle.Render(fmt.Sprintf("Done! Installed %d packages.\n", n)) + } + + pkgCount := fmt.Sprintf(" %*d/%*d", w, m.index, w, n) + + spin := m.spinner.View() + " " + prog := m.progress.View() + cellsAvail := max(0, m.width-lipgloss.Width(spin+prog+pkgCount)) + + pkgName := currentPkgNameStyle.Render(m.packages[m.index]) + info := lipgloss.NewStyle().MaxWidth(cellsAvail).Render("Installing " + pkgName) + + cellsRemaining := max(0, m.width-lipgloss.Width(spin+info+prog+pkgCount)) + gap := strings.Repeat(" ", cellsRemaining) + + return spin + info + gap + prog + pkgCount +} + +type installedPkgMsg string + +func installPackageCmd(pkg string) tea.Cmd { + return func() tea.Msg { + if err := installPackage(installCommand, pkg); err != nil { + log.Printf("Fehler beim Installieren von %s: %v", pkg, err) + } + return installedPkgMsg(pkg) + } +} + +func max(a, b int) int { + if a > b { + return a + } + return b } var rootCmd = &cobra.Command{ @@ -188,6 +276,8 @@ type Config struct { SpecialPackages SpecialPackages `mapstructure:"special_packages"` } +var installCommand string + func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringP("config", "c", "", "Pfad zur Konfigurationsdatei") @@ -233,10 +323,6 @@ func installSpecialPackages(sp SpecialPackages) error { return nil } -type item string - -func (i item) FilterValue() string { return string(i) } - func run(cmd *cobra.Command, args []string) { os, err := getLinuxDistribution() if err != nil { @@ -246,7 +332,7 @@ func run(cmd *cobra.Command, args []string) { if err != nil { log.Fatal(err) } - installCommand, err := getInstallCommand(pm) + installCommand, err = getInstallCommand(pm) if err != nil { log.Fatal(err) } @@ -274,51 +360,12 @@ func run(cmd *cobra.Command, args []string) { packages = append(packages, cfg.Packages.NonHeadless...) } - // packages := viper.GetStringSlice("packages") - - items := make([]list.Item, len(packages)) - for i, pkg := range packages { - items[i] = item(pkg) - } - - m := model{ - list: list.New(items, list.NewDefaultDelegate(), 0, 0), - packages: packages, - } - m.list.Title = "Zu installierende Pakete" - + m := newModel(packages) p := tea.NewProgram(m) if _, err := p.Run(); err != nil { log.Fatal(err) } - var confirm bool - form = huh.NewForm( - huh.NewGroup( - huh.NewConfirm(). - Title("Möchten Sie mit der Installation fortfahren?"). - Value(&confirm), - ), - ).WithTheme(huh.ThemeCatppuccin()) - - err = form.Run() - if err != nil { - log.Fatal(err) - } - - if !confirm { - fmt.Println("Installation abgebrochen.") - return - } - - for _, pkg := range packages { - if err := installPackage(installCommand, pkg); err != nil { - log.Printf("Fehler beim Installieren von %s: %v", pkg, err) - } else { - fmt.Printf("Erfolgreich installiert: %s\n", pkg) - } - } - var installSpecial bool form = huh.NewForm( huh.NewGroup( @@ -342,10 +389,10 @@ func run(cmd *cobra.Command, args []string) { } else { fmt.Println("Installation der speziellen Pakete übersprungen") } + if err := installSpecialPackages(cfg.SpecialPackages); err != nil { log.Printf("Fehler bei der Installation spezieller Pakete: %v", err) } - } func main() {