Skip to content

Commit

Permalink
[FEATURE] Introduces homebrew task (#67)
Browse files Browse the repository at this point in the history
* Respond to code review and migrate to the new task api

* Fix naming

* Files to formulas

* Simplify constructor for homebrew

* Split the work for homebrew between cask and cellar

* Fixes comments

* Adds more tests for caskroom

* Privatize getPathParts/setPathParts

* Adds some explanation about formula construction

* Privatize isInstalled of cellar and caskroom

* Removes uncessary outputfilter

* Moves codes that test installation in needed
  • Loading branch information
Mathieu Leduc-Hamel committed Jun 20, 2018
1 parent a472e3b commit e40c3f5
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 12 deletions.
2 changes: 2 additions & 0 deletions dev.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
up:
- go: 1.10.1
- golang_dep
- homebrew:
- curl
- custom:
name: Install shellcheck
met?: test -e /usr/local/Cellar/shellcheck
Expand Down
24 changes: 12 additions & 12 deletions pkg/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,30 +62,22 @@ func (e *Env) Environ() (vars []string) {
return vars
}

func (e *Env) getAndSplitPath() []string {
return strings.Split(e.env["PATH"], ":")
}

func (e *Env) joinAndSetPath(elems ...string) {
e.env["PATH"] = strings.Join(elems, ":")
}

// PrependToPath inserts a new path at the beginning of the PATH variable
func (e *Env) PrependToPath(path string) {
elems := e.getAndSplitPath()
elems := e.getPathParts()
elems = append([]string{path}, elems...)
e.joinAndSetPath(elems...)
e.setPathParts(elems...)
}

// RemoveFromPath removes all path entries matching a substring
func (e *Env) RemoveFromPath(substring string) {
newElems := []string{}
for _, elem := range e.getAndSplitPath() {
for _, elem := range e.getPathParts() {
if !strings.Contains(elem, substring) {
newElems = append(newElems, elem)
}
}
e.joinAndSetPath(newElems...)
e.setPathParts(newElems...)
}

// Changed returns all changes made on the variables
Expand All @@ -104,3 +96,11 @@ func (e *Env) Changed() []VariableChange {
}
return c
}

func (e *Env) getPathParts() []string {
return strings.Split(e.env["PATH"], ":")
}

func (e *Env) setPathParts(elems ...string) {
e.env["PATH"] = strings.Join(elems, ":")
}
77 changes: 77 additions & 0 deletions pkg/helpers/homebrew.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package helpers

import (
"path/filepath"
"strings"

"github.com/devbuddy/devbuddy/pkg/utils"
)

type caskroom struct {
prefix string
}

type cellar struct {
prefix string
}

// Homebrew represent an homebrew installation
type Homebrew struct {
caskroom *caskroom
cellar *cellar
}

// NewHomebrew is returning a new Cellar
func NewHomebrew() *Homebrew {
prefix := "/usr/local"

return &Homebrew{
cellar: &cellar{prefix: prefix},
caskroom: &caskroom{prefix: prefix},
}
}

// NewHomebrewWithPrefix is returning a new Cellar at prefix
func NewHomebrewWithPrefix(prefix string) *Homebrew {
return &Homebrew{
cellar: &cellar{prefix: prefix},
caskroom: &caskroom{prefix: prefix},
}
}

// IsInstalled returns true if `pkg` is installed in cellar or in caskroom
func (h *Homebrew) IsInstalled(formula string) (installed bool) {
path := buildFormulaPath(formula)

return h.cellar.isInstalled(path) || h.caskroom.isInstalled(path)
}

// buildFormulaPath building a formula name from a full path by doing the following operations:
// 1. split the path by `/`
// 2. returns the filename
// 3. removes the file extension
// 4. returns the resulting formula name
func buildFormulaPath(path string) string {
results := strings.Split(path, "/")
formula := results[len(results)-1]
return strings.TrimSuffix(formula, filepath.Ext(formula))
}

// isInstalled returns true if formulua was installed in Caskrook
func (c *caskroom) isInstalled(formula string) bool {
path := "/opt/homebrew-cask/Caskroom"

if !utils.PathExists(path) {
path = filepath.Join(c.prefix, "Caskroom")
}

return utils.PathExists(filepath.Join(path, formula))
}

// isInstalled returns true if formulua was installed in cellar
func (c *cellar) isInstalled(formula string) bool {
path := filepath.Join(c.prefix, "Cellar")

path = filepath.Join(path, formula)
return utils.PathExists(path)
}
28 changes: 28 additions & 0 deletions pkg/helpers/homebrew_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package helpers

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func TestIsInCellar(t *testing.T) {
prefix, err := ioutil.TempDir("/tmp", "dad-brew")
require.NoError(t, err, "ioutil.TempDir() failed")

cellarPath := filepath.Join(prefix, "Cellar")

caskroomPath := filepath.Join(prefix, "Caskroom")

os.MkdirAll(filepath.Join(cellarPath, "curl", "1.2.3"), os.ModePerm)
os.MkdirAll(filepath.Join(caskroomPath, "emacs", "26.1"), os.ModePerm)

h := NewHomebrewWithPrefix(prefix)

require.Truef(t, h.IsInstalled("curl"), "Curl is properly installed in Cellar %s", cellarPath)
require.Falsef(t, h.IsInstalled("vim"), "vim is missing from Homebrew %s", prefix)
require.True(t, h.IsInstalled("emacs"), "Emacs is properly installed in caskroom %s", caskroomPath)
}
77 changes: 77 additions & 0 deletions pkg/tasks/homebrew.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package tasks

import (
"fmt"
"strings"

"github.com/devbuddy/devbuddy/pkg/helpers"
)

func init() {
allTasks["homebrew"] = newHomebrew
}

type Homebrew struct {
formulas []string
}

func newHomebrew(config *taskConfig) (Task, error) {
task := &Homebrew{}

for _, value := range config.payload.([]interface{}) {
if v, ok := value.(string); ok {
task.formulas = append(task.formulas, v)
} else {
return nil, fmt.Errorf("invalid homebrew formulas")
}
}

if len(task.formulas) == 0 {
return nil, fmt.Errorf("no homebrew formulas specified")
}

return task, nil
}

func (h *Homebrew) name() string {
return "Homebrew"
}

func (h *Homebrew) header() string {
return strings.Join(h.formulas, ", ")
}

func (h *Homebrew) actions(ctx *context) (actions []taskAction) {
for _, f := range h.formulas {
actions = append(actions, &brewInstall{formula: f})
}
return
}

type brewInstall struct {
formula string
success bool
}

func (b *brewInstall) description() string {
return fmt.Sprintf("installing %s", b.formula)
}

func (b *brewInstall) needed(ctx *context) (bool, error) {
brew := helpers.NewHomebrew()

installed := brew.IsInstalled(b.formula)

return !installed, nil
}

func (b *brewInstall) run(ctx *context) error {
err := command(ctx, "brew", "install", b.formula).Run()

if err != nil {
return fmt.Errorf("Homebrew failed: %s", err)
}

b.success = true
return nil
}
16 changes: 16 additions & 0 deletions pkg/tasks/homebrew_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package tasks

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestHomebrew(t *testing.T) {
task := ensureLoadTestTask(t, `
homebrew:
- file1
- file2
`)
require.Equal(t, task.(*Homebrew).formulas, []string{"file1", "file2"})
}

0 comments on commit e40c3f5

Please sign in to comment.