blob: 71c61d09dbe2dc19a756cefe97e0d3e45afae771 [file] [log] [blame]
/*
Copyright 2015 Runtime Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cli
import (
"bufio"
"bytes"
"fmt"
"github.com/hashicorp/logutils"
"github.com/spf13/viper"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
type NewtError struct {
Text string
StackTrace []byte
}
var Logger *log.Logger
var Verbosity int
var OK_STRING = " ok!\n"
const (
VERBOSITY_SILENT = 0
VERBOSITY_QUIET = 1
VERBOSITY_DEFAULT = 2
VERBOSITY_VERBOSE = 3
)
func (se *NewtError) Error() string {
return se.Text + "\n" + string(se.StackTrace)
}
func NewNewtError(msg string) *NewtError {
err := &NewtError{
Text: msg,
StackTrace: make([]byte, 1<<16),
}
runtime.Stack(err.StackTrace, true)
return err
}
func NewtErrorNoTrace(msg string) *NewtError {
return &NewtError{
Text: msg,
StackTrace: nil,
}
}
// Initialize the CLI module
func Init(level string, silent bool, quiet bool, verbose bool) {
if level == "" {
level = "WARN"
}
filter := &logutils.LevelFilter{
Levels: []logutils.LogLevel{"DEBUG", "VERBOSE", "INFO",
"WARN", "ERROR"},
MinLevel: logutils.LogLevel(level),
Writer: os.Stderr,
}
log.SetOutput(filter)
if silent {
Verbosity = VERBOSITY_SILENT
} else if quiet {
Verbosity = VERBOSITY_QUIET
} else if verbose {
Verbosity = VERBOSITY_VERBOSE
} else {
Verbosity = VERBOSITY_DEFAULT
}
}
func checkBoolMap(mapVar map[string]bool, item string) bool {
v, ok := mapVar[item]
return v && ok
}
// Read in the configuration file specified by name, in path
// return a new viper config object if successful, and error if not
func ReadConfig(path string, name string) (*viper.Viper, error) {
v := viper.New()
v.SetConfigType("yaml")
v.SetConfigName(name)
v.AddConfigPath(path)
err := v.ReadInConfig()
if err != nil {
return nil, NewNewtError(err.Error())
} else {
return v, nil
}
}
func GetStringIdentities(v *viper.Viper, t *Target, key string) string {
val := v.GetString(key)
if t == nil {
return val
}
idents := t.Identities
for _, ident := range idents {
overwriteVal := v.GetString(key + "." + ident + ".OVERWRITE")
if overwriteVal != "" {
val = strings.Trim(overwriteVal, "\n")
break
}
appendVal := v.GetString(key + "." + ident)
if appendVal != "" {
val += " " + strings.Trim(appendVal, "\n")
}
}
return strings.TrimSpace(val)
}
func GetStringSliceIdentities(v *viper.Viper, t *Target, key string) []string {
val := v.GetStringSlice(key)
// string empty items
result := []string{}
for _, item := range val {
if item == "" || item == " " {
continue
}
result = append(result, item)
}
if t == nil {
return result
}
idents := t.Identities
for _, ident := range idents {
result = append(result, v.GetStringSlice(key+"."+ident)...)
}
return result
}
func NodeExist(path string) bool {
if _, err := os.Stat(path); err == nil {
return true
} else {
return false
}
}
// Check whether the node (either dir or file) specified by path exists
func NodeNotExist(path string) bool {
if _, err := os.Stat(path); os.IsNotExist(err) {
return true
} else {
return false
}
}
func FileModificationTime(path string) (time.Time, error) {
fileInfo, err := os.Stat(path)
if err != nil {
epoch := time.Unix(0, 0)
if os.IsNotExist(err) {
return epoch, nil
} else {
return epoch, NewNewtError(err.Error())
}
}
return fileInfo.ModTime(), nil
}
// Execute the command specified by cmdStr on the shell and return results
func ShellCommand(cmdStr string) ([]byte, error) {
log.Print("[VERBOSE] " + cmdStr)
cmd := exec.Command("sh", "-c", cmdStr)
o, err := cmd.CombinedOutput()
log.Print("[VERBOSE] o=" + string(o))
if err != nil {
return o, NewNewtError(err.Error())
} else {
return o, nil
}
}
func CopyFile(srcFile string, destFile string) error {
_, err := ShellCommand(fmt.Sprintf("mkdir -p %s", filepath.Dir(destFile)))
if err != nil {
return err
}
if _, err := ShellCommand(fmt.Sprintf("cp -Rf %s %s", srcFile,
destFile)); err != nil {
return err
}
return nil
}
func CopyDir(srcDir, destDir string) error {
return CopyFile(srcDir, destDir)
}
// Print Silent, Quiet and Verbose aware status messages
func StatusMessage(level int, message string, args ...interface{}) {
if Verbosity >= level {
fmt.Printf(message, args...)
}
}
// Reads each line from the specified text file into an array of strings. If a
// line ends with a backslash, it is concatenated with the following line.
func ReadLines(path string) ([]string, error) {
file, err := os.Open(path)
if err != nil {
return nil, NewNewtError(err.Error())
}
defer file.Close()
lines := []string{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
concatted := false
if len(lines) != 0 {
prevLine := lines[len(lines)-1]
if len(prevLine) > 0 && prevLine[len(prevLine)-1:] == "\\" {
prevLine = prevLine[:len(prevLine)-1]
prevLine += line
lines[len(lines)-1] = prevLine
concatted = true
}
}
if !concatted {
lines = append(lines, line)
}
}
if scanner.Err() != nil {
return lines, NewNewtError(scanner.Err().Error())
}
return lines, nil
}
// Determines if a file was previously built with a command line invocation
// different from the one specified.
//
// @param dstFile The output file whose build invocation is being
// tested.
// @param cmd The command that would be used to generate the
// specified destination file.
//
// @return true if the command has changed or if the
// destination file was never built;
// false otherwise.
func CommandHasChanged(dstFile string, cmd string) bool {
cmdFile := dstFile + ".cmd"
prevCmd, err := ioutil.ReadFile(cmdFile)
if err != nil {
return true
}
return bytes.Compare(prevCmd, []byte(cmd)) != 0
}
// Writes a file containing the command-line invocation used to generate the
// specified file. The file that this function writes can be used later to
// determine if the set of compiler options has changed.
//
// @param dstFile The output file whose build invocation is being
// recorded.
// @param cmd The command to write.
func WriteCommandFile(dstFile string, cmd string) error {
cmdPath := dstFile + ".cmd"
err := ioutil.WriteFile(cmdPath, []byte(cmd), 0644)
if err != nil {
return err
}
return nil
}
// Removes all duplicate strings from the specified array, while preserving
// order.
func UniqueStrings(elems []string) []string {
set := make(map[string]bool)
result := make([]string, 0)
for _, elem := range elems {
if !set[elem] {
result = append(result, elem)
set[elem] = true
}
}
return result
}