Skip to content

Commit

Permalink
Add support for importing Gomfile's
Browse files Browse the repository at this point in the history
  • Loading branch information
mcuelenaere committed Feb 27, 2016
1 parent 3c7bfad commit a057cbb
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 7 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -408,13 +408,13 @@ That's up to you. It's not necessary, but it may also cause you extra
work and lots of extra space in your VCS. There may also be unforeseen errors
([see an example](https://github.com/mattfarina/golang-broken-vendor)).

#### Q: How do I import settings from GPM, Godep, or gb?
#### Q: How do I import settings from GPM, Godep, gom or gb?

There are two parts to importing.

1. If a package you import has configuration for GPM, Godep, or gb Glide will
1. If a package you import has configuration for GPM, Godep, gom or gb Glide will
recursively install the dependencies automatically.
2. If you would like to import configuration from GPM, Godep, or gb to Glide see
2. If you would like to import configuration from GPM, Godep, gom or gb to Glide see
the `glide import` command. For example, you can run `glide import godep` for
Glide to detect the projects Godep configuration and generate a `glide.yaml`
file for you.
Expand Down
21 changes: 21 additions & 0 deletions action/import_gom.go
@@ -0,0 +1,21 @@
package action

import (
"github.com/Masterminds/glide/gom"
"github.com/Masterminds/glide/msg"
)

// ImportGom imports a Gomfile.
func ImportGom(dest string) {
base := "."
config := EnsureConfig()
if !gom.Has(base) {
msg.Die("No gom data found.")
}
deps, err := gom.Parse(base)
if err != nil {
msg.Die("Failed to extract Gomfile: %s", err)
}
appendImports(deps, config)
writeConfigToFileOrStdout(config, dest)
}
13 changes: 13 additions & 0 deletions glide.go
Expand Up @@ -310,6 +310,19 @@ func commands() []cli.Command {
action.ImportGB(c.String("file"))
},
},
{
Name: "gom",
Usage: "Import Gomfile and display the would-be yaml file",
Flags: []cli.Flag{
cli.StringFlag{
Name: "file, f",
Usage: "Save all of the discovered dependencies to a Glide YAML file.",
},
},
Action: func(c *cli.Context) {
action.ImportGom(c.String("file"))
},
},
},
},
{
Expand Down
110 changes: 110 additions & 0 deletions gom/gom.go
@@ -0,0 +1,110 @@
package gom

import (
"errors"
"os"
"path/filepath"

"github.com/Masterminds/glide/cfg"
"github.com/Masterminds/glide/msg"
"github.com/Masterminds/glide/util"
)

// Has returns true if this dir has a Gomfile.
func Has(dir string) bool {
path := filepath.Join(dir, "Gomfile")
fi, err := os.Stat(path)
return err == nil && !fi.IsDir()
}

// Parse parses a Gomfile.
func Parse(dir string) ([]*cfg.Dependency, error) {
path := filepath.Join(dir, "Gomfile")
if fi, err := os.Stat(path); err != nil || fi.IsDir() {
return []*cfg.Dependency{}, nil
}

msg.Info("Found Gomfile.\n")
buf := []*cfg.Dependency{}

goms, err := parseGomfile(path)
if err != nil {
return []*cfg.Dependency{}, err
}

for _, gom := range goms {
// Do we need to skip this dependency?
if val, ok := gom.options["skipdep"]; ok && val.(string) == "true" {
continue
}

// Check for custom cloning command
if _, ok := gom.options["command"]; ok {
return []*cfg.Dependency{}, errors.New("Glide does not support custom Gomfile commands")
}

// Check for groups/environments
if val, ok := gom.options["group"]; ok {
groups := toStringSlice(val)
if !stringsContain(groups, "development") && !stringsContain(groups, "production") {
// right now we only support development and production
msg.Info("Skipping dependency '%s' because it isn't in the development or production group", gom.name)
continue
}
}

pkg, sub := util.NormalizeName(gom.name)

dep := &cfg.Dependency{
Name: pkg,
}

if len(sub) > 0 {
dep.Subpackages = []string{sub}
}

// Check for a specific revision
if val, ok := gom.options["commit"]; ok {
dep.Reference = val.(string)
}
if val, ok := gom.options["tag"]; ok {
dep.Reference = val.(string)
}
if val, ok := gom.options["branch"]; ok {
dep.Reference = val.(string)
}

// Parse goos and goarch
if val, ok := gom.options["goos"]; ok {
dep.Os = toStringSlice(val)
}
if val, ok := gom.options["goarch"]; ok {
dep.Arch = toStringSlice(val)
}

buf = append(buf, dep)
}

return buf, nil
}

func stringsContain(v []string, key string) bool {
for _, s := range v {
if s == key {
return true
}
}
return false
}

func toStringSlice(v interface{}) []string {
if v, ok := v.(string); ok {
return []string{v}
}

if v, ok := v.([]string); ok {
return v
}

return []string{}
}
130 changes: 130 additions & 0 deletions gom/parser.go
@@ -0,0 +1,130 @@
package gom

// This is copied + slightly adapted from gom's `gomfile.go` file.
//
// gom's license is MIT-style.

import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strings"
)

