diff --git a/internal/modules/fonts/fonts.go b/internal/modules/fonts/fonts.go new file mode 100644 index 0000000..9dc7c9c --- /dev/null +++ b/internal/modules/fonts/fonts.go @@ -0,0 +1,82 @@ +// Package fonts installs AUR fonts via the yay helper. +package fonts + +import ( + "context" + "fmt" + "strings" + + "github.com/MDietermann/ArduinoArchSetup/internal/module" + "github.com/MDietermann/ArduinoArchSetup/internal/platform" +) + +type Module struct { + fonts []string + pm platform.PackageManager + aur platform.AURHelper +} + +func New(fonts []string, pm platform.PackageManager, aur platform.AURHelper) Module { + return Module{fonts: fonts, pm: pm, aur: aur} +} + +type state struct{ Missing []string } + +func (Module) Meta() module.Meta { + return module.Meta{ID: "fonts", Name: "Nerd fonts", Description: "Install AUR nerd fonts."} +} + +func (m Module) Detect(ctx context.Context) (module.State, error) { + var missing []string + for _, f := range m.fonts { + ok, err := m.pm.IsInstalled(ctx, f) + if err != nil { + return nil, err + } + if !ok { + missing = append(missing, f) + } + } + return state{Missing: missing}, nil +} + +func (m Module) Plan(ctx context.Context, cur module.State) (module.Plan, error) { + s, _ := cur.(state) + if len(s.Missing) == 0 { + return module.Plan{}, nil + } + missing := append([]string{}, s.Missing...) + return module.Plan{Actions: []module.Action{ + { + Description: "ensure yay installed", + Kind: module.ActionPackageInstall, + Exec: func(ctx context.Context) error { return m.aur.EnsureInstalled(ctx) }, + }, + { + Description: "yay install " + strings.Join(missing, ", "), + Kind: module.ActionPackageInstall, + Exec: func(ctx context.Context) error { return m.aur.Install(ctx, missing) }, + }, + }}, 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 + } + if miss := st.(state).Missing; len(miss) > 0 { + return fmt.Errorf("fonts still missing: %v", miss) + } + return nil +} diff --git a/internal/modules/fonts/fonts_test.go b/internal/modules/fonts/fonts_test.go new file mode 100644 index 0000000..52c5150 --- /dev/null +++ b/internal/modules/fonts/fonts_test.go @@ -0,0 +1,72 @@ +package fonts + +import ( + "context" + "testing" + + "github.com/MDietermann/ArduinoArchSetup/internal/module" + "github.com/MDietermann/ArduinoArchSetup/internal/platform" +) + +type fakeRunner struct { + runCalls [][]string + outCalls [][]string + installed map[string]bool +} + +func (f *fakeRunner) Run(_ context.Context, name string, args ...string) error { + f.runCalls = append(f.runCalls, append([]string{name}, args...)) + return nil +} +func (f *fakeRunner) Output(_ context.Context, name string, args ...string) ([]byte, error) { + f.outCalls = append(f.outCalls, append([]string{name}, args...)) + if name == "pacman" && len(args) == 2 && args[0] == "-Q" { + if f.installed[args[1]] { + return []byte("ok"), nil + } + return nil, errOne + } + return nil, nil +} + +type eo struct{} + +func (eo) Error() string { return "exit 1" } + +var errOne = eo{} + +type nop struct{} + +func (nop) Progress(string, int, int, string) {} +func (nop) Log(string, string) {} + +func TestFontsPlanApplyUsesYay(t *testing.T) { + r := &fakeRunner{installed: map[string]bool{"yay": true}} + m := New([]string{"ttf-jetbrains-mono-nerd"}, platform.NewPacman(r), platform.NewYay(r)) + st, _ := m.Detect(context.Background()) + p, _ := m.Plan(context.Background(), st) + if err := m.Apply(context.Background(), p, nop{}); err != nil { + t.Fatal(err) + } + sawYay := false + for _, c := range r.runCalls { + if len(c) >= 2 && c[0] == "yay" && c[1] == "-S" { + sawYay = true + } + } + if !sawYay { + t.Fatalf("no yay install; calls=%v", r.runCalls) + } +} + +func TestFontsDetectPresentNoActions(t *testing.T) { + r := &fakeRunner{installed: map[string]bool{"yay": true, "ttf-jetbrains-mono-nerd": true}} + m := New([]string{"ttf-jetbrains-mono-nerd"}, platform.NewPacman(r), platform.NewYay(r)) + st, _ := m.Detect(context.Background()) + p, _ := m.Plan(context.Background(), st) + if !p.IsEmpty() { + t.Fatal("want empty plan when already installed") + } +} + +var _ module.Module = Module{}