jkawamoto/loci

View on GitHub
command/travis.go

Summary

Maintainability
A
0 mins
Test Coverage
//
// command/travis.go
//
// Copyright (c) 2016-2017 Junpei Kawamoto
//
// This software is released under the MIT License.
//
// http://opensource.org/licenses/mit-license.php
//

package command

import (
    "fmt"
    "io"
    "io/ioutil"
    "os"
    "sort"
    "strings"

    "gopkg.in/yaml.v2"
)

// Travis defines the structure of .travis.yml.
type Travis struct {
    // Base language.
    Language string
    // List of addons.
    Addons struct {
        Apt struct {
            Packages []string
        } `yaml:"apt,omitempty"`
    } `yaml:"addons,omitempty"`

    // List of commands run before install steps.
    BeforeInstall ListOrString `yaml:"before_install,omitempty"`

    // List of commands used to install packages.
    Install ListOrString `yaml:"install,omitempty"`

    // List of commands run before main scripts.
    BeforeScript ListOrString `yaml:"before_script,omitempty"`

    // List of scripts.
    Script ListOrString `yaml:"script,omitempty"`

    // List of environment variables.
    Env Env `yaml:"env,omitempty"`

    // Configuration for matrix build.
    Matrix Matrix `yaml:"matrix,omitempty"`

    // List of python versions. (used only in python)
    Python []string `yaml:"python,omitempty"`

    // List of golang versions. (used only in go)
    Go []string `yaml:"go,omitempty"`
    // Go import path. (used only in go)
    GoImportPath string `yaml:"go_import_path,omitempty"`
    // Build args for go project. (used only in go)
    GoBuildArgs string `yaml:"gobuild_args,omitempty"`
}

// Env defines the full structure of a definition of environment variables.
type Env struct {
    Global []string `yaml:"global,omitempty"`
    Matrix []string `yaml:"matrix,omitempty"`
}

// Matrix defines the structure of matrix element in .travis.yml.
type Matrix struct {
    Include []interface{} `yaml:"include,omitempty"`
    Exclude []interface{} `yaml:"exclude,omitempty"`
}

// TestCase is a set of environment variables and represented as a map of which
// a key is a name of one environment variable and the associated value is the
// value of the variable.
type TestCase map[string]string

// Slice returns a slice of strings representing this test case.
func (c TestCase) Slice() (res []string) {
    for key, value := range c {
        res = append(res, fmt.Sprintf("%v=%v", key, value))
    }
    sort.Strings(res)
    return
}

// Copy returns a hard copy of this test case.
func (c TestCase) Copy() TestCase {
    res := make(TestCase)
    for k, v := range c {
        res[k] = v
    }
    return res
}

// Merge updates this TestCase so that it also has key and values defined in the
// given test case. If both test cases have a same key, the value associated
// with the key will be overwritten by the value in the given test case.
func (c TestCase) Merge(o TestCase) TestCase {
    for k, v := range o {
        c[k] = v
    }
    return c
}

// Match returns true if and only if the given TestCase has same configuration
// as this test case.
func (c TestCase) Match(o TestCase) bool {

    if len(c) != len(o) {
        return false
    }
    for k, v := range o {
        if c[k] != v {
            return false
        }
    }
    return true

}

// TestCaseSet defines a set of arguments for build matrix.
// The test case set is a map of which key is a version and the associated value
// is a list of test cases.
type TestCaseSet map[string][]TestCase

// NewTravis creates a Travis object from a byte array.
func NewTravis(buf []byte) (res *Travis, err error) {

    res = &Travis{}
    if err = yaml.Unmarshal(buf, res); err != nil {
        return
    }
    return

}

// NewTravisFromFile creates a Travis object from a file.
func NewTravisFromFile(filename string) (res *Travis, err error) {

    fp, err := os.Open(filename)
    if err != nil {
        return
    }
    defer fp.Close()

    buf, err := ioutil.ReadAll(fp)
    if err != nil {
        return
    }
    return NewTravis(buf)

}

// ArgumentSet returns a set of arguments to run entrypoint based on a build
// matrix.
func (t *Travis) ArgumentSet(logger io.Writer) (res TestCaseSet, err error) {

    switch t.Language {
    case "python":
        res, err = t.argumentSetPython(logger)
    case "go":
        res, err = t.argumentSetGo()
    default:
        res = make(TestCaseSet)
        res[""] = []TestCase{}
    }

    return

}

// UnmarshalYAML defines a way to unmarshal variables of Env.
func (e *Env) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {

    var aux interface{}
    if err = unmarshal(&aux); err != nil {
        return
    }

    switch raw := aux.(type) {
    case []interface{}:
        // If attribute env has one list instead of global and/or matrix attributes.
        if len(raw) == 0 {
            return
        }
        value := make([]string, len(raw))
        for i, r := range raw {
            v, ok := r.(string)
            if !ok {
                return fmt.Errorf("An item in evn cannot be converted to a string: %v", aux)
            }
            value[i] = v
        }
        // If each string has more than two variables, it means matrix configuration.
        if len(strings.Split(strings.TrimSpace(value[0]), " ")) == 1 {
            e.Global = value
        } else {
            e.Matrix = value
        }

    case map[interface{}]interface{}:
        e.Global = parseEnvMap(raw, "global")
        e.Matrix = parseEnvMap(raw, "matrix")

    }

    return

}

// parseEnvMap parses a map of which key and value are defined as interface{},
// and returns a list of strings the given map's value, which is associated with
// the given key, represents.
func parseEnvMap(m map[interface{}]interface{}, key string) (res []string) {

    if selected, exist := m[key]; exist {
        switch items := selected.(type) {
        case string:
            res = []string{items}

        case []interface{}:
            for _, v := range items {
                if s, ok := v.(string); ok {
                    res = append(res, s)
                }
            }
        }
    }

    return

}

// parseEnv parses a string representing a set of environment variable
// definitions; and returns a TestCase.
func parseEnv(env string) (c TestCase) {
    c = make(TestCase)

    b := 0
    quoted := false
    for i, v := range env {
        if v == '"' {
            quoted = !quoted
        }
        if !quoted && v == ' ' {
            pair := strings.SplitN(strings.Replace(env[b:i], "\"", "", 2), "=", 2)
            if len(pair) == 2 {
                c[pair[0]] = pair[1]
            }
            b = i + 1
        }
    }
    pair := strings.SplitN(strings.Replace(env[b:], "\"", "", 2), "=", 2)
    if len(pair) == 2 {
        c[pair[0]] = pair[1]
    }
    return

}