├── README.md ├── clean.go ├── exec.go ├── formula.yaml ├── gen.go ├── images └── brionac-attack.gif ├── init.go ├── install.go └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # :dragon: Brionac 2 | 3 | A simple alternative means of the Brewfile 4 | 5 | ## Description 6 | 7 | One day, `brew bundle` is deprecated. 8 | 9 | > [What? “Warning: brew bundle is unsupported …” #30815](https://github.com/Homebrew/homebrew/issues/30815) 10 | 11 | Having said that, we want to manage in the Brewfile. Although there are some alternative means, they are too multi-functional to deal with "formula" installation easily. We just want to simplify the installation using the Brewfile. Then, it is :dragon: *Brionac* !!. 12 | 13 | Brionac will only install the formula described in the `formula.yaml`. Strictly speaking, Brionac synchronize formulas to `formula.yaml`. Also, Brionac will uninstall the formula that are installed without being described in `formula.yaml` 14 | 15 | ***DEMO:*** 16 | 17 | ![demo](images/brionac-attack.gif) 18 | 19 | ## Features 20 | 21 | - Install the formula described in `formula.yaml` 22 | - Uninstall the formula that are installed without being described in `formula.yaml` 23 | 24 | ## Requirements 25 | 26 | - [Homebrew](https://brew.sh) 27 | 28 | ## Usage 29 | 30 | ```bash 31 | $ brionac attack 32 | ``` 33 | 34 | If no arguments are given, Brionac will synchronise based on the `formula.yaml`. 35 | 36 | For more information, see `brionac help` 37 | 38 | ### Example 39 | 40 | See sample of `formula.yaml` below. 41 | 42 | ```yaml 43 | --- 44 | tap: 45 | - b4b4r07/brionac 46 | - b4b4r07/gomi 47 | 48 | brew: 49 | install: 50 | - { name: git } 51 | - { name: gomi } 52 | - { name: brionac } 53 | - { name: reattach-to-user-namespace } 54 | - { name: the_silver_searcher } 55 | - { name: tig } 56 | - { name: tmux } 57 | - { name: vim, args: --with-lua } 58 | - { name: zsh, args: --disable-etcdir } 59 | - { name: peco } 60 | ``` 61 | 62 | ## Installation 63 | 64 | The fact that you are interested in the Brionac shows that you are a homebrew user. 65 | 66 | $ brew tap b4b4r07/brionac 67 | $ brew install brionac 68 | 69 | or use `go get`, if you using High Sierra. 70 | 71 | $ go get -u github.com/b4b4r07/brionac 72 | 73 | or go to the [releases page](Link), find the version you want, download the binary file, and put it in your `$PATH` 74 | 75 | ## Author 76 | 77 | [BABAROT](http://tellme.tokyo) a.k.a. b4b4r07 78 | 79 | ## License 80 | 81 | Released under the [MIT](https://raw.githubusercontent.com/b4b4r07/dotfiles/master/doc/LICENSE-MIT.txt) License. 82 | -------------------------------------------------------------------------------- /clean.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/deckarep/golang-set" 5 | "reflect" 6 | "sort" 7 | ) 8 | 9 | type Formulas struct { 10 | installed []string 11 | described []string 12 | } 13 | 14 | func clean() (err error) { 15 | var fs Formulas 16 | fs.installed, err = listInstalledFormula() 17 | if err != nil { 18 | return err 19 | } 20 | fs.described, err = listDescribedFormula() 21 | if err != nil { 22 | return err 23 | } 24 | 25 | if !fs.Equal() { 26 | if err = fs.Clean(); err != nil { 27 | return err 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func (f Formulas) Equal() bool { 35 | sort.Strings(f.installed) 36 | sort.Strings(f.described) 37 | return reflect.DeepEqual(f.installed, f.described) 38 | } 39 | 40 | func (f Formulas) Clean() (err error) { 41 | installedClasses := mapset.NewSet() 42 | for _, f := range f.installed { 43 | installedClasses.Add(f) 44 | } 45 | describedClasses := mapset.NewSet() 46 | for _, f := range f.described { 47 | describedClasses.Add(f) 48 | } 49 | 50 | for _, f := range installedClasses.Difference(describedClasses).ToSlice() { 51 | err = run([]string{"brew", "uninstall", f.(string)}, Blue) 52 | } 53 | 54 | if err != nil { 55 | return err 56 | } 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /exec.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "github.com/daviddengcn/go-colortext" 7 | "os" 8 | "os/exec" 9 | "os/signal" 10 | "path/filepath" 11 | "strings" 12 | "syscall" 13 | ) 14 | 15 | type Color int 16 | 17 | const ( 18 | None Color = Color(ct.None) 19 | Red Color = Color(ct.Red) 20 | Blue Color = Color(ct.Blue) 21 | ) 22 | 23 | func handleSignal() { 24 | sc := make(chan os.Signal, 10) 25 | signal.Notify(sc, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP) 26 | go func() { 27 | <-sc 28 | ct.ResetColor() 29 | os.Exit(0) 30 | }() 31 | } 32 | 33 | var stdout = os.Stdout 34 | var stderr = os.Stderr 35 | var stdin = os.Stdin 36 | 37 | func run(args []string, c Color) error { 38 | if len(args) == 0 { 39 | return errors.New("") 40 | } 41 | cmd := exec.Command(args[0], args[1:]...) 42 | cmd.Stdout = stdout 43 | cmd.Stderr = stderr 44 | cmd.Stdin = stdin 45 | ct.ChangeColor(ct.Color(c), true, ct.None, false) 46 | err := cmd.Run() 47 | ct.ResetColor() 48 | return err 49 | } 50 | 51 | func justRun(args []string) error { 52 | if len(args) == 0 { 53 | return errors.New("") 54 | } 55 | cmd := exec.Command(args[0], args[1:]...) 56 | return cmd.Run() 57 | } 58 | 59 | func runAndGetStdout(args ...string) (out []string, err error) { 60 | cmd := exec.Command(args[0], args[1:]...) 61 | stdout, err := cmd.StdoutPipe() 62 | 63 | if err != nil { 64 | return 65 | } 66 | 67 | cmd.Start() 68 | 69 | scanner := bufio.NewScanner(stdout) 70 | for scanner.Scan() { 71 | out = append(out, scanner.Text()) 72 | } 73 | 74 | cmd.Wait() 75 | 76 | return 77 | } 78 | 79 | func getPath(args ...string) string { 80 | s, err := runAndGetStdout(args...) 81 | if err != nil { 82 | return "" 83 | } 84 | 85 | return filepath.Join(strings.Join(s, " ")) 86 | } 87 | -------------------------------------------------------------------------------- /formula.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | tap: 3 | - b4b4r07/gomi 4 | 5 | brew: 6 | install: 7 | - { name: gomi } 8 | - { name: readline } 9 | - { name: openssl } 10 | - { name: ansible } 11 | - { name: cmake } 12 | - { name: ctags } 13 | - { name: direnv } 14 | - { name: docker } 15 | - { name: git } 16 | - { name: jq } 17 | - { name: hub } 18 | - { name: imagemagick } 19 | - { name: mercurial } 20 | - { name: pwgen } 21 | - { name: python } 22 | - { name: reattach-to-user-namespace } 23 | - { name: the_silver_searcher } 24 | - { name: tig } 25 | - { name: tmux } 26 | - { name: xz } 27 | - { name: vim } 28 | - { name: wget } 29 | - { name: zsh, args: [--disable-etcdir] } 30 | - { name: nkf } 31 | - { name: libyaml } 32 | - { name: ruby-build } 33 | - { name: tree } 34 | - { name: peco } 35 | - { name: jbig2dec } 36 | - { name: cscope } 37 | - { name: ghr } 38 | - { name: rbenv } 39 | - { name: pkg-config } 40 | - { name: ghostscript } 41 | - { name: wdiff } 42 | - { name: ack } 43 | - { name: brew-cask } 44 | - { name: go } 45 | - { name: libpng } 46 | - { name: figlet } 47 | - { name: freetype } 48 | - { name: shellcheck } 49 | - { name: cowsay } 50 | - { name: gmp } 51 | - { name: jpeg } 52 | - { name: w3m } 53 | - { name: bdw-gc } 54 | - { name: libtool } 55 | - { name: gawk } 56 | - { name: libevent } 57 | - { name: groff } 58 | - { name: libtiff } 59 | - { name: terminal-notifier } 60 | - { name: autoconf } 61 | 62 | cask: 63 | install: 64 | - atom 65 | - calibre 66 | - firefox 67 | - google-chrome 68 | - google-japanese-ime 69 | - grandperspective 70 | - hipchat 71 | - iterm2 72 | - java 73 | - keepassx 74 | - kindle 75 | - mysqlworkbench 76 | - skype 77 | - spark 78 | - vagrant 79 | - virtualbox 80 | - zeroxed atom 81 | - calibre 82 | - firefox 83 | - google-chrome 84 | - google-japanese-ime 85 | - grandperspective 86 | - hipchat 87 | - iterm2 88 | - java 89 | - keepassx 90 | - kindle 91 | - mysqlworkbench 92 | - skype 93 | - spark 94 | - vagrant 95 | - virtualbox 96 | - zeroxed 97 | -------------------------------------------------------------------------------- /gen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "gopkg.in/yaml.v2" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | func listInstalledFormula() (out []string, err error) { 11 | out, err = runAndGetStdout("brew", "list") 12 | if err != nil { 13 | return 14 | } 15 | return 16 | } 17 | 18 | func gen() error { 19 | if _, err := os.Stat(formulaYaml); err == nil { 20 | return fmt.Errorf("%s: already exists", formulaYaml) 21 | } 22 | 23 | tappedFormula, err := listTappedFormula() 24 | if err != nil { 25 | return err 26 | } 27 | 28 | installedFormula, err := listInstalledFormula() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | fs := make([]Formula, len(installedFormula)) 34 | for i, f := range installedFormula { 35 | fs[i].Name = f 36 | } 37 | bri := Brionac{ 38 | Tap: tappedFormula, 39 | Brew: Brew{ 40 | fs, 41 | }, 42 | } 43 | c, err := yaml.Marshal(bri) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | err = ioutil.WriteFile(formulaYaml, c, 0644) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | return nil 54 | } 55 | 56 | func listTappedFormula() (out []string, err error) { 57 | out, err = runAndGetStdout("brew", "tap") 58 | if err != nil { 59 | return 60 | } 61 | return 62 | } 63 | -------------------------------------------------------------------------------- /images/brionac-attack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/babarot/brionac/22f2e2b4ee9ec029efff393ee5b24bf118fd7d43/images/brionac-attack.gif -------------------------------------------------------------------------------- /init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/mgutz/ansi" 6 | "os" 7 | "os/exec" 8 | ) 9 | 10 | func initBrionac() error { 11 | fmt.Print(ansi.LightWhite) 12 | fmt.Fprintln(os.Stdout, "Initializing Brionac...") 13 | fmt.Print(ansi.Reset) 14 | 15 | homebrewInstaller := `ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"` 16 | homebrewPath, err := exec.LookPath("brew") 17 | if err != nil { 18 | return fmt.Errorf("command not found -> brew") 19 | } 20 | 21 | fmt.Fprintf(os.Stdout, " not found brew, %s\n", homebrewInstaller) 22 | fmt.Fprintf(os.Stdout, " not found brew, %s\n", homebrewPath) 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /install.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | //"errors" 5 | "fmt" 6 | "github.com/cheggaaa/pb" 7 | "github.com/kyokomi/emoji" 8 | "github.com/mgutz/ansi" 9 | "gopkg.in/yaml.v2" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | "regexp" 14 | //"strings" 15 | ) 16 | 17 | type Brionac struct { 18 | Tap []string 19 | Brew Brew 20 | } 21 | 22 | type Brew struct { 23 | Formula []Formula `yaml:"install"` 24 | } 25 | 26 | type Formula struct { 27 | Name string 28 | Args []string 29 | } 30 | 31 | const formulaYaml = "formula.yaml" 32 | 33 | func readFormulaYaml() (Brionac, error) { 34 | var bri Brionac 35 | 36 | if _, err := os.Stat(formulaYaml); err != nil { 37 | return bri, fmt.Errorf("%s: no such file or directory", formulaYaml) 38 | } 39 | 40 | buf, err := ioutil.ReadFile(formulaYaml) 41 | if err != nil { 42 | return bri, err 43 | } 44 | 45 | err = yaml.Unmarshal(buf, &bri) 46 | if err != nil { 47 | str := []byte(err.Error()) 48 | assigned := regexp.MustCompile(`(line \d+)`) 49 | group := assigned.FindSubmatch(str) 50 | if len(group) != 0 { 51 | err = fmt.Errorf("Syntax Error at %s in %s", string(group[0]), formulaYaml) 52 | } 53 | } 54 | 55 | return bri, err 56 | } 57 | 58 | func install() error { 59 | d, err := readFormulaYaml() 60 | if err != nil { 61 | return err 62 | } 63 | 64 | defer func() { 65 | fmt.Fprintln(os.Stdout, emoji.Sprintf("\n:dragon: Brionac ended the %s.", state)) 66 | }() 67 | 68 | for _, tap := range d.Tap { 69 | if alreadyTapped(tap) { 70 | continue 71 | } 72 | run([]string{"brew", "tap", tap}, Blue) 73 | } 74 | 75 | var failure []string 76 | var success []string 77 | 78 | var installFormula []Formula 79 | for _, f := range d.Brew.Formula { 80 | if alreadyInstalled(f.Name) { 81 | failure = append(success, f.Name) 82 | } else { 83 | installFormula = append(installFormula, f) 84 | } 85 | } 86 | 87 | barLength := 0 88 | if state == "attack" { 89 | barLength += len(installFormula) 90 | } 91 | bar := pb.StartNew(barLength) 92 | bar.SetMaxWidth(80) 93 | 94 | if len(installFormula) == 0 { 95 | fmt.Fprintf(os.Stdout, "%d formulas are already installed.\n", len(d.Brew.Formula)) 96 | return nil 97 | } else { 98 | fmt.Fprintf(os.Stdout, "%d formulas installing...\n", len(d.Brew.Formula)) 99 | } 100 | 101 | //if verbose { 102 | // for _, brew := range installFormula { 103 | // var args []string 104 | // if brew.Args != "" { 105 | // args = strings.Split(brew.Args, " ") 106 | // } 107 | // if err := brew.Install(args); err != nil { 108 | // fc++ 109 | // failure["name"] = append(failure["name"].([]string), brew.Name) 110 | // } else { 111 | // sc++ 112 | // } 113 | // } 114 | //} 115 | for _, brew := range installFormula { 116 | bar.Increment() 117 | if err := brew.Install(brew.Args); err != nil { 118 | failure = append(failure, brew.Name) 119 | } else { 120 | failure = append(success, brew.Name) 121 | } 122 | } 123 | bar.FinishPrint("complete processing!\n") 124 | 125 | fmt.Print(ansi.LightWhite) 126 | fmt.Fprintf(os.Stdout, "success: %d failure: %d\n", len(success), len(failure)) 127 | fmt.Print(ansi.Reset) 128 | if len(failure) != 0 { 129 | fmt.Print(ansi.Red) 130 | fmt.Fprintf(os.Stdout, "errors: %q\n", failure) 131 | fmt.Fprintf(os.Stdout, " You should run `brew install '\n") 132 | fmt.Print(ansi.Reset) 133 | } 134 | 135 | return nil 136 | } 137 | 138 | func alreadyTapped(name string) bool { 139 | tappedFormula, err := listTappedFormula() 140 | if err != nil { 141 | return true 142 | } 143 | for _, c := range tappedFormula { 144 | if c == name { 145 | return true 146 | } 147 | } 148 | return false 149 | } 150 | 151 | func alreadyInstalled(name string) bool { 152 | bin := filepath.Join(Cellar, name) 153 | if _, err := os.Stat(bin); err == nil { 154 | return true 155 | } 156 | return false 157 | } 158 | 159 | func (f Formula) Install(args []string) error { 160 | if alreadyInstalled(f.Name) { 161 | //return errors.New("already installed") 162 | return nil 163 | } 164 | 165 | cmdArgs := []string{"brew", "install"} 166 | cmdArgs = append(cmdArgs, args...) 167 | cmdArgs = append(cmdArgs, f.Name) 168 | 169 | //fmt.Printf("installing %s\n", f.Name) 170 | if verbose { 171 | return run(cmdArgs, Blue) 172 | } 173 | return justRun(cmdArgs) 174 | } 175 | 176 | func listDescribedFormula() ([]string, error) { 177 | var formulas []string 178 | 179 | d, err := readFormulaYaml() 180 | if err != nil { 181 | return formulas, err 182 | } 183 | 184 | for _, f := range d.Brew.Formula { 185 | formulas = append(formulas, f.Name) 186 | } 187 | 188 | return formulas, nil 189 | } 190 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | var Cellar = getPath("brew", "--cellar") 9 | var state string = "" 10 | var verbose = false 11 | 12 | func main() { 13 | var err error 14 | if len(os.Args) < 2 { 15 | usage() 16 | } 17 | fmt.Println(logo) 18 | 19 | switch os.Args[1] { 20 | case "attack", "a": 21 | state = "attack" 22 | if len(os.Args) == 3 && os.Args[2] == "-v" { 23 | verbose = true 24 | } 25 | //gen() 26 | //if err = install(); err == nil { 27 | // err = clean() 28 | //} 29 | err = install() 30 | case "install", "i": 31 | if len(os.Args) == 3 && os.Args[2] == "-v" { 32 | verbose = true 33 | } 34 | state = "install" 35 | err = install() 36 | case "clean", "c": 37 | err = clean() 38 | case "gen", "g": 39 | err = gen() 40 | case "init": 41 | initBrionac() 42 | default: 43 | usage() 44 | } 45 | if err != nil { 46 | fmt.Fprintln(os.Stderr, "brionac: ", err) 47 | os.Exit(1) 48 | } 49 | } 50 | 51 | func usage() { 52 | fmt.Printf(`Usage of %s: 53 | Tasks: 54 | brionac attack : Run gen, install, clean and some brew commands 55 | 56 | brionac install : Install brew formulas based on formula.yaml 57 | brionac clean : Uninstall brew formulas 58 | brionac gen : Generate formula.yaml 59 | `, os.Args[0]) 60 | os.Exit(1) 61 | } 62 | 63 | const logo = ` 64 | '########::'########::'####::'#######::'##::: ##::::'###:::::'######:: 65 | ##.... ##: ##.... ##:. ##::'##.... ##: ###:: ##:::'## ##:::'##... ##: 66 | ##:::: ##: ##:::: ##:: ##:: ##:::: ##: ####: ##::'##:. ##:: ##:::..:: 67 | ########:: ########::: ##:: ##:::: ##: ## ## ##:'##:::. ##: ##::::::: 68 | ##.... ##: ##.. ##:::: ##:: ##:::: ##: ##. ####: #########: ##::::::: 69 | ##:::: ##: ##::. ##::: ##:: ##:::: ##: ##:. ###: ##.... ##: ##::: ##: 70 | ########:: ##:::. ##:'####:. #######:: ##::. ##: ##:::: ##:. ######:: 71 | ........:::..:::::..::....:::.......:::..::::..::..:::::..:::......::: 72 | ` 73 | --------------------------------------------------------------------------------