Merge pull request #110 from tockins/dev

1.5.1 Revision 1
This commit is contained in:
Alessio Pracchia 2017-11-05 21:30:43 +01:00 committed by GitHub
commit d743ea816c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 148 additions and 341 deletions

138
cmd.go
View File

@ -5,8 +5,6 @@ import (
"gopkg.in/urfave/cli.v2"
"os"
"path/filepath"
"time"
"reflect"
)
// Tool options customizable, should be moved in Cmd
@ -21,6 +19,8 @@ type tool struct {
// Cmds list of go commands
type Cmds struct {
Fix Cmd `yaml:"fix,omitempty" json:"fix,omitempty"`
Clean Cmd `yaml:"clean,omitempty" json:"clean,omitempty"`
Vet Cmd `yaml:"vet,omitempty" json:"vet,omitempty"`
Fmt Cmd `yaml:"fmt,omitempty" json:"fmt,omitempty"`
Test Cmd `yaml:"test,omitempty" json:"test,omitempty"`
@ -41,21 +41,29 @@ type Cmd struct {
}
// Clean duplicate projects
func (r *realize) clean() {
arr := r.Schema
for key, val := range arr {
if _, err := duplicates(val, arr[key+1:]); err != nil {
r.Schema = append(arr[:key], arr[key+1:]...)
break
func (r *realize) clean() error {
if len(r.Schema) > 0 {
arr := r.Schema
for key, val := range arr {
if _, err := duplicates(val, arr[key+1:]); err != nil {
r.Schema = append(arr[:key], arr[key+1:]...)
break
}
}
return nil
}
return errors.New("there are no projects")
}
// Add a new project
func (r *realize) add(p *cli.Context) error {
path, err := filepath.Abs(p.String("path"))
if err != nil{
return err
}
project := Project{
Name: filepath.Base(filepath.Clean(p.String("path"))),
Path: filepath.Clean(p.String("path")),
Path: path,
Cmds: Cmds{
Vet: Cmd{
Status: p.Bool("vet"),
@ -80,7 +88,7 @@ func (r *realize) add(p *cli.Context) error {
Args: params(p),
Watcher: Watch{
Paths: []string{"/"},
Ignore: []string{".git",".realize","vendor"},
Ignore: []string{".git", ".realize", "vendor"},
Exts: []string{"go"},
},
}
@ -95,14 +103,11 @@ func (r *realize) add(p *cli.Context) error {
func (r *realize) run(p *cli.Context) error {
var match bool
// check projects and remove duplicates
if len(r.Schema) > 0 {
r.clean()
}else{
return errors.New("there are no projects")
if err := r.clean(); err != nil {
return err
}
// set gobin
err := os.Setenv("GOBIN", filepath.Join(os.Getenv("GOPATH"), "bin"))
if err != nil {
if err := os.Setenv("GOBIN", filepath.Join(os.Getenv("GOPATH"), "bin")); err != nil {
return err
}
// loop projects
@ -113,113 +118,18 @@ func (r *realize) run(p *cli.Context) error {
}
for k, elm := range r.Schema {
// command start using name flag
if p.String("name") != "" && r.Schema[k].Name != p.String("name") {
if p.String("name") != "" && elm.Name != p.String("name") {
continue
}
// validate project path, if invalid get wdir or clean current
if !filepath.IsAbs(elm.Path){
r.Schema[k].Path = wdir()
}else{
r.Schema[k].Path = filepath.Clean(elm.Path)
}
// env variables
for key, item := range r.Schema[k].Environment {
if err := os.Setenv(key, item); err != nil {
r.Schema[k].Buffer.StdErr = append(r.Schema[k].Buffer.StdErr, BufferOut{Time: time.Now(), Text: err.Error(), Type: "Env error", Stream: ""})
}
}
// get basepath name
r.Schema[k].name = filepath.Base(r.Schema[k].Path)
fields := reflect.Indirect(reflect.ValueOf(&r.Schema[k].Cmds))
// Loop struct Cmds fields
for i := 0; i < fields.NumField(); i++ {
field := fields.Type().Field(i).Name
if fields.FieldByName(field).Type().Name() == "Cmd" {
v := fields.FieldByName(field)
// Loop struct Cmd
for i := 0; i < v.NumField(); i++ {
//f := v.Type().Field(i).Name
//fmt.Println(f)
//if f.IsValid() {
// if f.CanSet() {
// fmt.Println(f.)
// //switch f.Kind() {
// //case reflect.Bool:
// //case reflect.String:
// //case reflect.Slice:
// //}
// }
//}
}
}
}
if elm.Cmds.Fmt.Status {
if len(elm.Cmds.Fmt.Args) == 0 {
elm.Cmds.Fmt.Args = []string{"-s", "-w", "-e", "./"}
}
r.Schema[k].tools = append(r.Schema[k].tools, tool{
status: elm.Cmds.Fmt.Status,
cmd: replace([]string{"gofmt"}, r.Schema[k].Cmds.Fmt.Method),
options: split([]string{}, elm.Cmds.Fmt.Args),
name: "Fmt",
})
}
if elm.Cmds.Generate.Status {
r.Schema[k].tools = append(r.Schema[k].tools, tool{
status: elm.Cmds.Generate.Status,
cmd: replace([]string{"go", "generate"}, r.Schema[k].Cmds.Generate.Method),
options: split([]string{}, elm.Cmds.Generate.Args),
name: "Generate",
dir: true,
})
}
if elm.Cmds.Test.Status {
r.Schema[k].tools = append(r.Schema[k].tools, tool{
status: elm.Cmds.Test.Status,
cmd: replace([]string{"go", "test"}, r.Schema[k].Cmds.Test.Method),
options: split([]string{}, elm.Cmds.Test.Args),
name: "Test",
dir: true,
})
}
if elm.Cmds.Vet.Status {
r.Schema[k].tools = append(r.Schema[k].tools, tool{
status: elm.Cmds.Vet.Status,
cmd: replace([]string{"go", "vet"}, r.Schema[k].Cmds.Vet.Method),
options: split([]string{}, elm.Cmds.Vet.Args),
name: "Vet",
dir: true,
})
}
// default settings
r.Schema[k].Cmds.Install = Cmd{
Status: elm.Cmds.Install.Status,
Args: append([]string{}, elm.Cmds.Install.Args...),
method: replace([]string{"go", "install"}, r.Schema[k].Cmds.Install.Method),
name: "Install",
startTxt: "Installing...",
endTxt: "Installed",
}
r.Schema[k].Cmds.Build = Cmd{
Status: elm.Cmds.Build.Status,
Args: append([]string{}, elm.Cmds.Build.Args...),
method: replace([]string{"go", "build"}, r.Schema[k].Cmds.Build.Method),
name: "Build",
startTxt: "Building...",
endTxt: "Built",
}
r.Schema[k].parent = r
match = true
r.Schema[k].config(r)
go r.Schema[k].watch()
}
if !match {
return errors.New("there is no project with the given name")
}
wg.Wait()
return err
return nil
}
// Remove a project

View File

@ -5,10 +5,10 @@ import (
"gopkg.in/urfave/cli.v2"
"log"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"path/filepath"
)
type loggerT struct{}
@ -34,8 +34,8 @@ func TestRealize_Clean(t *testing.T) {
r.Schema = append(r.Schema, Project{Path: "test1"})
r.Schema = append(r.Schema, Project{Path: "test1"})
r.clean()
if len(r.Schema) > 2 {
t.Error("Expected only one project")
if len(r.Schema) != 2 {
t.Error("Expected two projects")
}
}
@ -81,7 +81,7 @@ func TestRealize_Add(t *testing.T) {
},
Watcher: Watch{
Paths: []string{"/"},
Ignore: []string{".git",".realize","vendor"},
Ignore: []string{".git", ".realize", "vendor"},
Exts: []string{"go"},
},
}

View File

@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"fmt"
"github.com/pkg/errors"
"log"
"os"
"os/exec"
@ -12,7 +13,6 @@ import (
"strings"
"sync"
"time"
"github.com/pkg/errors"
)
// GoCompile is used for compile a project
@ -71,7 +71,6 @@ func (p *Project) goRun(stop <-chan bool, runner chan bool) {
args = append(args, a...)
}
gobin := os.Getenv("GOBIN")
path := filepath.Join(gobin, p.name)
if _, err := os.Stat(path); err == nil {

View File

@ -70,7 +70,7 @@ func main() {
if err := r.insert(p); err != nil {
return err
}
if !p.Bool("no-config") && p.String("name") == ""{
if !p.Bool("no-config") && p.String("name") == "" {
if err := r.Settings.record(r); err != nil {
return err
}
@ -118,7 +118,7 @@ func main() {
interact.Run(&interact.Interact{
Before: func(context interact.Context) error {
context.SetErr(red.bold("INVALID INPUT"))
context.SetPrfx(color.Output, yellow.regular("[") + time.Now().Format("15:04:05") + yellow.regular("]") + yellow.bold("[")+"REALIZE"+yellow.bold("]"))
context.SetPrfx(color.Output, yellow.regular("[")+time.Now().Format("15:04:05")+yellow.regular("]")+yellow.bold("[")+"REALIZE"+yellow.bold("]"))
return nil
},
Questions: []*interact.Question{

View File

@ -7,7 +7,6 @@ import (
"math/rand"
"os"
"path/filepath"
"syscall"
"time"
)
@ -76,15 +75,6 @@ func random(n int) string {
return string(b)
}
// Flimit defines the max number of watched files
func (s *Settings) flimit() error {
var rLimit syscall.Rlimit
rLimit.Max = uint64(s.FileLimit)
rLimit.Cur = uint64(s.FileLimit)
return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
}
// Delete realize folder
func (s *Settings) del(d string) error {
_, err := os.Stat(d)

View File

@ -108,7 +108,7 @@ func TestSettings_Validate(t *testing.T) {
}
}
func TestSettings_Fatal(t *testing.T){
func TestSettings_Fatal(t *testing.T) {
s := Settings{}
s.fatal(nil,"test")
}
s.fatal(nil, "test")
}

14
settings_unix.go Normal file
View File

@ -0,0 +1,14 @@
// +build !windows
package main
import "syscall"
// Flimit defines the max number of watched files
func (s *Settings) flimit() error {
var rLimit syscall.Rlimit
rLimit.Max = uint64(s.FileLimit)
rLimit.Cur = uint64(s.FileLimit)
return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
}

View File

@ -1,189 +1,8 @@
// +build windows
package main
import (
yaml "gopkg.in/yaml.v2"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
"time"
)
// settings const
const (
Permission = 0775
Directory = ".realize"
File = "realize.yaml"
FileOut = "outputs.log"
FileErr = "errors.log"
FileLog = "logs.log"
)
// random string preference
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
// Settings defines a group of general settings and options
type Settings struct {
File string `yaml:"-" json:"-"`
Files `yaml:"files,omitempty" json:"files,omitempty"`
Server `yaml:"server,omitempty" json:"server,omitempty"`
FileLimit int64 `yaml:"flimit,omitempty" json:"flimit,omitempty"`
}
// Server settings, used for the web panel
type Server struct {
Status bool `yaml:"status" json:"status"`
Open bool `yaml:"open" json:"open"`
Host string `yaml:"host" json:"host"`
Port int `yaml:"port" json:"port"`
}
// Files defines the files generated by realize
type Files struct {
Outputs Resource `yaml:"outputs,omitempty" json:"outputs,omitempty"`
Logs Resource `yaml:"logs,omitempty" json:"log,omitempty"`
Errors Resource `yaml:"errors,omitempty" json:"error,omitempty"`
}
// Resource status and file name
type Resource struct {
Status bool
Name string
}
// Rand is used for generate a random string
func random(n int) string {
src := rand.NewSource(time.Now().UnixNano())
b := make([]byte, n)
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
// Wdir return the current working Directory
func (s Settings) wdir() string {
dir, err := os.Getwd()
s.validate(err)
return filepath.Base(dir)
}
// Flimit defines the max number of watched files
func (s *Settings) flimit() error {
return nil
}
// Delete realize folder
func (s *Settings) delete(d string) error {
_, err := os.Stat(d)
if !os.IsNotExist(err) {
return os.RemoveAll(d)
}
return err
}
// Path cleaner
func (s Settings) path(path string) string {
return strings.Replace(filepath.Clean(path), "\\", "/", -1)
}
// Validate checks a fatal error
func (s Settings) validate(err error) error {
if err != nil {
s.fatal(err, "")
}
return nil
}
// Read from config file
func (s *Settings) read(out interface{}) error {
localConfigPath := s.File
// backward compatibility
path := filepath.Join(Directory, s.File)
if _, err := os.Stat(path); err == nil {
localConfigPath = path
}
content, err := s.stream(localConfigPath)
if err == nil {
err = yaml.Unmarshal(content, out)
return err
}
return err
}
// Record create and unmarshal the yaml config file
func (s *Settings) record(out interface{}) error {
y, err := yaml.Marshal(out)
if err != nil {
return err
}
if _, err := os.Stat(Directory); os.IsNotExist(err) {
if err = os.Mkdir(Directory, Permission); err != nil {
return s.write(s.File, y)
}
}
return s.write(filepath.Join(Directory, s.File), y)
}
// Stream return a byte stream of a given file
func (s Settings) stream(file string) ([]byte, error) {
_, err := os.Stat(file)
if err != nil {
return nil, err
}
content, err := ioutil.ReadFile(file)
s.validate(err)
return content, err
}
// Fatal prints a fatal error with its additional messages
func (s Settings) fatal(err error, msg ...interface{}) {
if len(msg) > 0 && err != nil {
log.Fatalln(red.regular(msg...), err.Error())
} else if err != nil {
log.Fatalln(err.Error())
}
}
// Write a file
func (s Settings) write(name string, data []byte) error {
err := ioutil.WriteFile(name, data, Permission)
return s.validate(err)
}
// Name return the project name or the path of the working dir
func (s Settings) name(name string, path string) string {
if name == "" && path == "" {
return s.wdir()
} else if path != "/" {
return filepath.Base(path)
}
return name
}
// Create a new file and return its pointer
func (s Settings) create(path string, name string) *os.File {
var file string
if _, err := os.Stat(Directory); err == nil {
file = filepath.Join(path, Directory, name)
} else {
file = filepath.Join(path, name)
}
out, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_SYNC, Permission)
s.validate(err)
return out
}
}

View File

@ -3,9 +3,9 @@ package main
import (
"errors"
"gopkg.in/urfave/cli.v2"
"log"
"os"
"strings"
"log"
)
// Array check if a string is in given array
@ -43,9 +43,6 @@ func split(args, fields []string) []string {
// Duplicates check projects with same name or same combinations of main/path
func duplicates(value Project, arr []Project) (Project, error) {
for _, val := range arr {
if value.Path == val.Path {
return val, errors.New("There is already a project with path '" + val.Path + "'. Check your config file!")
}
if value.Name == val.Name {
return val, errors.New("There is already a project with name '" + val.Name + "'. Check your config file!")
}

View File

@ -51,23 +51,23 @@ type Buffer struct {
// Project defines the informations of a single project
type Project struct {
parent *realize
watcher FileWatcher
init bool
files, folders int64
name, lastFile string
tools []tool
paths []string
lastTime time.Time
Settings `yaml:"-" json:"-"`
Name string `yaml:"name" json:"name"`
Path string `yaml:"path" json:"path"`
Environment map[string]string `yaml:"environment,omitempty" json:"environment,omitempty"`
Cmds Cmds `yaml:"commands" json:"commands"`
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
Watcher Watch `yaml:"watcher" json:"watcher"`
Buffer Buffer `yaml:"-" json:"buffer"`
ErrorOutputPattern string `yaml:"errorOutputPattern,omitempty" json:"errorOutputPattern,omitempty"`
parent *realize
watcher FileWatcher
init bool
files, folders int64
name, lastFile string
tools []tool
paths []string
lastTime time.Time
Settings `yaml:"-" json:"-"`
Name string `yaml:"name" json:"name"`
Path string `yaml:"path" json:"path"`
Environment map[string]string `yaml:"environment,omitempty" json:"environment,omitempty"`
Cmds Cmds `yaml:"commands" json:"commands"`
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
Watcher Watch `yaml:"watcher" json:"watcher"`
Buffer Buffer `yaml:"-" json:"buffer"`
ErrorOutputPattern string `yaml:"errorOutputPattern,omitempty" json:"errorOutputPattern,omitempty"`
}
// Command options
@ -170,6 +170,84 @@ func (p *Project) err(err error) {
p.stamp("error", out, msg, "")
}
// Config project init
func (p *Project) config(r *realize) {
// validate project path, if invalid get wdir or clean current
if !filepath.IsAbs(p.Path) {
p.Path = wdir()
} else {
p.Path = filepath.Clean(p.Path)
}
// get basepath name
p.name = filepath.Base(p.Path)
// env variables
for key, item := range p.Environment {
if err := os.Setenv(key, item); err != nil {
p.Buffer.StdErr = append(p.Buffer.StdErr, BufferOut{Time: time.Now(), Text: err.Error(), Type: "Env error", Stream: ""})
}
}
// init commands
if len(p.Cmds.Fmt.Args) == 0 {
p.Cmds.Fmt.Args = []string{"-s", "-w", "-e", "./"}
}
p.tools = append(p.tools, tool{
status: p.Cmds.Fix.Status,
cmd: replace([]string{"go fix"}, p.Cmds.Fix.Method),
options: split([]string{}, p.Cmds.Fix.Args),
name: "Fix",
})
p.tools = append(p.tools, tool{
status: p.Cmds.Clean.Status,
cmd: replace([]string{"go clean"}, p.Cmds.Clean.Method),
options: split([]string{}, p.Cmds.Clean.Args),
name: "Clean",
})
p.tools = append(p.tools, tool{
status: p.Cmds.Fmt.Status,
cmd: replace([]string{"gofmt"}, p.Cmds.Fmt.Method),
options: split([]string{}, p.Cmds.Fmt.Args),
name: "Fmt",
})
p.tools = append(p.tools, tool{
status: p.Cmds.Generate.Status,
cmd: replace([]string{"go", "generate"}, p.Cmds.Generate.Method),
options: split([]string{}, p.Cmds.Generate.Args),
name: "Generate",
dir: true,
})
p.tools = append(p.tools, tool{
status: p.Cmds.Test.Status,
cmd: replace([]string{"go", "test"}, p.Cmds.Test.Method),
options: split([]string{}, p.Cmds.Test.Args),
name: "Test",
dir: true,
})
p.tools = append(p.tools, tool{
status: p.Cmds.Vet.Status,
cmd: replace([]string{"go", "vet"}, p.Cmds.Vet.Method),
options: split([]string{}, p.Cmds.Vet.Args),
name: "Vet",
dir: true,
})
p.Cmds.Install = Cmd{
Status: p.Cmds.Install.Status,
Args: append([]string{}, p.Cmds.Install.Args...),
method: replace([]string{"go", "install"}, p.Cmds.Install.Method),
name: "Install",
startTxt: "Installing...",
endTxt: "Installed",
}
p.Cmds.Build = Cmd{
Status: p.Cmds.Build.Status,
Args: append([]string{}, p.Cmds.Build.Args...),
method: replace([]string{"go", "build"}, p.Cmds.Build.Method),
name: "Build",
startTxt: "Building...",
endTxt: "Built",
}
p.parent = r
}
// Cmd calls the method that execute commands after/before and display the results
func (p *Project) cmd(stop <-chan bool, flag string, global bool) {
done := make(chan bool)
@ -312,7 +390,7 @@ func (p *Project) changed(event fsnotify.Event, stop chan bool) {
// Watch the files tree of a project
func (p *Project) walk(path string, info os.FileInfo, err error) error {
for _, v := range p.Watcher.Ignore {
s := append([]string{p.Path},strings.Split(v,string(os.PathSeparator))...)
s := append([]string{p.Path}, strings.Split(v, string(os.PathSeparator))...)
if strings.Contains(path, filepath.Join(s...)) {
return nil
}