var qx = `'[^']*'|"[^"]*"`
var kx = `:[a-z][a-z0-9_]*`
var ax = `(?:\s*` + kx + `\s*|,\s*` + kx + `\s*)`
var re_group = regexp.MustCompile(`\s*group\s+((?:` + kx + `\s*|,\s*` + kx + `\s*)*)\s*do\s*$`)
var re_end = regexp.MustCompile(`\s*end\s*$`)
var re_gom = regexp.MustCompile(`^\s*gom\s+(` + qx + `)\s*((?:,\s*` + kx + `\s*=>\s*(?:` + qx + `|\s*\[\s*` + ax + `*\s*\]\s*))*)$`)
var re_options = regexp.MustCompile(`(,\s*` + kx + `\s*=>\s*(?:` + qx + `|\s*\[\s*` + ax + `*\s*\]\s*)\s*)`)

func unquote(name string) string {
name = strings.TrimSpace(name)
if len(name) > 2 {
if (name[0] == '\'' && name[len(name)-1] == '\'') || (name[0] == '"' && name[len(name)-1] == '"') {
return name[1 : len(name)-1]
}
}
return name
}

func parseOptions(line string, options map[string]interface{}) {
ss := re_options.FindAllStringSubmatch(line, -1)
re_a := regexp.MustCompile(ax)
for _, s := range ss {
kvs := strings.SplitN(strings.TrimSpace(s[0])[1:], "=>", 2)
kvs[0], kvs[1] = strings.TrimSpace(kvs[0]), strings.TrimSpace(kvs[1])
if kvs[1][0] == '[' {
as := re_a.FindAllStringSubmatch(kvs[1][1:len(kvs[1])-1], -1)
a := []string{}
for i := range as {
it := strings.TrimSpace(as[i][0])
if strings.HasPrefix(it, ",") {
it = strings.TrimSpace(it[1:])
}
if strings.HasPrefix(it, ":") {
it = strings.TrimSpace(it[1:])
}
a = append(a, it)
}
options[kvs[0][1:]] = a
} else {
options[kvs[0][1:]] = unquote(kvs[1])
}
}
}

type Gom struct {
name string
options map[string]interface{}
}

func parseGomfile(filename string) ([]Gom, error) {
f, err := os.Open(filename + ".lock")
if err != nil {
f, err = os.Open(filename)
if err != nil {
return nil, err
}
}
br := bufio.NewReader(f)

goms := make([]Gom, 0)

n := 0
skip := 0
valid := true
var envs []string
for {
n++
lb, _, err := br.ReadLine()
if err != nil {
if err == io.EOF {
return goms, nil
}
return nil, err
}
line := strings.TrimSpace(string(lb))
if line == "" || strings.HasPrefix(line, "#") {
continue
}

name := ""
options := make(map[string]interface{})
var items []string
if re_group.MatchString(line) {
envs = strings.Split(re_group.FindStringSubmatch(line)[1], ",")
for i := range envs {
envs[i] = strings.TrimSpace(envs[i])[1:]
}
valid = true
continue
} else if re_end.MatchString(line) {
if !valid {
skip--
if skip < 0 {
return nil, fmt.Errorf("Syntax Error at line %d", n)
}
}
valid = false
envs = nil
continue
} else if skip > 0 {
continue
} else if re_gom.MatchString(line) {
items = re_gom.FindStringSubmatch(line)[1:]
name = unquote(items[0])
parseOptions(items[1], options)
} else {
return nil, fmt.Errorf("Syntax Error at line %d", n)
}
if envs != nil {
options["group"] = envs
}
goms = append(goms, Gom{name, options})
}
return goms, nil
}
18 changes: 14 additions & 4 deletions importer/importer.go
@@ -1,4 +1,4 @@
// Package importer imports dependency configuration from Glide, Godep, GPM, and GB
// Package importer imports dependency configuration from Glide, Godep, GPM, GB and gom
package importer

import (
Expand All @@ -9,12 +9,13 @@ import (
"github.com/Masterminds/glide/cfg"
"github.com/Masterminds/glide/gb"
"github.com/Masterminds/glide/godep"
"github.com/Masterminds/glide/gom"
"github.com/Masterminds/glide/gpm"
)

var i = &DefaultImporter{}

// Import uses the DefaultImporter to import from Glide, Godep, GPM, and GB.
// Import uses the DefaultImporter to import from Glide, Godep, GPM, GB and gom.
func Import(path string) (bool, []*cfg.Dependency, error) {
return i.Import(path)
}
Expand All @@ -29,10 +30,10 @@ type Importer interface {
Import(path string) (bool, []*cfg.Dependency, error)
}

// DefaultImporter imports from Glide, Godep, GPM, and GB.
// DefaultImporter imports from Glide, Godep, GPM, GB and gom.
type DefaultImporter struct{}

// Import tries to import configuration from Glide, Godep, GPM, and GB.
// Import tries to import configuration from Glide, Godep, GPM, GB and gom.
func (d *DefaultImporter) Import(path string) (bool, []*cfg.Dependency, error) {

// Try importing from Glide first.
Expand Down Expand Up @@ -77,6 +78,15 @@ func (d *DefaultImporter) Import(path string) (bool, []*cfg.Dependency, error) {
return true, deps, nil
}

// Try importing from gom
if gom.Has(path) {
deps, err := gom.Parse(path)
if err != nil {
return false, []*cfg.Dependency{}, err
}
return true, deps, nil
}

// When none are found.
return false, []*cfg.Dependency{}, nil
}

0 comments on commit a057cbb

Please sign in to comment.