// Package zsh installs zsh, oh-my-zsh, and configured plugins. package zsh import ( "context" "fmt" "os" "path/filepath" "github.com/MDietermann/ArduinoArchSetup/internal/config" "github.com/MDietermann/ArduinoArchSetup/internal/module" "github.com/MDietermann/ArduinoArchSetup/internal/platform" ) type Module struct { cfg config.ZshConfig pm platform.PackageManager git platform.GitClient } func New(cfg config.ZshConfig, pm platform.PackageManager, git platform.GitClient) Module { return Module{cfg: cfg, pm: pm, git: git} } type state struct { ZshInstalled bool OMZInstalled bool MissingPlugins []config.ZshPlugin } func omzHome() (string, error) { h, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(h, ".oh-my-zsh"), nil } func (Module) Meta() module.Meta { return module.Meta{ID: "zsh", Name: "Zsh + oh-my-zsh", Description: "Install zsh, oh-my-zsh, and plugins."} } func (m Module) Detect(ctx context.Context) (module.State, error) { var s state ok, err := m.pm.IsInstalled(ctx, "zsh") if err != nil { return nil, err } s.ZshInstalled = ok omz, err := omzHome() if err != nil { return nil, err } if _, err := os.Stat(omz); err == nil { s.OMZInstalled = true } for _, p := range m.cfg.Plugins { path := filepath.Join(omz, "custom", "plugins", p.Name, ".git") if _, err := os.Stat(path); err != nil { s.MissingPlugins = append(s.MissingPlugins, p) } } return s, nil } func (m Module) Plan(ctx context.Context, cur module.State) (module.Plan, error) { s, _ := cur.(state) var actions []module.Action if !s.ZshInstalled { actions = append(actions, module.Action{ Description: "pacman install zsh", Kind: module.ActionPackageInstall, Exec: func(ctx context.Context) error { return m.pm.Install(ctx, []string{"zsh"}) }, }) } if !s.OMZInstalled { actions = append(actions, module.Action{ Description: "clone oh-my-zsh from " + m.cfg.OhMyZshRepo, Kind: module.ActionGitClone, Exec: func(ctx context.Context) error { omz, err := omzHome() if err != nil { return err } return m.git.EnsureRepo(ctx, m.cfg.OhMyZshRepo, omz) }, }) } for _, p := range s.MissingPlugins { p := p // capture actions = append(actions, module.Action{ Description: "clone plugin " + p.Name, Kind: module.ActionGitClone, Exec: func(ctx context.Context) error { omz, err := omzHome() if err != nil { return err } return m.git.EnsureRepo(ctx, p.Repo, filepath.Join(omz, "custom", "plugins", p.Name)) }, }) } return module.Plan{Actions: actions}, nil } func (m Module) Apply(ctx context.Context, p module.Plan, r module.Reporter) error { for i, a := range p.Actions { r.Progress(m.Meta().ID, i+1, len(p.Actions), a.Description) if err := a.Exec(ctx); err != nil { return err } } return nil } func (m Module) Verify(ctx context.Context) error { st, err := m.Detect(ctx) if err != nil { return err } s := st.(state) if !s.ZshInstalled { return fmt.Errorf("zsh not installed") } if !s.OMZInstalled { return fmt.Errorf("oh-my-zsh not installed") } if len(s.MissingPlugins) > 0 { return fmt.Errorf("plugins missing: %v", s.MissingPlugins) } return nil }