commit
cf2f61085b
|
@ -26,6 +26,7 @@ _testmain.go
|
||||||
.glide
|
.glide
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.realize
|
||||||
|
|
||||||
server/assets
|
server/assets
|
||||||
docker
|
docker
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.7
|
- 1.8.x
|
||||||
- 1.8
|
- 1.9.x
|
||||||
- tip
|
- tip
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Contributing to Realize
|
||||||
|
|
||||||
|
:+1: First off, thanks for taking the time to contribute! :+1:
|
||||||
|
|
||||||
|
The following is a set of guidelines for contributing to this node package. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
|
||||||
|
|
||||||
|
#### Table Of Contents
|
||||||
|
|
||||||
|
[Code of Conduct](#code-of-conduct)
|
||||||
|
|
||||||
|
[How Can I Contribute?](#how-can-i-contribute)
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
This project and everyone participating in it is governed by following best pratices:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
By participating, you are expected to uphold this code. Please report unacceptable behavior.
|
||||||
|
|
||||||
|
## How Can I Contribute?
|
||||||
|
* Report Bugs
|
||||||
|
* Suggest Features
|
||||||
|
* Make Pull Requests
|
||||||
|
* Give a star :star: to the project
|
|
@ -1,4 +1,10 @@
|
||||||
memo = "4f11729a341d710751e002460e3767a15151cb5aee2e11ba6f70409107016f15"
|
memo = "26587affd3e0577e23a9c4afef3eaf279d9a013fe3d673c0dc1fd062f26d2d2e"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/Sirupsen/logrus"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e"
|
||||||
|
version = "v1.0.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/dgrijalva/jwt-go"
|
name = "github.com/dgrijalva/jwt-go"
|
||||||
|
@ -7,10 +13,10 @@ memo = "4f11729a341d710751e002460e3767a15151cb5aee2e11ba6f70409107016f15"
|
||||||
version = "v3.0.0"
|
version = "v3.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
name = "github.com/fatih/color"
|
name = "github.com/fatih/color"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "9131ab34cf20d2f6d83fdc67168a5430d1c7dc23"
|
revision = "1535ebc2637cc042c938f07fa26c6356ab8f8504"
|
||||||
version = "v1.4.1"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -22,19 +28,19 @@ memo = "4f11729a341d710751e002460e3767a15151cb5aee2e11ba6f70409107016f15"
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/labstack/echo"
|
name = "github.com/labstack/echo"
|
||||||
packages = [".","middleware"]
|
packages = [".","middleware"]
|
||||||
revision = "4256489c0e1ff50bdb71aedd1a21483fe2d67607"
|
revision = "a625e589cffa33eac768ab98bc21518cef786e2d"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/labstack/gommon"
|
name = "github.com/labstack/gommon"
|
||||||
packages = ["bytes","color","log","random"]
|
packages = ["bytes","color","log","random"]
|
||||||
revision = "9cedb429ffbe71a32a3ae7c65fd109cb7ae07804"
|
revision = "779b8a8b9850a97acba6a3fe20feb628c39e17c1"
|
||||||
version = "v0.2.0"
|
version = "0.2.2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/mattn/go-colorable"
|
name = "github.com/mattn/go-colorable"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "d228849504861217f796da67fae4f6e347643f15"
|
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||||
version = "v0.0.7"
|
version = "v0.0.9"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/mattn/go-isatty"
|
name = "github.com/mattn/go-isatty"
|
||||||
|
@ -42,11 +48,17 @@ memo = "4f11729a341d710751e002460e3767a15151cb5aee2e11ba6f70409107016f15"
|
||||||
revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe"
|
revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe"
|
||||||
version = "v0.0.2"
|
version = "v0.0.2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/moby/moby"
|
||||||
|
packages = ["pkg/filenotify"]
|
||||||
|
revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
|
||||||
|
version = "v1.13.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/tockins/interact"
|
name = "github.com/tockins/interact"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "c424f9a549ee8ca6b36332a8cb4374701fff6ecb"
|
revision = "620e305cb360fbbaaec15504caada4303f65a370"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -63,29 +75,29 @@ memo = "4f11729a341d710751e002460e3767a15151cb5aee2e11ba6f70409107016f15"
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
packages = ["acme","acme/autocert"]
|
packages = ["acme","acme/autocert","ssh/terminal"]
|
||||||
revision = "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e"
|
revision = "7d9177d70076375b9a59c8fde23d52d9c4a7ecd5"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/net"
|
name = "golang.org/x/net"
|
||||||
packages = ["websocket"]
|
packages = ["websocket"]
|
||||||
revision = "da118f7b8e5954f39d0d2130ab35d4bf0e3cb344"
|
revision = "0744d001aa8470aaa53df28d32e5ceeb8af9bd70"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = ["unix"]
|
packages = ["unix","windows"]
|
||||||
revision = "9f30dcbe5be197894515a338a9bda9253567ea8f"
|
revision = "429f518978ab01db8bb6f44b66785088e7fba58b"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "v2"
|
branch = "v2"
|
||||||
name = "gopkg.in/urfave/cli.v2"
|
name = "gopkg.in/urfave/cli.v2"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "61c3eb6ba1da1df17cd50f1456a9252d92bf5edd"
|
revision = "50a0fdeea6330854c7b05371cce1fac8c7e636d2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "v2"
|
branch = "v2"
|
||||||
name = "gopkg.in/yaml.v2"
|
name = "gopkg.in/yaml.v2"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b"
|
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
|
||||||
|
|
92
README.md
92
README.md
|
@ -52,15 +52,15 @@ Various operations can be programmed for each project, which can be executed at
|
||||||
- Support for most go commands (install, build, run, vet, test, fmt and much more)
|
- Support for most go commands (install, build, run, vet, test, fmt and much more)
|
||||||
- Web panel for a smart control of the workflow
|
- Web panel for a smart control of the workflow
|
||||||
|
|
||||||
v 1.5
|
Next features and informations
|
||||||
|
|
||||||
- [ ] Use cases
|
- [ ] More use cases
|
||||||
- [ ] Tests
|
- [ ] Complete tests
|
||||||
- [ ] Watch gopath dependencies
|
- [ ] Watch gopath dependencies
|
||||||
- [ ] Web panel, download logs
|
- [ ] Web panel, download logs
|
||||||
- [ ] Multiple configurations (dev, production)
|
- [ ] Multiple configurations (dev, production)
|
||||||
- [ ] Support to ignore paths and files in gititnore
|
- [ ] Support to ignore paths and files in gititnore
|
||||||
- [ ] Input redirection (wait for an input and redirect)
|
- [ ] Input redirection (wait for an input and redirect) useful for cli scripts
|
||||||
|
|
||||||
#### Installation
|
#### Installation
|
||||||
Run this to get/install:
|
Run this to get/install:
|
||||||
|
@ -72,50 +72,51 @@ $ go get github.com/tockins/realize
|
||||||
- ##### Run
|
- ##### Run
|
||||||
From project/projects root execute:
|
From project/projects root execute:
|
||||||
```
|
```
|
||||||
$ realize run
|
$ realize start
|
||||||
```
|
```
|
||||||
|
|
||||||
It will create a realize.yaml file if it doesn't exist already, add the working directory as project and run the pipeline.
|
It will create a realize.yaml file if it doesn't exist already, add the working directory as project and run your workflow.
|
||||||
|
|
||||||
The Run command supports the following custom parameters:
|
"Start" command supports the following custom parameters:
|
||||||
|
|
||||||
```
|
```
|
||||||
--name="name" -> Run by name on existing configuration
|
--name="name" -> Run by name on existing configuration
|
||||||
--path="realize/server" -> Custom Path, if not specified takes the working directory name
|
--path="realize/server" -> Custom Path, if not specified takes the working directory name
|
||||||
--build -> Enable go build
|
|
||||||
--no-run -> Disable go run
|
|
||||||
--no-install -> Disable go install
|
|
||||||
--no-config -> Ignore an existing config / skip the creation of a new one
|
|
||||||
--server -> Enable the web server
|
|
||||||
--legacy -> Enable legacy watch instead of Fsnotify watch
|
|
||||||
--generate -> Enable go generate
|
--generate -> Enable go generate
|
||||||
|
--fmt -> Enable go fmt
|
||||||
--test -> Enable go test
|
--test -> Enable go test
|
||||||
--open -> Open in default browser
|
--vet -> Enable go vet
|
||||||
|
--install -> Enable go install
|
||||||
|
--build -> Enable go build
|
||||||
|
--run -> Enable go run
|
||||||
|
--server -> Enable the web server
|
||||||
|
--no-config -> Ignore an existing config / skip the creation of a new one
|
||||||
```
|
```
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ realize run
|
$ realize start
|
||||||
$ realize run --path="mypath"
|
$ realize start --path="mypath"
|
||||||
$ realize run --name="My Project" --build
|
$ realize start --name="realize" --build
|
||||||
$ realize run --path="realize" --no-run --no-config
|
$ realize start --path="realize" --run --no-config
|
||||||
$ realize run --path="/Users/alessio/go/src/github.com/tockins/realize-examples/coin/"
|
$ realize start --install --test --fmt --no-config
|
||||||
|
$ realize start --path="/Users/alessio/go/src/github.com/tockins/realize-examples/coin/"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want, you can specify additional arguments for your project.
|
If you want, you can specify additional arguments for your project.
|
||||||
|
|
||||||
**The additional arguments must go after the params**
|
**The additional arguments must go after the params**
|
||||||
|
|
||||||
**Run can run a project from its working directory without make a config file (--no-config).**
|
**Start command can be used with a project from its working directory without make a config file (--no-config).**
|
||||||
|
|
||||||
```
|
```
|
||||||
$ realize run --path="/print/printer" --no-run yourParams --yourFlags // right
|
$ realize start --path="/print/printer" --run yourParams --yourFlags // right
|
||||||
$ realize run yourParams --yourFlags --path="/print/printer" --no-run // wrong
|
$ realize start yourParams --yourFlags --path="/print/printer" --run // wrong
|
||||||
```
|
```
|
||||||
- ##### Add
|
- ##### Add
|
||||||
Add a project to an existing config file or create a new one without run the pipeline.
|
Add a project to an existing config file or create a new one.
|
||||||
|
|
||||||
"Add" supports the same parameters of the "Run" command.
|
"Add" supports the same parameters of "Start" command.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ realize add
|
$ realize add
|
||||||
|
@ -124,7 +125,7 @@ $ go get github.com/tockins/realize
|
||||||
- ##### Init
|
- ##### Init
|
||||||
Like add, but with this command you can create a configuration step by step and customize each option.
|
Like add, but with this command you can create a configuration step by step and customize each option.
|
||||||
|
|
||||||
**Init is the only command that supports a complete customization of all the options supported**
|
**Init is the only command that supports a complete customization of all supported options**
|
||||||
|
|
||||||
```
|
```
|
||||||
$ realize init
|
$ realize init
|
||||||
|
@ -136,12 +137,6 @@ $ go get github.com/tockins/realize
|
||||||
$ realize remove --name="myname"
|
$ realize remove --name="myname"
|
||||||
```
|
```
|
||||||
|
|
||||||
- ##### List
|
|
||||||
Projects list in cli
|
|
||||||
```
|
|
||||||
$ realize list
|
|
||||||
```
|
|
||||||
|
|
||||||
- #### Color reference
|
- #### Color reference
|
||||||
- Blue: outputs of the project
|
- Blue: outputs of the project
|
||||||
- Red: errors
|
- Red: errors
|
||||||
|
@ -156,8 +151,8 @@ $ go get github.com/tockins/realize
|
||||||
```
|
```
|
||||||
settings:
|
settings:
|
||||||
legacy:
|
legacy:
|
||||||
status: true // enable polling watcher instead fsnotifiy
|
force: true // force polling watcher instead fsnotifiy
|
||||||
interval: 10s // polling interval
|
interval: 100ms // polling interval
|
||||||
resources: // files names
|
resources: // files names
|
||||||
outputs: outputs.log
|
outputs: outputs.log
|
||||||
logs: logs.log
|
logs: logs.log
|
||||||
|
@ -167,21 +162,30 @@ $ go get github.com/tockins/realize
|
||||||
open: false // open browser at start
|
open: false // open browser at start
|
||||||
host: localhost // server host
|
host: localhost // server host
|
||||||
port: 5001 // server port
|
port: 5001 // server port
|
||||||
projects:
|
schema:
|
||||||
- name: coin
|
- name: coin
|
||||||
path: coin // project path
|
path: coin // project path
|
||||||
environment: // env variables available at startup
|
environment: // env variables available at startup
|
||||||
test: test
|
test: test
|
||||||
myvar: value
|
myvar: value
|
||||||
commands: // go commands supported
|
commands: // go commands supported
|
||||||
vet: true
|
vet:
|
||||||
fmt: true
|
status: true
|
||||||
test: false
|
fmt:
|
||||||
generate: false
|
status: true
|
||||||
bin:
|
args:
|
||||||
|
- -s
|
||||||
|
- -w
|
||||||
|
test:
|
||||||
|
status: true
|
||||||
|
method: gb test // support differents build tool
|
||||||
|
generate:
|
||||||
|
status: true
|
||||||
|
install:
|
||||||
status: true
|
status: true
|
||||||
build:
|
build:
|
||||||
status: false
|
status: false
|
||||||
|
method: gb build // support differents build tool
|
||||||
args: // additional params for the command
|
args: // additional params for the command
|
||||||
- -race
|
- -race
|
||||||
run: true
|
run: true
|
||||||
|
@ -193,8 +197,9 @@ $ go get github.com/tockins/realize
|
||||||
- /
|
- /
|
||||||
ignore_paths: // ignored paths
|
ignore_paths: // ignored paths
|
||||||
- vendor
|
- vendor
|
||||||
exts: // watched extensions
|
extensions: // watched extensions
|
||||||
- .go
|
- go
|
||||||
|
- html
|
||||||
scripts: // custom scripts
|
scripts: // custom scripts
|
||||||
- type: before // type (after/before)
|
- type: before // type (after/before)
|
||||||
command: ./ls -l // command
|
command: ./ls -l // command
|
||||||
|
@ -203,10 +208,7 @@ $ go get github.com/tockins/realize
|
||||||
- type: after
|
- type: after
|
||||||
command: ./ls
|
command: ./ls
|
||||||
changed: true
|
changed: true
|
||||||
streams: // save logs/errors/outputs on files
|
errorOutputPattern: mypattern //custom error pattern
|
||||||
file_out: false
|
|
||||||
file_log: false
|
|
||||||
file_err: false
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Support us and suggest an improvement
|
#### Support us and suggest an improvement
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,247 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"gopkg.in/urfave/cli.v2"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tool options customizable, should be moved in Cmd
|
||||||
|
type tool struct {
|
||||||
|
dir bool
|
||||||
|
status bool
|
||||||
|
name string
|
||||||
|
err string
|
||||||
|
cmd []string
|
||||||
|
options []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmds
|
||||||
|
type Cmds struct {
|
||||||
|
Vet Cmd `yaml:"vet,omitempty" json:"vet,omitempty"`
|
||||||
|
Fmt Cmd `yaml:"fmt,omitempty" json:"fmt,omitempty"`
|
||||||
|
Test Cmd `yaml:"test,omitempty" json:"test,omitempty"`
|
||||||
|
Generate Cmd `yaml:"generate,omitempty" json:"generate,omitempty"`
|
||||||
|
Install Cmd `yaml:"install,omitempty" json:"install,omitempty"`
|
||||||
|
Build Cmd `yaml:"build,omitempty" json:"build,omitempty"`
|
||||||
|
Run bool `yaml:"run,omitempty" json:"run,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmd
|
||||||
|
type Cmd struct {
|
||||||
|
Status bool `yaml:"status,omitempty" json:"status,omitempty"`
|
||||||
|
Method string `yaml:"method,omitempty" json:"method,omitempty"`
|
||||||
|
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
|
||||||
|
method []string
|
||||||
|
tool bool
|
||||||
|
name, startTxt, endTxt string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether there is a project
|
||||||
|
func (r *realize) check() error {
|
||||||
|
if len(r.Schema) > 0 {
|
||||||
|
r.clean()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("there are no projects")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new project
|
||||||
|
func (r *realize) add(p *cli.Context) error {
|
||||||
|
project := Project{
|
||||||
|
Name: r.Settings.name(p.String("name"), p.String("path")),
|
||||||
|
Path: r.Settings.path(p.String("path")),
|
||||||
|
Cmds: Cmds{
|
||||||
|
Vet: Cmd{
|
||||||
|
Status: p.Bool("vet"),
|
||||||
|
},
|
||||||
|
Fmt: Cmd{
|
||||||
|
Status: p.Bool("fmt"),
|
||||||
|
},
|
||||||
|
Test: Cmd{
|
||||||
|
Status: p.Bool("test"),
|
||||||
|
},
|
||||||
|
Generate: Cmd{
|
||||||
|
Status: p.Bool("generate"),
|
||||||
|
},
|
||||||
|
Build: Cmd{
|
||||||
|
Status: p.Bool("build"),
|
||||||
|
},
|
||||||
|
Install: Cmd{
|
||||||
|
Status: p.Bool("install"),
|
||||||
|
},
|
||||||
|
Run: p.Bool("run"),
|
||||||
|
},
|
||||||
|
Args: params(p),
|
||||||
|
Watcher: Watch{
|
||||||
|
Paths: []string{"/"},
|
||||||
|
Ignore: []string{"vendor"},
|
||||||
|
Exts: []string{"go"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if _, err := duplicates(project, r.Schema); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Schema = append(r.Schema, project)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run launches the toolchain for each project
|
||||||
|
func (r *realize) run(p *cli.Context) error {
|
||||||
|
err := r.check()
|
||||||
|
if err == nil {
|
||||||
|
// loop projects
|
||||||
|
if p.String("name") != "" {
|
||||||
|
wg.Add(1)
|
||||||
|
} else {
|
||||||
|
wg.Add(len(r.Schema))
|
||||||
|
}
|
||||||
|
for k, elm := range r.Schema {
|
||||||
|
// command start using name flag
|
||||||
|
if p.String("name") != "" && r.Schema[k].Name != p.String("name") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//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.Field(i)
|
||||||
|
// if f.IsValid() {
|
||||||
|
// if f.CanSet() {
|
||||||
|
// 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
|
||||||
|
r.Schema[k].path = r.Schema[k].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: ""})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// base path of the project
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if elm.path == "." || elm.path == "/" {
|
||||||
|
r.Schema[k].base = wd
|
||||||
|
r.Schema[k].path = elm.wdir()
|
||||||
|
} else if filepath.IsAbs(elm.path) {
|
||||||
|
r.Schema[k].base = elm.path
|
||||||
|
} else {
|
||||||
|
r.Schema[k].base = filepath.Join(wd, elm.path)
|
||||||
|
}
|
||||||
|
go r.Schema[k].watch()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a project
|
||||||
|
func (r *realize) remove(p *cli.Context) error {
|
||||||
|
for key, val := range r.Schema {
|
||||||
|
if p.String("name") == val.Name {
|
||||||
|
r.Schema = append(r.Schema[:key], r.Schema[key+1:]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("no project found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert current project if there isn't already one
|
||||||
|
func (r *realize) insert(c *cli.Context) error {
|
||||||
|
if c.Bool("no-config") {
|
||||||
|
r.Schema = []Project{}
|
||||||
|
}
|
||||||
|
if len(r.Schema) <= 0 {
|
||||||
|
if err := r.add(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"gopkg.in/urfave/cli.v2"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type loggerT struct{}
|
||||||
|
|
||||||
|
func (loggerT) Write(bytes []byte) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetOutput(loggerT{})
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_Clean(t *testing.T) {
|
||||||
|
r := realize{}
|
||||||
|
r.Schema = append(r.Schema, Project{Name: "test0"})
|
||||||
|
r.Schema = append(r.Schema, Project{Name: "test0"})
|
||||||
|
r.clean()
|
||||||
|
if len(r.Schema) > 1 {
|
||||||
|
t.Error("Expected only one project")
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_Check(t *testing.T) {
|
||||||
|
r := realize{}
|
||||||
|
err := r.check()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("There is no project, error expected")
|
||||||
|
}
|
||||||
|
r.Schema = append(r.Schema, Project{Name: "test0"})
|
||||||
|
err = r.check()
|
||||||
|
if err != nil {
|
||||||
|
t.Error("There is a project, error unexpected", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_Add(t *testing.T) {
|
||||||
|
r := realize{}
|
||||||
|
// add all flags, test with expected
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("fmt", false, "")
|
||||||
|
set.Bool("vet", false, "")
|
||||||
|
set.Bool("test", false, "")
|
||||||
|
set.Bool("install", false, "")
|
||||||
|
set.Bool("run", false, "")
|
||||||
|
set.Bool("build", false, "")
|
||||||
|
set.Bool("generate", false, "")
|
||||||
|
set.String("path", "", "")
|
||||||
|
c := cli.NewContext(nil, set, nil)
|
||||||
|
set.Parse([]string{"--path=test_path", "--fmt", "--install", "--run", "--build", "--generate", "--test", "--vet"})
|
||||||
|
r.add(c)
|
||||||
|
expected := Project{
|
||||||
|
Name: "test_path",
|
||||||
|
Path: "test_path",
|
||||||
|
Cmds: Cmds{
|
||||||
|
Fmt: Cmd{
|
||||||
|
Status: true,
|
||||||
|
},
|
||||||
|
Install: Cmd{
|
||||||
|
Status: true,
|
||||||
|
},
|
||||||
|
Generate: Cmd{
|
||||||
|
Status: true,
|
||||||
|
},
|
||||||
|
Test: Cmd{
|
||||||
|
Status: true,
|
||||||
|
},
|
||||||
|
Build: Cmd{
|
||||||
|
Status: true,
|
||||||
|
},
|
||||||
|
Vet: Cmd{
|
||||||
|
Status: true,
|
||||||
|
},
|
||||||
|
Run: true,
|
||||||
|
},
|
||||||
|
Watcher: Watch{
|
||||||
|
Paths: []string{"/"},
|
||||||
|
Ignore: []string{"vendor"},
|
||||||
|
Exts: []string{"go"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(r.Schema[0], expected) {
|
||||||
|
t.Error("Expected equal struct")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_Run(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
params := cli.NewContext(nil, set, nil)
|
||||||
|
m := make(map[string]string)
|
||||||
|
m["test"] = "test"
|
||||||
|
r := realize{}
|
||||||
|
r.Schema = []Project{
|
||||||
|
{
|
||||||
|
Name: "test0",
|
||||||
|
Path: ".",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test1",
|
||||||
|
Path: "/test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test2",
|
||||||
|
Path: "/test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
go r.run(params)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_Remove(t *testing.T) {
|
||||||
|
r := realize{}
|
||||||
|
set := flag.NewFlagSet("name", 0)
|
||||||
|
set.String("name", "", "")
|
||||||
|
c := cli.NewContext(nil, set, nil)
|
||||||
|
set.Parse([]string{"--name=test0"})
|
||||||
|
err := r.remove(c)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected an error, there are no projects")
|
||||||
|
}
|
||||||
|
// Append a new project
|
||||||
|
r.Schema = append(r.Schema, Project{Name: "test0"})
|
||||||
|
err = r.remove(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error unexpected, the project should be remove", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_Insert(t *testing.T) {
|
||||||
|
r := realize{}
|
||||||
|
// add all flags, test with expected
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("no-config", false, "")
|
||||||
|
c := cli.NewContext(nil, set, nil)
|
||||||
|
set.Parse([]string{"--no-config"})
|
||||||
|
|
||||||
|
r.insert(c)
|
||||||
|
if len(r.Schema) != 1 {
|
||||||
|
t.Error("Expected one project instead", len(r.Schema))
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Schema = []Project{}
|
||||||
|
r.Schema = append(r.Schema, Project{})
|
||||||
|
r.Schema = append(r.Schema, Project{})
|
||||||
|
c = cli.NewContext(nil, set, nil)
|
||||||
|
r.insert(c)
|
||||||
|
if len(r.Schema) != 1 {
|
||||||
|
t.Error("Expected one project instead", len(r.Schema))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoCompile is used for compile a project
|
||||||
|
func (p *Project) goCompile(stop <-chan bool, method []string, args []string) (string, error) {
|
||||||
|
var out bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
done := make(chan error)
|
||||||
|
err := os.Setenv("GOBIN", filepath.Join(getEnvPath("GOPATH"), "bin"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
args = append(method, args...)
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
if _, err := os.Stat(filepath.Join(p.base, p.path)); err == nil {
|
||||||
|
p.path = filepath.Join(p.base, p.path)
|
||||||
|
}
|
||||||
|
cmd.Dir = p.path
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
// Start command
|
||||||
|
cmd.Start()
|
||||||
|
go func() { done <- cmd.Wait() }()
|
||||||
|
// Wait a result
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
// Stop running command
|
||||||
|
cmd.Process.Kill()
|
||||||
|
return msgStop, nil
|
||||||
|
case err := <-done:
|
||||||
|
// Command completed
|
||||||
|
if err != nil {
|
||||||
|
return stderr.String(), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoRun is an implementation of the bin execution
|
||||||
|
func (p *Project) goRun(stop <-chan bool, runner chan bool) {
|
||||||
|
var build *exec.Cmd
|
||||||
|
var args []string
|
||||||
|
isErrorText := func(string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
errRegexp, err := regexp.Compile(p.ErrorOutputPattern)
|
||||||
|
if err != nil {
|
||||||
|
msg := fmt.Sprintln(p.pname(p.Name, 3), ":", blue.regular(err.Error()))
|
||||||
|
out := BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Run"}
|
||||||
|
p.stamp("error", out, msg, "")
|
||||||
|
} else {
|
||||||
|
isErrorText = func(t string) bool {
|
||||||
|
return errRegexp.MatchString(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range p.Args {
|
||||||
|
a := strings.FieldsFunc(arg, func(i rune) bool {
|
||||||
|
return i == '"' || i == '=' || i == '\''
|
||||||
|
})
|
||||||
|
args = append(args, a...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(filepath.Join(getEnvPath("GOBIN"), filepath.Base(p.base))); err == nil {
|
||||||
|
build = exec.Command(filepath.Join(getEnvPath("GOBIN"), filepath.Base(p.base)), args...)
|
||||||
|
} else if _, err := os.Stat(filepath.Join(getEnvPath("GOBIN"), filepath.Base(p.base)) + extWindows); err == nil {
|
||||||
|
build = exec.Command(filepath.Join(getEnvPath("GOBIN"), filepath.Base(p.base))+extWindows, args...)
|
||||||
|
} else {
|
||||||
|
path := filepath.Join(p.base, filepath.Base(p.base))
|
||||||
|
if _, err = os.Stat(path); err == nil {
|
||||||
|
build = exec.Command(path, args...)
|
||||||
|
} else if _, err = os.Stat(path + extWindows); err == nil {
|
||||||
|
build = exec.Command(path+extWindows, args...)
|
||||||
|
} else {
|
||||||
|
p.Buffer.StdLog = append(p.Buffer.StdLog, BufferOut{Time: time.Now(), Text: "Can't run a not compiled project"})
|
||||||
|
p.fatal(nil, "Can't run a not compiled project", ":")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := build.Process.Kill(); err != nil {
|
||||||
|
p.Buffer.StdLog = append(p.Buffer.StdLog, BufferOut{Time: time.Now(), Text: "Failed to stop: " + err.Error()})
|
||||||
|
p.fatal(err, "Failed to stop", ":")
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintln(p.pname(p.Name, 2), ":", red.regular("Ended"))
|
||||||
|
out := BufferOut{Time: time.Now(), Text: "Ended", Type: "Go Run"}
|
||||||
|
p.stamp("log", out, msg, "")
|
||||||
|
}()
|
||||||
|
|
||||||
|
stdout, err := build.StdoutPipe()
|
||||||
|
stderr, err := build.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(red.bold(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := build.Start(); err != nil {
|
||||||
|
log.Println(red.bold(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
close(runner)
|
||||||
|
|
||||||
|
execOutput, execError := bufio.NewScanner(stdout), bufio.NewScanner(stderr)
|
||||||
|
stopOutput, stopError := make(chan bool, 1), make(chan bool, 1)
|
||||||
|
scanner := func(stop chan bool, output *bufio.Scanner, isError bool) {
|
||||||
|
for output.Scan() {
|
||||||
|
text := output.Text()
|
||||||
|
msg := fmt.Sprintln(p.pname(p.Name, 3), ":", blue.regular(text))
|
||||||
|
if isError && !isErrorText(text) {
|
||||||
|
out := BufferOut{Time: time.Now(), Text: text, Type: "Go Run"}
|
||||||
|
p.stamp("error", out, msg, "")
|
||||||
|
} else {
|
||||||
|
out := BufferOut{Time: time.Now(), Text: text, Type: "Go Run"}
|
||||||
|
p.stamp("out", out, msg, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(stop)
|
||||||
|
}
|
||||||
|
go scanner(stopOutput, execOutput, false)
|
||||||
|
go scanner(stopError, execError, true)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
case <-stopOutput:
|
||||||
|
return
|
||||||
|
case <-stopError:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec an additional command from a defined path if specified
|
||||||
|
func (p *Project) command(stop <-chan bool, cmd Command) (string, string) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
done := make(chan error)
|
||||||
|
args := strings.Split(strings.Replace(strings.Replace(cmd.Command, "'", "", -1), "\"", "", -1), " ")
|
||||||
|
exec := exec.Command(args[0], args[1:]...)
|
||||||
|
exec.Dir = p.base
|
||||||
|
if cmd.Path != "" {
|
||||||
|
if strings.Contains(cmd.Path, p.base) {
|
||||||
|
exec.Dir = cmd.Path
|
||||||
|
} else {
|
||||||
|
exec.Dir = filepath.Join(p.base, cmd.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exec.Stdout = &stdout
|
||||||
|
exec.Stderr = &stderr
|
||||||
|
// Start command
|
||||||
|
exec.Start()
|
||||||
|
go func() { done <- exec.Wait() }()
|
||||||
|
// Wait a result
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
// Stop running command
|
||||||
|
exec.Process.Kill()
|
||||||
|
return "", ""
|
||||||
|
case err := <-done:
|
||||||
|
// Command completed
|
||||||
|
if err != nil {
|
||||||
|
return stderr.String(), stdout.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", stdout.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoTool is used for run go tools methods such as fmt, test, generate and so on
|
||||||
|
func (p *Project) goTool(wg *sync.WaitGroup, stop <-chan bool, result chan<- tool, path string, tool tool) {
|
||||||
|
defer wg.Done()
|
||||||
|
if tool.status {
|
||||||
|
if tool.dir && filepath.Ext(path) != "" {
|
||||||
|
path = filepath.Dir(path)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "") {
|
||||||
|
if strings.HasSuffix(path, ".go") {
|
||||||
|
tool.options = append(tool.options, path)
|
||||||
|
path = p.base
|
||||||
|
}
|
||||||
|
if s := ext(path); s == "" || s == "go" {
|
||||||
|
var out, stderr bytes.Buffer
|
||||||
|
done := make(chan error)
|
||||||
|
tool.cmd = append(tool.cmd, tool.options...)
|
||||||
|
cmd := exec.Command(tool.cmd[0], tool.cmd[1:]...)
|
||||||
|
cmd.Dir = path
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
// Start command
|
||||||
|
cmd.Start()
|
||||||
|
go func() { done <- cmd.Wait() }()
|
||||||
|
// Wait a result
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
// Stop running command
|
||||||
|
cmd.Process.Kill()
|
||||||
|
break
|
||||||
|
case err := <-done:
|
||||||
|
// Command completed
|
||||||
|
if err != nil {
|
||||||
|
tool.err = stderr.String() + out.String()
|
||||||
|
// send command result
|
||||||
|
result <- tool
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProject_GoCompile(t *testing.T) {
|
||||||
|
p := Project{}
|
||||||
|
stop := make(chan bool)
|
||||||
|
response := make(chan string)
|
||||||
|
result, err := p.goCompile(stop, []string{"echo"}, []string{"test"})
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Unexpected", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
result, err = p.goCompile(stop, []string{"sleep"}, []string{"20s"})
|
||||||
|
response <- result
|
||||||
|
}()
|
||||||
|
close(stop)
|
||||||
|
select {
|
||||||
|
case v := <-response:
|
||||||
|
if v != msgStop {
|
||||||
|
t.Error("Unexpected result", response)
|
||||||
|
}
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Error("Channel doesn't works")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,281 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// this code is imported from moby, unfortunately i can't import it directly as dependencies from its repo,
|
||||||
|
// cause there was a problem between moby vendor and fsnotify
|
||||||
|
// i have just added only walk methods and some little changes to polling interval, originally set as static.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// errPollerClosed is returned when the poller is closed
|
||||||
|
errPollerClosed = errors.New("poller is closed")
|
||||||
|
// errNoSuchWatch is returned when trying to remove a watch that doesn't exist
|
||||||
|
errNoSuchWatch = errors.New("watch does not exist")
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// FileWatcher is an interface for implementing file notification watchers
|
||||||
|
FileWatcher interface {
|
||||||
|
Close() error
|
||||||
|
Add(string) error
|
||||||
|
Walk(string, bool) string
|
||||||
|
Remove(string) error
|
||||||
|
Errors() <-chan error
|
||||||
|
Events() <-chan fsnotify.Event
|
||||||
|
}
|
||||||
|
// fsNotifyWatcher wraps the fsnotify package to satisfy the FileNotifier interface
|
||||||
|
fsNotifyWatcher struct {
|
||||||
|
*fsnotify.Watcher
|
||||||
|
}
|
||||||
|
// filePoller is used to poll files for changes, especially in cases where fsnotify
|
||||||
|
// can't be run (e.g. when inotify handles are exhausted)
|
||||||
|
// filePoller satisfies the FileWatcher interface
|
||||||
|
filePoller struct {
|
||||||
|
// watches is the list of files currently being polled, close the associated channel to stop the watch
|
||||||
|
watches map[string]chan struct{}
|
||||||
|
// events is the channel to listen to for watch events
|
||||||
|
events chan fsnotify.Event
|
||||||
|
// errors is the channel to listen to for watch errors
|
||||||
|
errors chan error
|
||||||
|
// mu locks the poller for modification
|
||||||
|
mu sync.Mutex
|
||||||
|
// closed is used to specify when the poller has already closed
|
||||||
|
closed bool
|
||||||
|
// polling interval
|
||||||
|
interval time.Duration
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPollingWatcher returns a poll-based file watcher
|
||||||
|
func PollingWatcher(interval time.Duration) FileWatcher {
|
||||||
|
if interval == 0 {
|
||||||
|
interval = 100 * time.Millisecond
|
||||||
|
}
|
||||||
|
return &filePoller{
|
||||||
|
interval: interval,
|
||||||
|
events: make(chan fsnotify.Event),
|
||||||
|
errors: make(chan error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New tries to use an fs-event watcher, and falls back to the poller if there is an error
|
||||||
|
func Watcher(force bool, interval time.Duration) (FileWatcher, error) {
|
||||||
|
if !force {
|
||||||
|
if w, err := EventWatcher(); err == nil {
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PollingWatcher(interval), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventWatcher returns an fs-event based file watcher
|
||||||
|
func EventWatcher() (FileWatcher, error) {
|
||||||
|
w, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &fsNotifyWatcher{Watcher: w}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors returns the fsnotify error channel receiver
|
||||||
|
func (w *fsNotifyWatcher) Errors() <-chan error {
|
||||||
|
return w.Watcher.Errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events returns the fsnotify event channel receiver
|
||||||
|
func (w *fsNotifyWatcher) Events() <-chan fsnotify.Event {
|
||||||
|
return w.Watcher.Events
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fsNotifyWatcher) Walk(path string, init bool) string {
|
||||||
|
if err := w.Add(path); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the poller
|
||||||
|
// All watches are stopped, removed, and the poller cannot be added to
|
||||||
|
func (w *filePoller) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.closed = true
|
||||||
|
for name := range w.watches {
|
||||||
|
w.remove(name)
|
||||||
|
delete(w.watches, name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors returns the errors channel
|
||||||
|
// This is used for notifications about errors on watched files
|
||||||
|
func (w *filePoller) Errors() <-chan error {
|
||||||
|
return w.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a filename to the list of watches
|
||||||
|
// once added the file is polled for changes in a separate goroutine
|
||||||
|
func (w *filePoller) Add(name string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return errPollerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fi, err := os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.watches == nil {
|
||||||
|
w.watches = make(map[string]chan struct{})
|
||||||
|
}
|
||||||
|
if _, exists := w.watches[name]; exists {
|
||||||
|
return fmt.Errorf("watch exists")
|
||||||
|
}
|
||||||
|
chClose := make(chan struct{})
|
||||||
|
w.watches[name] = chClose
|
||||||
|
go w.watch(f, fi, chClose)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *filePoller) remove(name string) error {
|
||||||
|
if w.closed {
|
||||||
|
return errPollerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
chClose, exists := w.watches[name]
|
||||||
|
if !exists {
|
||||||
|
return errNoSuchWatch
|
||||||
|
}
|
||||||
|
close(chClose)
|
||||||
|
delete(w.watches, name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops and removes watch with the specified name
|
||||||
|
func (w *filePoller) Remove(name string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
return w.remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events returns the event channel
|
||||||
|
// This is used for notifications on events about watched files
|
||||||
|
func (w *filePoller) Events() <-chan fsnotify.Event {
|
||||||
|
return w.events
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *filePoller) Walk(path string, init bool) string {
|
||||||
|
check := w.watches[path]
|
||||||
|
if err := w.Add(path); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if check == nil && init {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
go w.sendEvent(fsnotify.Event{Op: fsnotify.Create, Name: path}, w.watches[path])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendErr publishes the specified error to the errors channel
|
||||||
|
func (w *filePoller) sendErr(e error, chClose <-chan struct{}) error {
|
||||||
|
select {
|
||||||
|
case w.errors <- e:
|
||||||
|
case <-chClose:
|
||||||
|
return fmt.Errorf("closed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendEvent publishes the specified event to the events channel
|
||||||
|
func (w *filePoller) sendEvent(e fsnotify.Event, chClose <-chan struct{}) error {
|
||||||
|
select {
|
||||||
|
case w.events <- e:
|
||||||
|
case <-chClose:
|
||||||
|
return fmt.Errorf("closed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// watch is responsible for polling the specified file for changes
|
||||||
|
// upon finding changes to a file or errors, sendEvent/sendErr is called
|
||||||
|
func (w *filePoller) watch(f *os.File, lastFi os.FileInfo, chClose chan struct{}) {
|
||||||
|
defer f.Close()
|
||||||
|
for {
|
||||||
|
time.Sleep(w.interval)
|
||||||
|
select {
|
||||||
|
case <-chClose:
|
||||||
|
logrus.Debugf("watch for %s closed", f.Name())
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
// if we got an error here and lastFi is not set, we can presume that nothing has changed
|
||||||
|
// This should be safe since before `watch()` is called, a stat is performed, there is any error `watch` is not called
|
||||||
|
if lastFi == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If it doesn't exist at this point, it must have been removed
|
||||||
|
// no need to send the error here since this is a valid operation
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Remove, Name: f.Name()}, chClose); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastFi = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// at this point, send the error
|
||||||
|
if err := w.sendErr(err, chClose); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastFi == nil {
|
||||||
|
if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Create, Name: f.Name()}, chClose); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastFi = fi
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode() != lastFi.Mode() {
|
||||||
|
if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Chmod, Name: f.Name()}, chClose); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastFi = fi
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.ModTime() != lastFi.ModTime() || fi.Size() != lastFi.Size() {
|
||||||
|
if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Write, Name: f.Name()}, chClose); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastFi = fi
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
interval = 100 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPoller_AddRemove(t *testing.T) {
|
||||||
|
w := PollingWatcher(interval)
|
||||||
|
|
||||||
|
if err := w.Add("no-such-file"); err == nil {
|
||||||
|
t.Fatal("should have gotten error when adding a non-existent file")
|
||||||
|
}
|
||||||
|
if err := w.Remove("no-such-file"); err == nil {
|
||||||
|
t.Fatal("should have gotten error when removing non-existent watch")
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "asdf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(f.Name())
|
||||||
|
|
||||||
|
if err := w.Add(f.Name()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.Remove(f.Name()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoller_Event(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("No chmod on Windows")
|
||||||
|
}
|
||||||
|
w := PollingWatcher(interval)
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "test-poller")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating temp file")
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(f.Name())
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
if err := w.Add(f.Name()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-w.Events():
|
||||||
|
t.Fatal("got event before anything happened")
|
||||||
|
case <-w.Errors():
|
||||||
|
t.Fatal("got error before anything happened")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(f.Name(), []byte("hello"), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := assertEvent(w, fsnotify.Write); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Chmod(f.Name(), 600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := assertEvent(w, fsnotify.Chmod); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(f.Name()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := assertEvent(w, fsnotify.Remove); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoller_Close(t *testing.T) {
|
||||||
|
w := PollingWatcher(interval)
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// test double-close
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "asdf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(f.Name())
|
||||||
|
if err := w.Add(f.Name()); err == nil {
|
||||||
|
t.Fatal("should have gotten error adding watch for closed watcher")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEvent(w FileWatcher, eType fsnotify.Op) error {
|
||||||
|
var err error
|
||||||
|
select {
|
||||||
|
case e := <-w.Events():
|
||||||
|
if e.Op != eType {
|
||||||
|
err = fmt.Errorf("got wrong event type, expected %q: %v", eType, e.Op)
|
||||||
|
}
|
||||||
|
case e := <-w.Errors():
|
||||||
|
err = fmt.Errorf("got unexpected error waiting for events %v: %v", eType, e)
|
||||||
|
case <-time.After(time.Duration(1) * time.Second):
|
||||||
|
err = fmt.Errorf("timeout waiting for event %v", eType)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
875
realize.go
875
realize.go
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,35 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gopkg.in/urfave/cli.v2"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrefix(t *testing.T) {
|
||||||
|
input := random(10)
|
||||||
|
value := fmt.Sprint(yellow.bold("[")+"REALIZE"+yellow.bold("]"), input)
|
||||||
|
result := prefix(input)
|
||||||
|
if result == "" {
|
||||||
|
t.Fatal("Expected a string")
|
||||||
|
}
|
||||||
|
if result != value {
|
||||||
|
t.Fatal("Expected", value, "Instead", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBefore(t *testing.T) {
|
||||||
|
context := cli.Context{}
|
||||||
|
if err := before(&context); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
r := new()
|
||||||
|
t.Log(reflect.TypeOf(r).String())
|
||||||
|
if reflect.TypeOf(r).String() != "main.realize" {
|
||||||
|
t.Error("Expected a realize struct")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,170 @@
|
||||||
package server
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
"github.com/labstack/echo/middleware"
|
"github.com/labstack/echo/middleware"
|
||||||
"github.com/tockins/realize/settings"
|
|
||||||
"github.com/tockins/realize/watcher"
|
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
"gopkg.in/urfave/cli.v2"
|
"gopkg.in/urfave/cli.v2"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dafault host and port
|
||||||
|
const (
|
||||||
|
host = "localhost"
|
||||||
|
port = 5001
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server settings
|
// Server settings
|
||||||
type Server struct {
|
type Server struct {
|
||||||
*settings.Settings `yaml:"-"`
|
parent *realize
|
||||||
*watcher.Blueprint `yaml:"-"`
|
Status bool `yaml:"status" json:"status"`
|
||||||
Sync chan string `yaml:"-"`
|
Open bool `yaml:"open" json:"open"`
|
||||||
|
Host string `yaml:"host" json:"host"`
|
||||||
|
Port int `yaml:"port" json:"port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Websocket projects
|
||||||
|
func (s *Server) projects(c echo.Context) error {
|
||||||
|
websocket.Handler(func(ws *websocket.Conn) {
|
||||||
|
defer ws.Close()
|
||||||
|
msg, _ := json.Marshal(s.parent)
|
||||||
|
err := websocket.Message.Send(ws, string(msg))
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.parent.sync:
|
||||||
|
msg, _ := json.Marshal(s.parent)
|
||||||
|
err = websocket.Message.Send(ws, string(msg))
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
// Read
|
||||||
|
text := ""
|
||||||
|
err := websocket.Message.Receive(ws, &text)
|
||||||
|
fmt.Println("receive")
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
err := json.Unmarshal([]byte(text), &s.parent)
|
||||||
|
if err == nil {
|
||||||
|
s.parent.Settings.record(s.parent)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).ServeHTTP(c.Response(), c.Request())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the web server
|
||||||
|
func (s *Server) start(p *cli.Context) (err error) {
|
||||||
|
if p.Bool("server") {
|
||||||
|
s.parent.Server.Status = p.Bool("server")
|
||||||
|
s.parent.Server.Open = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.parent.Server.Status {
|
||||||
|
e := echo.New()
|
||||||
|
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
|
||||||
|
Level: 2,
|
||||||
|
}))
|
||||||
|
e.Use(middleware.Recover())
|
||||||
|
|
||||||
|
// web panel
|
||||||
|
e.GET("/", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/index.html", 1)
|
||||||
|
})
|
||||||
|
e.GET("/assets/js/all.min.js", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/js/all.min.js", 2)
|
||||||
|
})
|
||||||
|
e.GET("/assets/css/app.css", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/css/app.css", 3)
|
||||||
|
})
|
||||||
|
e.GET("/app/components/settings/index.html", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/app/components/settings/index.html", 1)
|
||||||
|
})
|
||||||
|
e.GET("/app/components/project/index.html", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/app/components/project/index.html", 1)
|
||||||
|
})
|
||||||
|
e.GET("/app/components/index.html", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/app/components/index.html", 1)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_settings_black_24px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_settings_black_24px.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_fullscreen_black_24px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_fullscreen_black_24px.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_add_black_24px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_add_black_24px.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_keyboard_backspace_black_24px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_keyboard_backspace_black_24px.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_error_black_48px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_error_black_48px.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_remove_black_24px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_remove_black_24px.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/logo.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/logo.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/favicon-32x32.png", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/favicon-32x32.png", 5)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_swap_vertical_circle_black_48px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_swap_vertical_circle_black_48px.svg", 4)
|
||||||
|
})
|
||||||
|
|
||||||
|
//websocket
|
||||||
|
e.GET("/ws", s.projects)
|
||||||
|
e.HideBanner = true
|
||||||
|
e.Debug = false
|
||||||
|
go e.Start(string(s.parent.Server.Host) + ":" + strconv.Itoa(s.parent.Server.Port))
|
||||||
|
_, err = s.openURL("http://" + string(s.parent.Server.Host) + ":" + strconv.Itoa(s.parent.Server.Port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenURL in a new tab of default browser
|
||||||
|
func (s *Server) openURL(url string) (io.Writer, error) {
|
||||||
|
stderr := bytes.Buffer{}
|
||||||
|
cmd := map[string]string{
|
||||||
|
"windows": "start",
|
||||||
|
"darwin": "open",
|
||||||
|
"linux": "xdg-open",
|
||||||
|
}
|
||||||
|
if s.Open {
|
||||||
|
open, err := cmd[runtime.GOOS]
|
||||||
|
if !err {
|
||||||
|
return nil, fmt.Errorf("operating system %q is not supported", runtime.GOOS)
|
||||||
|
}
|
||||||
|
cmd := exec.Command(open, url)
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return cmd.Stderr, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render return a web pages defined in bindata
|
// Render return a web pages defined in bindata
|
||||||
func render(c echo.Context, path string, mime int) error {
|
func (s *Server) render(c echo.Context, path string, mime int) error {
|
||||||
data, err := Asset(path)
|
data, err := Asset(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
|
@ -49,114 +192,3 @@ func render(c echo.Context, path string, mime int) error {
|
||||||
rs.Write(data)
|
rs.Write(data)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the web server
|
|
||||||
func (s *Server) Start(p *cli.Context) (err error) {
|
|
||||||
if p.Bool("server") {
|
|
||||||
s.Server.Status = p.Bool("server")
|
|
||||||
}
|
|
||||||
if p.Bool("open") {
|
|
||||||
s.Server.Open = p.Bool("open")
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Server.Status {
|
|
||||||
e := echo.New()
|
|
||||||
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
|
|
||||||
Level: 2,
|
|
||||||
}))
|
|
||||||
e.Use(middleware.Recover())
|
|
||||||
|
|
||||||
// web panel
|
|
||||||
e.GET("/", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/index.html", 1)
|
|
||||||
})
|
|
||||||
e.GET("/assets/js/all.min.js", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/js/all.min.js", 2)
|
|
||||||
})
|
|
||||||
e.GET("/assets/css/app.css", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/css/app.css", 3)
|
|
||||||
})
|
|
||||||
e.GET("/app/components/settings/index.html", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/app/components/settings/index.html", 1)
|
|
||||||
})
|
|
||||||
e.GET("/app/components/project/index.html", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/app/components/project/index.html", 1)
|
|
||||||
})
|
|
||||||
e.GET("/app/components/index.html", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/app/components/index.html", 1)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/ic_settings_black_24px.svg", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/img/svg/ic_settings_black_24px.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/ic_fullscreen_black_24px.svg", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/img/svg/ic_fullscreen_black_24px.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/ic_add_black_24px.svg", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/img/svg/ic_add_black_24px.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/ic_keyboard_backspace_black_24px.svg", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/img/svg/ic_keyboard_backspace_black_24px.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/ic_error_black_48px.svg", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/img/svg/ic_error_black_48px.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/ic_remove_black_24px.svg", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/img/svg/ic_remove_black_24px.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/logo.svg", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/img/svg/logo.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/favicon-32x32.png", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/img/favicon-32x32.png", 5)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/ic_swap_vertical_circle_black_48px.svg", func(c echo.Context) error {
|
|
||||||
return render(c, "assets/assets/img/svg/ic_swap_vertical_circle_black_48px.svg", 4)
|
|
||||||
})
|
|
||||||
|
|
||||||
//websocket
|
|
||||||
e.GET("/ws", s.projects)
|
|
||||||
|
|
||||||
go e.Start(string(s.Settings.Server.Host) + ":" + strconv.Itoa(s.Settings.Server.Port))
|
|
||||||
_, err = s.OpenURL("http://" + string(s.Settings.Server.Host) + ":" + strconv.Itoa(s.Settings.Server.Port))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Websocket projects
|
|
||||||
func (s *Server) projects(c echo.Context) error {
|
|
||||||
websocket.Handler(func(ws *websocket.Conn) {
|
|
||||||
defer ws.Close()
|
|
||||||
msg, _ := json.Marshal(s.Blueprint.Projects)
|
|
||||||
err := websocket.Message.Send(ws, string(msg))
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.Sync:
|
|
||||||
msg, _ := json.Marshal(s.Blueprint.Projects)
|
|
||||||
err = websocket.Message.Send(ws, string(msg))
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
// Read
|
|
||||||
text := ""
|
|
||||||
err := websocket.Message.Receive(ws, &text)
|
|
||||||
if err != nil {
|
|
||||||
//log.Println(err)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
err := json.Unmarshal([]byte(text), &s.Blueprint.Projects)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).ServeHTTP(c.Response(), c.Request())
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/tockins/realize/settings"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServer_Start(t *testing.T) {
|
|
||||||
s := settings.Settings{
|
|
||||||
Server: settings.Server{
|
|
||||||
Status: true,
|
|
||||||
Open: false,
|
|
||||||
Host: "localhost",
|
|
||||||
Port: 5000,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
server := Server{
|
|
||||||
Settings: &s,
|
|
||||||
}
|
|
||||||
err := server.Start(nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
host := "http://localhost:5000/"
|
|
||||||
urls := []string{
|
|
||||||
host,
|
|
||||||
host + "assets/js/all.min.js",
|
|
||||||
host + "assets/css/app.css",
|
|
||||||
host + "app/components/settings/index.html",
|
|
||||||
host + "app/components/project/index.html",
|
|
||||||
host + "app/components/project/index.html",
|
|
||||||
host + "app/components/index.html",
|
|
||||||
host + "assets/img/svg/ic_settings_black_24px.svg",
|
|
||||||
host + "assets/img/svg/ic_fullscreen_black_24px.svg",
|
|
||||||
host + "assets/img/svg/ic_add_black_24px.svg",
|
|
||||||
host + "assets/img/svg/ic_keyboard_backspace_black_24px.svg",
|
|
||||||
host + "assets/img/svg/ic_error_black_48px.svg",
|
|
||||||
host + "assets/img/svg/ic_remove_black_24px.svg",
|
|
||||||
host + "assets/img/svg/logo.svg",
|
|
||||||
host + "assets/img/favicon-32x32.png",
|
|
||||||
host + "assets/img/svg/ic_swap_vertical_circle_black_48px.svg",
|
|
||||||
}
|
|
||||||
for _, elm := range urls {
|
|
||||||
resp, err := http.Get(elm)
|
|
||||||
if err != nil || resp.StatusCode != 200 {
|
|
||||||
t.Fatal(err, resp.StatusCode, elm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cmd map[string]string
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
|
|
||||||
// Init an associative array with the os supported
|
|
||||||
func init() {
|
|
||||||
cmd = map[string]string{
|
|
||||||
"windows": "start",
|
|
||||||
"darwin": "open",
|
|
||||||
"linux": "xdg-open",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open a url in the default browser
|
|
||||||
func (s *Server) OpenURL(url string) (io.Writer, error) {
|
|
||||||
if s.Open {
|
|
||||||
open, err := cmd[runtime.GOOS]
|
|
||||||
if !err {
|
|
||||||
return nil, fmt.Errorf("operating system %q is not supported", runtime.GOOS)
|
|
||||||
}
|
|
||||||
cmd := exec.Command(open, url)
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return cmd.Stderr, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
//import (
|
|
||||||
// "testing"
|
|
||||||
// //"fmt"
|
|
||||||
// "github.com/tockins/realize/settings"
|
|
||||||
// "fmt"
|
|
||||||
//)
|
|
||||||
//
|
|
||||||
//func TestOpen(t *testing.T) {
|
|
||||||
// config := settings.Settings{
|
|
||||||
// Server: settings.Server{
|
|
||||||
// Open: true,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// s := Server{
|
|
||||||
// Settings: &config,
|
|
||||||
// }
|
|
||||||
// url := "open_test"
|
|
||||||
// out, err := s.OpenURL(url)
|
|
||||||
// if err == nil {
|
|
||||||
// t.Fatal("Unexpected, invalid url", url, err)
|
|
||||||
// }
|
|
||||||
// output := fmt.Sprint(out)
|
|
||||||
// if output == "" {
|
|
||||||
// t.Fatal("Unexpected, invalid url", url, output)
|
|
||||||
// }
|
|
||||||
//}
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
//import (
|
||||||
|
// "fmt"
|
||||||
|
// "net/http"
|
||||||
|
// "testing"
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//func TestServer_Start(t *testing.T) {
|
||||||
|
// s := Server{
|
||||||
|
// Status: true,
|
||||||
|
// Open: false,
|
||||||
|
// Host: "localhost",
|
||||||
|
// Port: 5000,
|
||||||
|
// }
|
||||||
|
// err := s.start(nil)
|
||||||
|
// if err != nil {
|
||||||
|
// t.Fatal(err)
|
||||||
|
// }
|
||||||
|
// host := "http://localhost:5000/"
|
||||||
|
// urls := []string{
|
||||||
|
// host,
|
||||||
|
// host + "assets/js/all.min.js",
|
||||||
|
// host + "assets/css/app.css",
|
||||||
|
// host + "app/components/settings/index.html",
|
||||||
|
// host + "app/components/project/index.html",
|
||||||
|
// host + "app/components/project/index.html",
|
||||||
|
// host + "app/components/index.html",
|
||||||
|
// host + "assets/img/svg/ic_settings_black_24px.svg",
|
||||||
|
// host + "assets/img/svg/ic_fullscreen_black_24px.svg",
|
||||||
|
// host + "assets/img/svg/ic_add_black_24px.svg",
|
||||||
|
// host + "assets/img/svg/ic_keyboard_backspace_black_24px.svg",
|
||||||
|
// host + "assets/img/svg/ic_error_black_48px.svg",
|
||||||
|
// host + "assets/img/svg/ic_remove_black_24px.svg",
|
||||||
|
// host + "assets/img/svg/logo.svg",
|
||||||
|
// host + "assets/img/favicon-32x32.png",
|
||||||
|
// host + "assets/img/svg/ic_swap_vertical_circle_black_48px.svg",
|
||||||
|
// }
|
||||||
|
// for _, elm := range urls {
|
||||||
|
// resp, err := http.Get(elm)
|
||||||
|
// if err != nil || resp.StatusCode != 200 {
|
||||||
|
// t.Fatal(err, resp.StatusCode, elm)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestServer_Open(t *testing.T) {
|
||||||
|
// c := Server{
|
||||||
|
// Open: true,
|
||||||
|
// }
|
||||||
|
// url := "open_test"
|
||||||
|
// out, err := c.openURL(url)
|
||||||
|
// if err == nil {
|
||||||
|
// t.Fatal("Unexpected, invalid url", url, err)
|
||||||
|
// }
|
||||||
|
// output := fmt.Sprint(out)
|
||||||
|
// if output == "" {
|
||||||
|
// t.Fatal("Unexpected, invalid url", url, output)
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -0,0 +1,198 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"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"`
|
||||||
|
FileLimit int64 `yaml:"flimit,omitempty" json:"flimit,omitempty"`
|
||||||
|
Legacy Legacy `yaml:"legacy" json:"legacy"`
|
||||||
|
Recovery bool `yaml:"recovery,omitempty" json:"recovery,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy is used to force polling and set a custom interval
|
||||||
|
type Legacy struct {
|
||||||
|
Force bool `yaml:"force" json:"force"`
|
||||||
|
Interval time.Duration `yaml:"interval" json:"interval"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files defines the files generated by realize
|
||||||
|
type Files struct {
|
||||||
|
Clean bool `yaml:"clean,omitempty" json:"clean,omitempty"`
|
||||||
|
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 {
|
||||||
|
var rLimit syscall.Rlimit
|
||||||
|
rLimit.Max = uint64(s.FileLimit)
|
||||||
|
rLimit.Cur = uint64(s.FileLimit)
|
||||||
|
|
||||||
|
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete realize folder
|
||||||
|
func (s *Settings) del(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
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package settings
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
// Flimit defines the max number of watched files
|
|
||||||
func (s *Settings) Flimit() error {
|
|
||||||
var rLimit syscall.Rlimit
|
|
||||||
rLimit.Max = uint64(s.Config.Flimit)
|
|
||||||
rLimit.Cur = uint64(s.Config.Flimit)
|
|
||||||
|
|
||||||
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSettings_Flimit(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
s.Config.Flimit = 100
|
|
||||||
if err := s.Flimit(); err != nil {
|
|
||||||
t.Fatal("Unable to increase limit", err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
// build windows
|
|
||||||
package settings
|
|
||||||
|
|
||||||
// Flimit defines the max number of watched files
|
|
||||||
func (s *Settings) Flimit() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a file
|
|
||||||
func (s Settings) Write(name string, data []byte) error {
|
|
||||||
err := ioutil.WriteFile(name, data, Permission)
|
|
||||||
return s.Validate(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/labstack/gommon/random"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSettings_Stream(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
filename := random.String(4)
|
|
||||||
if _, err := s.Stream(filename); err == nil {
|
|
||||||
t.Fatal("Error expected, none found", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = "io.go"
|
|
||||||
if _, err := s.Stream(filename); err != nil {
|
|
||||||
t.Fatal("Error unexpected", filename, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Write(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
data := "abcdefgh"
|
|
||||||
d, err := ioutil.TempFile("", "io_test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := s.Write(d.Name(), []byte(data)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Create(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
p, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
f := s.Create(p, "io_test")
|
|
||||||
os.Remove(f.Name())
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// settings const
|
|
||||||
const (
|
|
||||||
Permission = 0775
|
|
||||||
Directory = ".realize/"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Settings defines a group of general settings
|
|
||||||
type Settings struct {
|
|
||||||
Config `yaml:",inline" json:"config"`
|
|
||||||
Resources `yaml:"resources" json:"resources"`
|
|
||||||
Server `yaml:"server,omitempty" json:"server,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config defines structural options
|
|
||||||
type Config struct {
|
|
||||||
Create bool `yaml:"-" json:"-"`
|
|
||||||
Flimit int64 `yaml:"flimit,omitempty" json:"flimit,omitempty"`
|
|
||||||
Legacy `yaml:"legacy,omitempty" json:"legacy,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Legacy configuration
|
|
||||||
type Legacy struct {
|
|
||||||
Status bool `yaml:"status" json:"status"`
|
|
||||||
Interval time.Duration `yaml:"interval" json:"interval"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resources defines the files generated by realize
|
|
||||||
type Resources struct {
|
|
||||||
Config string `yaml:"-" json:"-"`
|
|
||||||
Outputs string `yaml:"outputs" json:"outputs"`
|
|
||||||
Logs string `yaml:"logs" json:"log"`
|
|
||||||
Errors string `yaml:"errors" json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read from config file
|
|
||||||
func (s *Settings) Read(out interface{}) error {
|
|
||||||
localConfigPath := s.Resources.Config
|
|
||||||
// backward compatibility
|
|
||||||
if _, err := os.Stat(Directory + s.Resources.Config); err == nil {
|
|
||||||
localConfigPath = Directory + s.Resources.Config
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
if s.Config.Create {
|
|
||||||
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.Resources.Config, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.Write(Directory+s.Resources.Config, y)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove realize folder
|
|
||||||
func (s *Settings) Remove(d string) error {
|
|
||||||
_, err := os.Stat(d)
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return os.RemoveAll(d)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSettings_Read(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
var a interface{}
|
|
||||||
s.Resources.Config = "settings_b"
|
|
||||||
if err := s.Read(a); err == nil {
|
|
||||||
t.Fatal("Error unexpected", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Resources.Config = "settings_test.yaml"
|
|
||||||
d, err := ioutil.TempFile("", "settings_test.yaml")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s.Resources.Config = d.Name()
|
|
||||||
if err := s.Read(a); err != nil {
|
|
||||||
t.Fatal("Error unexpected", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Remove(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
if err := s.Remove("abcd"); err == nil {
|
|
||||||
t.Fatal("Error unexpected, dir dosn't exist", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := ioutil.TempDir("", "settings_test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := s.Remove(d); err != nil {
|
|
||||||
t.Fatal("Error unexpected, dir exist", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Record(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
s.Resources.Config = "settings_test.yaml"
|
|
||||||
var a interface{}
|
|
||||||
if err := s.Record(a); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s.Remove(filepath.Join(Directory, s.Resources.Config))
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tockins/realize/style"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wdir return the current working Directory
|
|
||||||
func (s Settings) Wdir() string {
|
|
||||||
dir, err := os.Getwd()
|
|
||||||
s.Validate(err)
|
|
||||||
return filepath.Base(dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate checks a fatal error
|
|
||||||
func (s Settings) Validate(err error) error {
|
|
||||||
if err != nil {
|
|
||||||
s.Fatal(err, "")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(style.Red.Regular(msg...), err.Error())
|
|
||||||
} else if err != nil {
|
|
||||||
log.Fatalln(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path cleaner
|
|
||||||
func (s Settings) Path(path string) string {
|
|
||||||
return strings.Replace(filepath.Clean(path), "\\", "/", -1)
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/labstack/gommon/random"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSettings_Wdir(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
expected, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
result := s.Wdir()
|
|
||||||
if result != filepath.Base(expected) {
|
|
||||||
t.Error("Expected", filepath.Base(expected), "instead", result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Validate(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
input := errors.New("")
|
|
||||||
input = nil
|
|
||||||
if err := s.Validate(input); err != nil {
|
|
||||||
t.Error("Expected", input, "instead", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Name(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
name := random.String(8)
|
|
||||||
path := random.String(5)
|
|
||||||
dir, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
result := s.Name(name, path)
|
|
||||||
if result != dir && result != filepath.Base(path) {
|
|
||||||
t.Fatal("Expected", dir, "or", filepath.Base(path), "instead", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Path(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
path := random.String(5)
|
|
||||||
expected := strings.Replace(filepath.Clean(path), "\\", "/", -1)
|
|
||||||
result := s.Path(path)
|
|
||||||
if result != expected {
|
|
||||||
t.Fatal("Expected", expected, "instead", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSettings_Flimit(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
s.FileLimit = 100
|
||||||
|
if err := s.flimit(); err != nil {
|
||||||
|
t.Fatal("Unable to increase limit", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Stream(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
filename := random(4)
|
||||||
|
if _, err := s.stream(filename); err == nil {
|
||||||
|
t.Fatal("Error expected, none found", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = "settings.go"
|
||||||
|
if _, err := s.stream(filename); err != nil {
|
||||||
|
t.Fatal("Error unexpected", filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Write(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
data := "abcdefgh"
|
||||||
|
d, err := ioutil.TempFile("", "io_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := s.write(d.Name(), []byte(data)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Create(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
p, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f := s.create(p, "io_test")
|
||||||
|
os.Remove(f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Read(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
var a interface{}
|
||||||
|
s.file = "settings_b"
|
||||||
|
if err := s.read(a); err == nil {
|
||||||
|
t.Fatal("Error unexpected", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.file = "settings_test.yaml"
|
||||||
|
dir, err := ioutil.TempDir("", directory)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
d, err := ioutil.TempFile(dir, "settings_test.yaml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s.file = d.Name()
|
||||||
|
if err := s.read(a); err != nil {
|
||||||
|
t.Fatal("Error unexpected", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Del(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
if err := s.del("abcd"); err == nil {
|
||||||
|
t.Fatal("Error unexpected, dir dosn't exist", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := ioutil.TempDir("", "settings_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := s.del(d); err != nil {
|
||||||
|
t.Fatal("Error unexpected, dir exist", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Record(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
s.file = "settings_test.yaml"
|
||||||
|
var a interface{}
|
||||||
|
if err := s.record(a); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s.del(filepath.Join(directory, s.file))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Wdir(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
expected, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
result := s.wdir()
|
||||||
|
if result != filepath.Base(expected) {
|
||||||
|
t.Error("Expected", filepath.Base(expected), "instead", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Validate(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
input := errors.New("")
|
||||||
|
input = nil
|
||||||
|
if err := s.validate(input); err != nil {
|
||||||
|
t.Error("Expected", input, "instead", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Name(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
name := random(8)
|
||||||
|
path := random(5)
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
result := s.name(name, path)
|
||||||
|
if result != dir && result != filepath.Base(path) {
|
||||||
|
t.Fatal("Expected", dir, "or", filepath.Base(path), "instead", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Path(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
path := random(5)
|
||||||
|
expected := strings.Replace(filepath.Clean(path), "\\", "/", -1)
|
||||||
|
result := s.path(path)
|
||||||
|
if result != expected {
|
||||||
|
t.Fatal("Expected", expected, "instead", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
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)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
output = color.Output
|
||||||
|
red = colorBase(color.FgRed)
|
||||||
|
blue = colorBase(color.FgBlue)
|
||||||
|
green = colorBase(color.FgGreen)
|
||||||
|
yellow = colorBase(color.FgYellow)
|
||||||
|
magenta = colorBase(color.FgMagenta)
|
||||||
|
)
|
||||||
|
|
||||||
|
type colorBase color.Attribute
|
||||||
|
|
||||||
|
func (c colorBase) regular(a ...interface{}) string {
|
||||||
|
return color.New(color.Attribute(c)).Sprint(a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c colorBase) bold(a ...interface{}) string {
|
||||||
|
return color.New(color.Attribute(c), color.Bold).Sprint(a...)
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
package style
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fatih/color"
|
|
||||||
)
|
|
||||||
|
|
||||||
type colorBase color.Attribute
|
|
||||||
|
|
||||||
func (s colorBase) Regular(a ...interface{}) string {
|
|
||||||
return color.New(color.Attribute(s)).Sprint(a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s colorBase) Bold(a ...interface{}) string {
|
|
||||||
return color.New(color.Attribute(s), color.Bold).Sprint(a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// allowed colors
|
|
||||||
var (
|
|
||||||
Red = colorBase(color.FgRed)
|
|
||||||
Blue = colorBase(color.FgBlue)
|
|
||||||
Yellow = colorBase(color.FgYellow)
|
|
||||||
Magenta = colorBase(color.FgMagenta)
|
|
||||||
Green = colorBase(color.FgGreen)
|
|
||||||
)
|
|
|
@ -1,34 +1,35 @@
|
||||||
package style
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/fatih/color"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestColorBase_Regular(t *testing.T) {
|
func TestStyle_Regular(t *testing.T) {
|
||||||
c := new(colorBase)
|
|
||||||
strs := []string{"a", "b", "c"}
|
strs := []string{"a", "b", "c"}
|
||||||
input := make([]interface{}, len(strs))
|
input := make([]interface{}, len(strs))
|
||||||
for i, s := range strs {
|
for i, s := range strs {
|
||||||
input[i] = s
|
input[i] = s
|
||||||
}
|
}
|
||||||
result := c.Regular(input)
|
result := red.regular(input)
|
||||||
expected := fmt.Sprint(input)
|
c := color.New(color.FgRed).SprintFunc()
|
||||||
|
expected := fmt.Sprint(c(input))
|
||||||
if !bytes.Equal([]byte(result), []byte(expected)) {
|
if !bytes.Equal([]byte(result), []byte(expected)) {
|
||||||
t.Error("Expected:", expected, "instead", result)
|
t.Error("Expected:", expected, "instead", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorBase_Bold(t *testing.T) {
|
func TestStyle_Bold(t *testing.T) {
|
||||||
c := new(colorBase)
|
|
||||||
strs := []string{"a", "b", "c"}
|
strs := []string{"a", "b", "c"}
|
||||||
input := make([]interface{}, len(strs))
|
input := make([]interface{}, len(strs))
|
||||||
for i, s := range strs {
|
for i, s := range strs {
|
||||||
input[i] = s
|
input[i] = s
|
||||||
}
|
}
|
||||||
result := c.Bold(input)
|
result := red.bold(input)
|
||||||
expected := fmt.Sprint(input)
|
c := color.New(color.FgRed, color.Bold).SprintFunc()
|
||||||
|
expected := fmt.Sprint(c(input))
|
||||||
if !bytes.Equal([]byte(result), []byte(expected)) {
|
if !bytes.Equal([]byte(result), []byte(expected)) {
|
||||||
t.Error("Expected:", expected, "instead", result)
|
t.Error("Expected:", expected, "instead", result)
|
||||||
}
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"gopkg.in/urfave/cli.v2"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getEnvPath returns the first path found in env or empty string
|
||||||
|
func getEnvPath(env string) string {
|
||||||
|
path := filepath.SplitList(os.Getenv(env))
|
||||||
|
if len(path) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return path[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array check if a string is in given array
|
||||||
|
func array(str string, list []string) bool {
|
||||||
|
for _, v := range list {
|
||||||
|
if v == str {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params parse one by one the given argumentes
|
||||||
|
func params(params *cli.Context) []string {
|
||||||
|
argsN := params.NArg()
|
||||||
|
if argsN > 0 {
|
||||||
|
var args []string
|
||||||
|
for i := 0; i <= argsN-1; i++ {
|
||||||
|
args = append(args, params.Args().Get(i))
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split each arguments in multiple fields
|
||||||
|
func split(args, fields []string) []string {
|
||||||
|
for _, arg := range fields {
|
||||||
|
arr := strings.Fields(arg)
|
||||||
|
args = append(args, arr...)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Project{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file extensions
|
||||||
|
func ext(path string) string {
|
||||||
|
var ext string
|
||||||
|
for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- {
|
||||||
|
if path[i] == '.' {
|
||||||
|
ext = path[i:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ext != "" {
|
||||||
|
return ext[1:]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace if isn't empty and create a new array
|
||||||
|
func replace(a []string, b string) []string {
|
||||||
|
if len(b) > 0 {
|
||||||
|
return strings.Fields(b)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package watcher
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
@ -11,9 +11,9 @@ import (
|
||||||
func TestArgsParam(t *testing.T) {
|
func TestArgsParam(t *testing.T) {
|
||||||
set := flag.NewFlagSet("test", 0)
|
set := flag.NewFlagSet("test", 0)
|
||||||
set.Bool("myflag", false, "doc")
|
set.Bool("myflag", false, "doc")
|
||||||
params := cli.NewContext(nil, set, nil)
|
p := cli.NewContext(nil, set, nil)
|
||||||
set.Parse([]string{"--myflag", "bat", "baz"})
|
set.Parse([]string{"--myflag", "bat", "baz"})
|
||||||
result := argsParam(params)
|
result := params(p)
|
||||||
if len(result) != 2 {
|
if len(result) != 2 {
|
||||||
t.Fatal("Expected 2 instead", len(result))
|
t.Fatal("Expected 2 instead", len(result))
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,13 @@ func TestDuplicates(t *testing.T) {
|
||||||
projects := []Project{
|
projects := []Project{
|
||||||
{
|
{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
|
Path: "a",
|
||||||
}, {
|
}, {
|
||||||
Name: "b",
|
Name: "a",
|
||||||
|
Path: "a",
|
||||||
}, {
|
}, {
|
||||||
Name: "c",
|
Name: "c",
|
||||||
|
Path: "c",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, err := duplicates(projects[0], projects)
|
_, err := duplicates(projects[0], projects)
|
||||||
|
@ -42,10 +45,10 @@ func TestDuplicates(t *testing.T) {
|
||||||
|
|
||||||
func TestInArray(t *testing.T) {
|
func TestInArray(t *testing.T) {
|
||||||
arr := []string{"a", "b", "c"}
|
arr := []string{"a", "b", "c"}
|
||||||
if !inArray(arr[0], arr) {
|
if !array(arr[0], arr) {
|
||||||
t.Fatal("Unexpected", arr[0], "should be in", arr)
|
t.Fatal("Unexpected", arr[0], "should be in", arr)
|
||||||
}
|
}
|
||||||
if inArray("d", arr) {
|
if array("d", arr) {
|
||||||
t.Fatal("Unexpected", "d", "shouldn't be in", arr)
|
t.Fatal("Unexpected", "d", "shouldn't be in", arr)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,441 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
msg string
|
||||||
|
out BufferOut
|
||||||
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
msgStop = "killed"
|
||||||
|
extWindows = ".exe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watch struct defines options for livereload
|
||||||
|
type Watch struct {
|
||||||
|
Preview bool `yaml:"preview,omitempty" json:"preview,omitempty"`
|
||||||
|
Paths []string `yaml:"paths" json:"paths"`
|
||||||
|
Exts []string `yaml:"extensions" json:"extensions"`
|
||||||
|
Ignore []string `yaml:"ignored_paths,omitempty" json:"ignored_paths,omitempty"`
|
||||||
|
Scripts []Command `yaml:"scripts,omitempty" json:"scripts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result channel with data stream and errors
|
||||||
|
type Result struct {
|
||||||
|
stream string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer define an array buffer for each log files
|
||||||
|
type Buffer struct {
|
||||||
|
StdOut []BufferOut `json:"stdOut"`
|
||||||
|
StdLog []BufferOut `json:"stdLog"`
|
||||||
|
StdErr []BufferOut `json:"stdErr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project defines the informations of a single project
|
||||||
|
type Project struct {
|
||||||
|
parent *realize
|
||||||
|
watcher FileWatcher
|
||||||
|
init bool
|
||||||
|
files, folders int64
|
||||||
|
base, path, 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
|
||||||
|
type Command struct {
|
||||||
|
Type string `yaml:"type" json:"type"`
|
||||||
|
Command string `yaml:"command" json:"command"`
|
||||||
|
Path string `yaml:"path,omitempty" json:"path,omitempty"`
|
||||||
|
Global bool `yaml:"global,omitempty" json:"global,omitempty"`
|
||||||
|
Output bool `yaml:"output,omitempty" json:"output,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BufferOut is used for exchange information between "realize cli" and "web realize"
|
||||||
|
type BufferOut struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Stream string `json:"stream"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch the project
|
||||||
|
func (p *Project) watch() {
|
||||||
|
p.watcher, _ = Watcher(r.Settings.Legacy.Force, r.Settings.Legacy.Interval)
|
||||||
|
stop, exit := make(chan bool), make(chan os.Signal, 2)
|
||||||
|
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
|
||||||
|
// before global commands
|
||||||
|
p.cmd(stop, "before", true)
|
||||||
|
// indexing files and dirs
|
||||||
|
for _, dir := range p.Watcher.Paths {
|
||||||
|
base := filepath.Join(p.base, dir)
|
||||||
|
if _, err := os.Stat(base); err == nil {
|
||||||
|
if err := filepath.Walk(base, p.walk); err == nil {
|
||||||
|
p.tool(stop, base)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// indexing done, files and folders
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 1), ":", blue.bold("Watching"), magenta.bold(p.files), "file/s", magenta.bold(p.folders), "folder/s")
|
||||||
|
out = BufferOut{Time: time.Now(), Text: "Watching " + strconv.FormatInt(p.files, 10) + " files/s " + strconv.FormatInt(p.folders, 10) + " folder/s"}
|
||||||
|
p.stamp("log", out, msg, "")
|
||||||
|
// start
|
||||||
|
go p.routines(stop, p.watcher, "")
|
||||||
|
//is watching
|
||||||
|
L:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-p.watcher.Events():
|
||||||
|
if time.Now().Truncate(time.Second).After(p.lastTime) || event.Name != p.lastFile {
|
||||||
|
switch event.Op {
|
||||||
|
case fsnotify.Chmod:
|
||||||
|
case fsnotify.Remove:
|
||||||
|
ext := ext(event.Name)
|
||||||
|
if !strings.Contains(ext, "_") && ext != "" {
|
||||||
|
close(stop)
|
||||||
|
stop = make(chan bool)
|
||||||
|
p.changed(event, stop) // stop
|
||||||
|
}
|
||||||
|
p.watcher.Remove(event.Name)
|
||||||
|
default:
|
||||||
|
file, err := os.Stat(event.Name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if file.IsDir() {
|
||||||
|
if time.Now().Truncate(time.Second).After(p.lastTime) {
|
||||||
|
filepath.Walk(event.Name, p.walk)
|
||||||
|
}
|
||||||
|
} else if file.Size() > 0 {
|
||||||
|
if p.parent.Settings.Recovery {
|
||||||
|
log.Println(event)
|
||||||
|
}
|
||||||
|
ext := ext(event.Name)
|
||||||
|
if (!strings.Contains(ext, "_") || !strings.Contains(ext, ".")) && array(ext, p.Watcher.Exts) {
|
||||||
|
// change watched
|
||||||
|
close(stop)
|
||||||
|
stop = make(chan bool)
|
||||||
|
p.changed(event, stop)
|
||||||
|
}
|
||||||
|
p.lastTime = time.Now().Truncate(time.Second)
|
||||||
|
p.lastFile = event.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case err := <-p.watcher.Errors():
|
||||||
|
p.err(err)
|
||||||
|
case <-exit:
|
||||||
|
p.cmd(nil, "after", true)
|
||||||
|
break L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error occurred
|
||||||
|
func (p *Project) err(err error) {
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", red.regular(err.Error()))
|
||||||
|
out = BufferOut{Time: time.Now(), Text: err.Error()}
|
||||||
|
p.stamp("error", out, msg, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
// cmds are scheduled in sequence
|
||||||
|
go func() {
|
||||||
|
for _, cmd := range p.Watcher.Scripts {
|
||||||
|
if strings.ToLower(cmd.Type) == flag && cmd.Global == global {
|
||||||
|
err, logs := p.command(stop, cmd)
|
||||||
|
if err == "" && logs == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", green.bold("Command"), green.bold("\"")+cmd.Command+green.bold("\""))
|
||||||
|
out = BufferOut{Time: time.Now(), Text: cmd.Command, Type: flag}
|
||||||
|
if err != "" {
|
||||||
|
p.stamp("error", out, msg, "")
|
||||||
|
|
||||||
|
msg = fmt.Sprintln(red.regular(err))
|
||||||
|
out = BufferOut{Time: time.Now(), Text: err, Type: flag}
|
||||||
|
p.stamp("error", out, "", msg)
|
||||||
|
|
||||||
|
} else if logs != "" && cmd.Output {
|
||||||
|
msg = fmt.Sprintln(logs)
|
||||||
|
out = BufferOut{Time: time.Now(), Text: logs, Type: flag}
|
||||||
|
p.stamp("log", out, "", msg)
|
||||||
|
} else {
|
||||||
|
p.stamp("log", out, msg, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile is used for run and display the result of a compiling
|
||||||
|
func (p *Project) compile(stop <-chan bool, cmd Cmd) error {
|
||||||
|
if cmd.Status {
|
||||||
|
start := time.Now()
|
||||||
|
channel := make(chan Result)
|
||||||
|
go func() {
|
||||||
|
log.Println(p.pname(p.Name, 1), ":", cmd.startTxt)
|
||||||
|
stream, err := p.goCompile(stop, cmd.method, cmd.Args)
|
||||||
|
if stream != msgStop {
|
||||||
|
channel <- Result{stream, err}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case r := <-channel:
|
||||||
|
if r.err != nil {
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", red.bold(cmd.name), red.regular(r.err.Error()))
|
||||||
|
out = BufferOut{Time: time.Now(), Text: r.err.Error(), Type: cmd.name, Stream: r.stream}
|
||||||
|
p.stamp("error", out, msg, r.stream)
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", green.regular(cmd.endTxt), "in", magenta.regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s"))
|
||||||
|
out = BufferOut{Time: time.Now(), Text: cmd.name + " in " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"}
|
||||||
|
p.stamp("log", out, msg, r.stream)
|
||||||
|
}
|
||||||
|
return r.err
|
||||||
|
case <-stop:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines the colors scheme for the project name
|
||||||
|
func (p *Project) pname(name string, color int) string {
|
||||||
|
switch color {
|
||||||
|
case 1:
|
||||||
|
name = yellow.regular("[") + strings.ToUpper(name) + yellow.regular("]")
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
name = yellow.regular("[") + red.bold(strings.ToUpper(name)) + yellow.regular("]")
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
name = yellow.regular("[") + blue.bold(strings.ToUpper(name)) + yellow.regular("]")
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
name = yellow.regular("[") + magenta.bold(strings.ToUpper(name)) + yellow.regular("]")
|
||||||
|
break
|
||||||
|
case 5:
|
||||||
|
name = yellow.regular("[") + green.bold(strings.ToUpper(name)) + yellow.regular("]")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tool logs the result of a go command
|
||||||
|
func (p *Project) tool(stop <-chan bool, path string) error {
|
||||||
|
if len(path) > 0 {
|
||||||
|
done := make(chan bool)
|
||||||
|
result := make(chan tool)
|
||||||
|
go func() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(p.tools))
|
||||||
|
for _, element := range p.tools {
|
||||||
|
// no need a sequence, these commands can be asynchronous
|
||||||
|
go p.goTool(&wg, stop, result, path, element)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case tool := <-result:
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", red.bold(tool.name), red.regular("there are some errors in"), ":", magenta.bold(path))
|
||||||
|
buff := BufferOut{Time: time.Now(), Text: "there are some errors in", Path: path, Type: tool.name, Stream: tool.err}
|
||||||
|
p.stamp("error", buff, msg, tool.err)
|
||||||
|
case <-done:
|
||||||
|
break loop
|
||||||
|
case <-stop:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changed detect a file/directory change
|
||||||
|
func (p *Project) changed(event fsnotify.Event, stop chan bool) {
|
||||||
|
e := ext(event.Name)
|
||||||
|
if e == "" {
|
||||||
|
e = "DIR"
|
||||||
|
}
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 4), ":", magenta.bold(strings.ToUpper(e)), "changed", magenta.bold(event.Name))
|
||||||
|
out = BufferOut{Time: time.Now(), Text: ext(event.Name) + " changed " + event.Name}
|
||||||
|
p.stamp("log", out, msg, "")
|
||||||
|
//stop running process
|
||||||
|
go p.routines(stop, p.watcher, event.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
if strings.Contains(path, filepath.Join(p.base, v)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !strings.Contains(path, "/.") && !strings.HasPrefix(path, ".") && (info.IsDir() || array(ext(path), p.Watcher.Exts)) {
|
||||||
|
result := p.watcher.Walk(path, p.init)
|
||||||
|
if result != "" {
|
||||||
|
if info.IsDir() {
|
||||||
|
p.folders++
|
||||||
|
} else {
|
||||||
|
p.files++
|
||||||
|
}
|
||||||
|
if p.Watcher.Preview {
|
||||||
|
log.Println(p.pname(p.Name, 1), ":", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print on files, cli, ws
|
||||||
|
func (p *Project) stamp(t string, o BufferOut, msg string, stream string) {
|
||||||
|
switch t {
|
||||||
|
case "out":
|
||||||
|
p.Buffer.StdOut = append(p.Buffer.StdOut, o)
|
||||||
|
if p.Files.Outputs.Status {
|
||||||
|
f := p.create(p.base, p.Files.Outputs.Name)
|
||||||
|
t := time.Now()
|
||||||
|
s := []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n"}
|
||||||
|
if _, err := f.WriteString(strings.Join(s, " ")); err != nil {
|
||||||
|
p.fatal(err, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "log":
|
||||||
|
p.Buffer.StdLog = append(p.Buffer.StdLog, o)
|
||||||
|
if p.Files.Logs.Status {
|
||||||
|
f := p.create(p.base, p.Files.Logs.Name)
|
||||||
|
t := time.Now()
|
||||||
|
s := []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n"}
|
||||||
|
if stream != "" {
|
||||||
|
s = []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n", stream}
|
||||||
|
}
|
||||||
|
if _, err := f.WriteString(strings.Join(s, " ")); err != nil {
|
||||||
|
p.fatal(err, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "error":
|
||||||
|
p.Buffer.StdErr = append(p.Buffer.StdErr, o)
|
||||||
|
if p.Files.Errors.Status {
|
||||||
|
f := p.create(p.base, p.Files.Errors.Name)
|
||||||
|
t := time.Now()
|
||||||
|
s := []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Type, o.Text, o.Path, "\r\n"}
|
||||||
|
if stream != "" {
|
||||||
|
s = []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Type, o.Text, o.Path, "\r\n", stream}
|
||||||
|
}
|
||||||
|
if _, err := f.WriteString(strings.Join(s, " ")); err != nil {
|
||||||
|
p.fatal(err, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if msg != "" {
|
||||||
|
log.Print(msg)
|
||||||
|
}
|
||||||
|
if stream != "" {
|
||||||
|
fmt.Fprint(output, stream)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
p.parent.sync <- "sync"
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routines launches the toolchain run, build, install
|
||||||
|
func (p *Project) routines(stop <-chan bool, watcher FileWatcher, path string) {
|
||||||
|
var done bool
|
||||||
|
var install, build error
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
done = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if !done {
|
||||||
|
// before command
|
||||||
|
p.cmd(stop, "before", false)
|
||||||
|
}
|
||||||
|
if !done {
|
||||||
|
// Go supported tools
|
||||||
|
p.tool(stop, path)
|
||||||
|
// Prevent fake events on polling startup
|
||||||
|
p.init = true
|
||||||
|
}
|
||||||
|
// prevent errors using realize without config with only run flag
|
||||||
|
if !p.Cmds.Install.Status && !p.Cmds.Build.Status {
|
||||||
|
p.Cmds.Install.Status = true
|
||||||
|
}
|
||||||
|
if !done {
|
||||||
|
install = p.compile(stop, p.Cmds.Install)
|
||||||
|
}
|
||||||
|
if !done {
|
||||||
|
build = p.compile(stop, p.Cmds.Build)
|
||||||
|
}
|
||||||
|
if !done && (install == nil && build == nil) {
|
||||||
|
if p.Cmds.Run {
|
||||||
|
start := time.Now()
|
||||||
|
runner := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
log.Println(p.pname(p.Name, 1), ":", "Running..")
|
||||||
|
p.goRun(stop, runner)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-runner:
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", green.regular("Started"), "in", magenta.regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s"))
|
||||||
|
out = BufferOut{Time: time.Now(), Text: "Started in " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"}
|
||||||
|
p.stamp("log", out, msg, "")
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !done {
|
||||||
|
p.cmd(stop, "after", false)
|
||||||
|
}
|
||||||
|
}
|
193
watcher/cmd.go
193
watcher/cmd.go
|
@ -1,193 +0,0 @@
|
||||||
package watcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/tockins/realize/style"
|
|
||||||
cli "gopkg.in/urfave/cli.v2"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Run launches the toolchain for each project
|
|
||||||
func (h *Blueprint) Run(p *cli.Context) error {
|
|
||||||
err := h.check()
|
|
||||||
if err == nil {
|
|
||||||
// loop projects
|
|
||||||
wg.Add(len(h.Projects))
|
|
||||||
for k, element := range h.Projects {
|
|
||||||
if p.String("name") != "" && h.Projects[k].Name != p.String("name") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tools := tools{}
|
|
||||||
if element.Cmds.Fmt {
|
|
||||||
tools.Fmt = tool{
|
|
||||||
status: &h.Projects[k].Cmds.Fmt,
|
|
||||||
cmd: "gofmt",
|
|
||||||
options: []string{"-s", "-w", "-e"},
|
|
||||||
name: "Go Fmt",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if element.Cmds.Generate {
|
|
||||||
tools.Generate = tool{
|
|
||||||
status: &h.Projects[k].Cmds.Generate,
|
|
||||||
cmd: "go",
|
|
||||||
options: []string{"generate"},
|
|
||||||
name: "Go Generate",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if element.Cmds.Test {
|
|
||||||
tools.Test = tool{
|
|
||||||
status: &h.Projects[k].Cmds.Test,
|
|
||||||
cmd: "go",
|
|
||||||
options: []string{"test"},
|
|
||||||
name: "Go Test",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if element.Cmds.Vet {
|
|
||||||
tools.Vet = tool{
|
|
||||||
status: &h.Projects[k].Cmds.Vet,
|
|
||||||
cmd: "go",
|
|
||||||
options: []string{"vet"},
|
|
||||||
name: "Go Vet",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.Projects[k].tools = tools
|
|
||||||
h.Projects[k].parent = h
|
|
||||||
h.Projects[k].path = h.Projects[k].Path
|
|
||||||
|
|
||||||
// env variables
|
|
||||||
for key, item := range h.Projects[k].Environment {
|
|
||||||
if err := os.Setenv(key, item); err != nil {
|
|
||||||
h.Projects[k].Buffer.StdErr = append(h.Projects[k].Buffer.StdErr, BufferOut{Time: time.Now(), Text: err.Error(), Type: "Env error", Stream: ""})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if h.Legacy.Status {
|
|
||||||
go h.Projects[k].watchByPolling()
|
|
||||||
} else {
|
|
||||||
go h.Projects[k].watchByNotify()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new project
|
|
||||||
func (h *Blueprint) Add(p *cli.Context) error {
|
|
||||||
project := Project{
|
|
||||||
Name: h.Name(p.String("name"), p.String("path")),
|
|
||||||
Path: h.Path(p.String("path")),
|
|
||||||
Cmds: Cmds{
|
|
||||||
Vet: p.Bool("vet"),
|
|
||||||
Fmt: !p.Bool("no-fmt"),
|
|
||||||
Test: p.Bool("test"),
|
|
||||||
Generate: p.Bool("generate"),
|
|
||||||
Build: Cmd{
|
|
||||||
Status: p.Bool("build"),
|
|
||||||
},
|
|
||||||
Bin: Cmd{
|
|
||||||
Status: !p.Bool("no-bin"),
|
|
||||||
},
|
|
||||||
Run: !p.Bool("no-run"),
|
|
||||||
},
|
|
||||||
Args: argsParam(p),
|
|
||||||
Watcher: Watcher{
|
|
||||||
Paths: []string{"/"},
|
|
||||||
Ignore: []string{"vendor"},
|
|
||||||
Exts: []string{".go"},
|
|
||||||
Preview: p.Bool("preview"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if _, err := duplicates(project, h.Projects); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h.Projects = append(h.Projects, project)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean duplicate projects
|
|
||||||
func (h *Blueprint) Clean() {
|
|
||||||
arr := h.Projects
|
|
||||||
for key, val := range arr {
|
|
||||||
if _, err := duplicates(val, arr[key+1:]); err != nil {
|
|
||||||
h.Projects = append(arr[:key], arr[key+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a project
|
|
||||||
func (h *Blueprint) Remove(p *cli.Context) error {
|
|
||||||
for key, val := range h.Projects {
|
|
||||||
if p.String("name") == val.Name {
|
|
||||||
h.Projects = append(h.Projects[:key], h.Projects[key+1:]...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New("no project found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// List of all the projects
|
|
||||||
func (h *Blueprint) List() error {
|
|
||||||
err := h.check()
|
|
||||||
if err == nil {
|
|
||||||
for _, val := range h.Projects {
|
|
||||||
fmt.Println(style.Blue.Bold("[") + strings.ToUpper(val.Name) + style.Blue.Bold("]"))
|
|
||||||
name := style.Magenta.Bold("[") + strings.ToUpper(val.Name) + style.Magenta.Bold("]")
|
|
||||||
|
|
||||||
fmt.Println(name, style.Yellow.Regular("Base Path"), ":", style.Magenta.Regular(val.Path))
|
|
||||||
fmt.Println(name, style.Yellow.Regular("Fmt"), ":", style.Magenta.Regular(val.Cmds.Fmt))
|
|
||||||
fmt.Println(name, style.Yellow.Regular("Generate"), ":", style.Magenta.Regular(val.Cmds.Generate))
|
|
||||||
fmt.Println(name, style.Yellow.Regular("Test"), ":", style.Magenta.Regular(val.Cmds.Test))
|
|
||||||
fmt.Println(name, style.Yellow.Regular("Install"), ":", style.Magenta.Regular(val.Cmds.Bin))
|
|
||||||
fmt.Println(name, style.Yellow.Regular("Build"), ":", style.Magenta.Regular(val.Cmds.Build))
|
|
||||||
fmt.Println(name, style.Yellow.Regular("Run"), ":", style.Magenta.Regular(val.Cmds.Run))
|
|
||||||
if len(val.Args) > 0 {
|
|
||||||
fmt.Println(name, style.Yellow.Regular("Params"), ":", style.Magenta.Regular(val.Args))
|
|
||||||
}
|
|
||||||
fmt.Println(name, style.Yellow.Regular("Watcher"), ":")
|
|
||||||
fmt.Println(name, "\t", style.Yellow.Regular("Preview"), ":", style.Magenta.Regular(val.Watcher.Preview))
|
|
||||||
if len(val.Watcher.Exts) > 0 {
|
|
||||||
fmt.Println(name, "\t", style.Yellow.Regular("Extensions"), ":", style.Magenta.Regular(val.Watcher.Exts))
|
|
||||||
}
|
|
||||||
if len(val.Watcher.Paths) > 0 {
|
|
||||||
fmt.Println(name, "\t", style.Yellow.Regular("Paths"), ":", style.Magenta.Regular(val.Watcher.Paths))
|
|
||||||
}
|
|
||||||
if len(val.Watcher.Ignore) > 0 {
|
|
||||||
fmt.Println(name, "\t", style.Yellow.Regular("Ignored paths"), ":", style.Magenta.Regular(val.Watcher.Ignore))
|
|
||||||
}
|
|
||||||
if len(val.Watcher.Scripts) > 0 {
|
|
||||||
fmt.Println(name, "\t", style.Yellow.Regular("Scripts"), ":")
|
|
||||||
for _, v := range val.Watcher.Scripts {
|
|
||||||
if v.Command != "" {
|
|
||||||
fmt.Println(name, "\t\t", style.Magenta.Regular("-"), style.Yellow.Regular("Command"), ":", style.Magenta.Regular(v.Command))
|
|
||||||
if v.Path != "" {
|
|
||||||
fmt.Println(name, "\t\t", style.Yellow.Regular("Path"), ":", style.Magenta.Regular(v.Path))
|
|
||||||
}
|
|
||||||
if v.Type != "" {
|
|
||||||
fmt.Println(name, "\t\t", style.Yellow.Regular("Type"), ":", style.Magenta.Regular(v.Type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println(name, style.Yellow.Regular("Streams"), ":")
|
|
||||||
fmt.Println(name, "\t", style.Yellow.Regular("File Out"), ":", style.Magenta.Regular(val.Streams.FileOut))
|
|
||||||
fmt.Println(name, "\t", style.Yellow.Regular("File Log"), ":", style.Magenta.Regular(val.Streams.FileLog))
|
|
||||||
fmt.Println(name, "\t", style.Yellow.Regular("File Err"), ":", style.Magenta.Regular(val.Streams.FileErr))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether there is a project
|
|
||||||
func (h *Blueprint) check() error {
|
|
||||||
if len(h.Projects) > 0 {
|
|
||||||
h.Clean()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("There are no projects")
|
|
||||||
}
|
|
192
watcher/exec.go
192
watcher/exec.go
|
@ -1,192 +0,0 @@
|
||||||
package watcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"github.com/tockins/realize/style"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GoRun is an implementation of the bin execution
|
|
||||||
func (p *Project) goRun(channel chan bool, runner chan bool, wr *sync.WaitGroup) error {
|
|
||||||
var build *exec.Cmd
|
|
||||||
var args []string
|
|
||||||
var path = ""
|
|
||||||
isErrorText := func(string) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
errRegexp, err := regexp.Compile(p.ErrorOutputPattern)
|
|
||||||
if err != nil {
|
|
||||||
msg := fmt.Sprintln(p.pname(p.Name, 3), ":", style.Blue.Regular(err.Error()))
|
|
||||||
out := BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Run"}
|
|
||||||
p.print("error", out, msg, "")
|
|
||||||
} else {
|
|
||||||
isErrorText = func(t string) bool {
|
|
||||||
return errRegexp.MatchString(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, arg := range p.Args {
|
|
||||||
arr := strings.Fields(arg)
|
|
||||||
args = append(args, arr...)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(filepath.Join(p.base, p.path)); err == nil {
|
|
||||||
path = filepath.Join(p.base, p.path)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(filepath.Join(p.base, p.path+".exe")); err == nil {
|
|
||||||
path = filepath.Join(p.base, p.path+".exe")
|
|
||||||
}
|
|
||||||
|
|
||||||
if path != "" {
|
|
||||||
build = exec.Command(path, args...)
|
|
||||||
} else {
|
|
||||||
if _, err := os.Stat(filepath.Join(getEnvPath("GOBIN"), filepath.Base(p.path))); err == nil {
|
|
||||||
build = exec.Command(filepath.Join(getEnvPath("GOBIN"), filepath.Base(p.path)), args...)
|
|
||||||
} else if _, err := os.Stat(filepath.Join(getEnvPath("GOBIN"), filepath.Base(p.path)) + ".exe"); err == nil {
|
|
||||||
build = exec.Command(filepath.Join(getEnvPath("GOBIN"), filepath.Base(p.path))+".exe", args...)
|
|
||||||
} else {
|
|
||||||
p.Buffer.StdLog = append(p.Buffer.StdLog, BufferOut{Time: time.Now(), Text: "Can't run a not compiled project"})
|
|
||||||
p.Fatal(err, "Can't run a not compiled project", ":")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := build.Process.Kill(); err != nil {
|
|
||||||
p.Buffer.StdLog = append(p.Buffer.StdLog, BufferOut{Time: time.Now(), Text: "Failed to stop: " + err.Error()})
|
|
||||||
p.Fatal(err, "Failed to stop", ":")
|
|
||||||
}
|
|
||||||
msg := fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Regular("Ended"))
|
|
||||||
out := BufferOut{Time: time.Now(), Text: "Ended", Type: "Go Run"}
|
|
||||||
p.print("log", out, msg, "")
|
|
||||||
wr.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
stdout, err := build.StdoutPipe()
|
|
||||||
stderr, err := build.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(style.Red.Bold(err.Error()))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := build.Start(); err != nil {
|
|
||||||
log.Println(style.Red.Bold(err.Error()))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
close(runner)
|
|
||||||
|
|
||||||
execOutput, execError := bufio.NewScanner(stdout), bufio.NewScanner(stderr)
|
|
||||||
stopOutput, stopError := make(chan bool, 1), make(chan bool, 1)
|
|
||||||
scanner := func(stop chan bool, output *bufio.Scanner, isError bool) {
|
|
||||||
for output.Scan() {
|
|
||||||
text := output.Text()
|
|
||||||
msg := fmt.Sprintln(p.pname(p.Name, 3), ":", style.Blue.Regular(text))
|
|
||||||
if isError && !isErrorText(text) {
|
|
||||||
out := BufferOut{Time: time.Now(), Text: text, Type: "Go Run"}
|
|
||||||
p.print("error", out, msg, "")
|
|
||||||
} else {
|
|
||||||
out := BufferOut{Time: time.Now(), Text: text, Type: "Go Run"}
|
|
||||||
p.print("out", out, msg, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(stop)
|
|
||||||
}
|
|
||||||
go scanner(stopOutput, execOutput, false)
|
|
||||||
go scanner(stopError, execError, true)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-channel:
|
|
||||||
return nil
|
|
||||||
case <-stopOutput:
|
|
||||||
return nil
|
|
||||||
case <-stopError:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GoBuild is an implementation of the "go build"
|
|
||||||
func (p *Project) goBuild() (string, error) {
|
|
||||||
var out bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
args := []string{"build"}
|
|
||||||
for _, arg := range p.Cmds.Build.Args {
|
|
||||||
arr := strings.Fields(arg)
|
|
||||||
args = append(args, arr...)
|
|
||||||
}
|
|
||||||
build := exec.Command("go", args...)
|
|
||||||
build.Dir = p.base
|
|
||||||
build.Stdout = &out
|
|
||||||
build.Stderr = &stderr
|
|
||||||
if err := build.Run(); err != nil {
|
|
||||||
return stderr.String(), err
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GoInstall is an implementation of the "go install"
|
|
||||||
func (p *Project) goInstall() (string, error) {
|
|
||||||
var out bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
err := os.Setenv("GOBIN", filepath.Join(getEnvPath("GOPATH"), "bin"))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
args := []string{"install"}
|
|
||||||
for _, arg := range p.Cmds.Bin.Args {
|
|
||||||
arr := strings.Fields(arg)
|
|
||||||
args = append(args, arr...)
|
|
||||||
}
|
|
||||||
build := exec.Command("go", args...)
|
|
||||||
build.Dir = p.base
|
|
||||||
build.Stdout = &out
|
|
||||||
build.Stderr = &stderr
|
|
||||||
if err := build.Run(); err != nil {
|
|
||||||
return stderr.String(), err
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GoTools is used for run go methods such as fmt, test, generate...
|
|
||||||
func (p *Project) goTools(dir string, name string, cmd ...string) (string, error) {
|
|
||||||
var out, stderr bytes.Buffer
|
|
||||||
build := exec.Command(name, cmd...)
|
|
||||||
build.Dir = dir
|
|
||||||
build.Stdout = &out
|
|
||||||
build.Stderr = &stderr
|
|
||||||
if err := build.Run(); err != nil {
|
|
||||||
return stderr.String(), err
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec an additional command from a defined path if specified
|
|
||||||
func (p *Project) command(cmd Command) (errors string, logs string) {
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
command := strings.Replace(strings.Replace(cmd.Command, "'", "", -1), "\"", "", -1)
|
|
||||||
c := strings.Split(command, " ")
|
|
||||||
build := exec.Command(c[0], c[1:]...)
|
|
||||||
build.Dir = p.base
|
|
||||||
if cmd.Path != "" {
|
|
||||||
if strings.Contains(cmd.Path, p.base) {
|
|
||||||
build.Dir = cmd.Path
|
|
||||||
} else {
|
|
||||||
build.Dir = filepath.Join(p.base, cmd.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
build.Stdout = &stdout
|
|
||||||
build.Stderr = &stderr
|
|
||||||
err := build.Run()
|
|
||||||
// check if log
|
|
||||||
logs = stdout.String()
|
|
||||||
if err != nil {
|
|
||||||
errors = stderr.String()
|
|
||||||
return errors, logs
|
|
||||||
}
|
|
||||||
return "", logs
|
|
||||||
}
|
|
125
watcher/main.go
125
watcher/main.go
|
@ -1,125 +0,0 @@
|
||||||
package watcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/tockins/realize/settings"
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
// Watcher interface used by polling/fsnotify watching
|
|
||||||
type watcher interface {
|
|
||||||
Add(path string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Polling watcher
|
|
||||||
type pollWatcher struct {
|
|
||||||
paths map[string]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log struct
|
|
||||||
type logWriter struct{}
|
|
||||||
|
|
||||||
// Blueprint struct contains a projects list
|
|
||||||
type Blueprint struct {
|
|
||||||
*settings.Settings `yaml:"-"`
|
|
||||||
Projects []Project `yaml:"projects,omitempty" json:"projects,omitempty"`
|
|
||||||
Sync chan string `yaml:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Project defines the informations of a single project
|
|
||||||
type Project struct {
|
|
||||||
settings.Settings `yaml:"-"`
|
|
||||||
LastChangedOn time.Time `yaml:"-" json:"-"`
|
|
||||||
base string
|
|
||||||
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 Watcher `yaml:"watcher" json:"watcher"`
|
|
||||||
Streams Streams `yaml:"streams,omitempty" json:"streams,omitempty"`
|
|
||||||
Buffer Buffer `yaml:"-" json:"buffer"`
|
|
||||||
ErrorOutputPattern string `yaml:"errorOutputPattern,omitempty" json:"errorOutputPattern,omitempty"`
|
|
||||||
parent *Blueprint
|
|
||||||
path string
|
|
||||||
tools tools
|
|
||||||
}
|
|
||||||
|
|
||||||
type tools struct {
|
|
||||||
Fmt, Test, Generate, Vet tool
|
|
||||||
}
|
|
||||||
|
|
||||||
type tool struct {
|
|
||||||
status *bool
|
|
||||||
cmd string
|
|
||||||
options []string
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cmds go supported
|
|
||||||
type Cmds struct {
|
|
||||||
Vet bool `yaml:"vet" json:"vet"`
|
|
||||||
Fmt bool `yaml:"fmt" json:"fmt"`
|
|
||||||
Test bool `yaml:"test" json:"test"`
|
|
||||||
Generate bool `yaml:"generate" json:"generate"`
|
|
||||||
Bin Cmd `yaml:"bin" json:"bin"`
|
|
||||||
Build Cmd `yaml:"build" json:"build"`
|
|
||||||
Run bool `yaml:"run" json:"run"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cmd buildmode options
|
|
||||||
type Cmd struct {
|
|
||||||
Status bool `yaml:"status" json:"status"`
|
|
||||||
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watcher struct defines the livereload's logic
|
|
||||||
type Watcher struct {
|
|
||||||
Preview bool `yaml:"preview" json:"preview"`
|
|
||||||
Paths []string `yaml:"paths" json:"paths"`
|
|
||||||
Ignore []string `yaml:"ignore_paths" json:"ignore"`
|
|
||||||
Exts []string `yaml:"exts" json:"exts"`
|
|
||||||
Scripts []Command `yaml:"scripts,omitempty" json:"scripts,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command options
|
|
||||||
type Command struct {
|
|
||||||
Type string `yaml:"type" json:"type"`
|
|
||||||
Command string `yaml:"command" json:"command"`
|
|
||||||
Path string `yaml:"path,omitempty" json:"path,omitempty"`
|
|
||||||
Changed bool `yaml:"changed,omitempty" json:"changed,omitempty"`
|
|
||||||
Startup bool `yaml:"startup,omitempty" json:"startup,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Streams is a collection of names and values for the logs functionality
|
|
||||||
type Streams struct {
|
|
||||||
FileOut bool `yaml:"file_out" json:"file_out"`
|
|
||||||
FileLog bool `yaml:"file_log" json:"file_log"`
|
|
||||||
FileErr bool `yaml:"file_err" json:"file_err"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffer define an array buffer for each log files
|
|
||||||
type Buffer struct {
|
|
||||||
StdOut []BufferOut `json:"stdOut"`
|
|
||||||
StdLog []BufferOut `json:"stdLog"`
|
|
||||||
StdErr []BufferOut `json:"stdErr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufferOut is used for exchange information between "realize cli" and "web realize"
|
|
||||||
type BufferOut struct {
|
|
||||||
Time time.Time `json:"time"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Stream string `json:"stream"`
|
|
||||||
Errors []string `json:"errors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the application
|
|
||||||
func init() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
log.SetOutput(new(logWriter))
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package watcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tockins/realize/style"
|
|
||||||
cli "gopkg.in/urfave/cli.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Argsparam parse one by one the given argumentes
|
|
||||||
func argsParam(params *cli.Context) []string {
|
|
||||||
argsN := params.NArg()
|
|
||||||
if argsN > 0 {
|
|
||||||
var args []string
|
|
||||||
for i := 0; i <= argsN-1; i++ {
|
|
||||||
args = append(args, params.Args().Get(i))
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 && val.Name == value.Name {
|
|
||||||
return val, errors.New("There is already a project for '" + val.Path + "'. Check your config file!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Project{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a string is inArray
|
|
||||||
func inArray(str string, list []string) bool {
|
|
||||||
for _, v := range list {
|
|
||||||
if v == str {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rewrite the layout of the log timestamp
|
|
||||||
func (w logWriter) Write(bytes []byte) (int, error) {
|
|
||||||
return fmt.Print(style.Yellow.Regular("[") + time.Now().Format("15:04:05") + style.Yellow.Regular("]") + string(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// getEnvPath returns the first path found in env or empty string
|
|
||||||
func getEnvPath(env string) string {
|
|
||||||
path := filepath.SplitList(os.Getenv(env))
|
|
||||||
if len(path) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return path[0]
|
|
||||||
}
|
|
|
@ -1,422 +0,0 @@
|
||||||
package watcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
"github.com/tockins/realize/style"
|
|
||||||
"log"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var msg string
|
|
||||||
var out BufferOut
|
|
||||||
|
|
||||||
// Add a path to paths list
|
|
||||||
func (w *pollWatcher) Add(path string) error {
|
|
||||||
if w.paths == nil {
|
|
||||||
w.paths = map[string]bool{}
|
|
||||||
}
|
|
||||||
w.paths[path] = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if is watching
|
|
||||||
func (w *pollWatcher) isWatching(path string) bool {
|
|
||||||
a, b := w.paths[path]
|
|
||||||
return a && b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch the project by polling
|
|
||||||
func (p *Project) watchByPolling() {
|
|
||||||
var wr sync.WaitGroup
|
|
||||||
var watcher = new(pollWatcher)
|
|
||||||
channel, exit := make(chan bool, 1), make(chan os.Signal, 2)
|
|
||||||
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
|
|
||||||
defer func() {
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
go p.routines(&wr, channel, watcher, "")
|
|
||||||
p.LastChangedOn = time.Now().Truncate(time.Second)
|
|
||||||
walk := func(changed string, info os.FileInfo, err error) error {
|
|
||||||
var ext string
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if !watcher.isWatching(changed) {
|
|
||||||
return nil
|
|
||||||
} else if !info.ModTime().Truncate(time.Second).After(p.LastChangedOn) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if index := strings.Index(filepath.Ext(changed), "_"); index == -1 {
|
|
||||||
ext = filepath.Ext(changed)
|
|
||||||
} else {
|
|
||||||
ext = filepath.Ext(changed)[0:index]
|
|
||||||
}
|
|
||||||
i := strings.Index(changed, filepath.Ext(changed))
|
|
||||||
file := changed[:i] + ext
|
|
||||||
if changed[:i] != "" && inArray(ext, p.Watcher.Exts) {
|
|
||||||
if p.Cmds.Run {
|
|
||||||
close(channel)
|
|
||||||
channel = make(chan bool)
|
|
||||||
}
|
|
||||||
p.LastChangedOn = time.Now().Truncate(time.Second)
|
|
||||||
// repeat the initial cycle
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 4), ":", style.Magenta.Bold(strings.ToUpper(ext[1:])+" changed"), style.Magenta.Bold(file))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: strings.ToUpper(ext[1:]) + " changed " + file}
|
|
||||||
p.print("log", out, msg, "")
|
|
||||||
go p.routines(&wr, channel, watcher, file)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
for _, dir := range p.Watcher.Paths {
|
|
||||||
base := filepath.Join(p.base, dir)
|
|
||||||
if _, err := os.Stat(base); err == nil {
|
|
||||||
if err := filepath.Walk(base, walk); err != nil {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Regular(err.Error()))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: err.Error()}
|
|
||||||
p.print("error", out, msg, "")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", base, "path doesn't exist")
|
|
||||||
out = BufferOut{Time: time.Now(), Text: base + " path doesn't exist"}
|
|
||||||
p.print("error", out, msg, "")
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-exit:
|
|
||||||
p.cmd("after", false)
|
|
||||||
return
|
|
||||||
case <-time.After(p.parent.Legacy.Interval / time.Duration(len(p.Watcher.Paths))):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch the project by fsnotify
|
|
||||||
func (p *Project) watchByNotify() {
|
|
||||||
var wr sync.WaitGroup
|
|
||||||
var watcher *fsnotify.Watcher
|
|
||||||
channel, exit := make(chan bool, 1), make(chan os.Signal, 2)
|
|
||||||
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
|
||||||
p.Fatal(err)
|
|
||||||
defer func() {
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
go p.routines(&wr, channel, watcher, "")
|
|
||||||
p.LastChangedOn = time.Now().Truncate(time.Second)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <-watcher.Events:
|
|
||||||
if time.Now().Truncate(time.Second).After(p.LastChangedOn) {
|
|
||||||
if event.Op&fsnotify.Chmod == fsnotify.Chmod {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(event.Name); err == nil {
|
|
||||||
var ext string
|
|
||||||
if index := strings.Index(filepath.Ext(event.Name), "_"); index == -1 {
|
|
||||||
ext = filepath.Ext(event.Name)
|
|
||||||
} else {
|
|
||||||
ext = filepath.Ext(event.Name)[0:index]
|
|
||||||
}
|
|
||||||
i := strings.Index(event.Name, filepath.Ext(event.Name))
|
|
||||||
file := event.Name[:i] + ext
|
|
||||||
if event.Name[:i] != "" && inArray(ext, p.Watcher.Exts) {
|
|
||||||
if p.Cmds.Run {
|
|
||||||
close(channel)
|
|
||||||
channel = make(chan bool)
|
|
||||||
}
|
|
||||||
// repeat the initial cycle
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 4), ":", style.Magenta.Bold(strings.ToUpper(ext[1:])+" changed"), style.Magenta.Bold(file))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: strings.ToUpper(ext[1:]) + " changed " + file}
|
|
||||||
p.print("log", out, msg, "")
|
|
||||||
go p.routines(&wr, channel, watcher, file)
|
|
||||||
p.LastChangedOn = time.Now().Truncate(time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case err := <-watcher.Errors:
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Regular(err.Error()))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: err.Error()}
|
|
||||||
p.print("error", out, msg, "")
|
|
||||||
case <-exit:
|
|
||||||
p.cmd("after", false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch the files tree of a project
|
|
||||||
func (p *Project) watch(watcher watcher) error {
|
|
||||||
var files, folders int64
|
|
||||||
wd, _ := os.Getwd()
|
|
||||||
walk := func(path string, info os.FileInfo, err error) error {
|
|
||||||
if !p.ignore(path) {
|
|
||||||
if (info.IsDir() && len(filepath.Ext(path)) == 0 && !strings.HasPrefix(path, ".")) && !strings.Contains(path, "/.") || (inArray(filepath.Ext(path), p.Watcher.Exts)) {
|
|
||||||
if p.Watcher.Preview {
|
|
||||||
log.Println(p.pname(p.Name, 1), ":", path)
|
|
||||||
}
|
|
||||||
if err = watcher.Add(path); err != nil {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
if inArray(filepath.Ext(path), p.Watcher.Exts) {
|
|
||||||
files++
|
|
||||||
p.tool(path, p.tools.Fmt)
|
|
||||||
} else {
|
|
||||||
folders++
|
|
||||||
p.tool(path, p.tools.Vet)
|
|
||||||
p.tool(path, p.tools.Test)
|
|
||||||
p.tool(path, p.tools.Generate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if p.path == "." || p.path == "/" {
|
|
||||||
p.base = wd
|
|
||||||
p.path = p.Wdir()
|
|
||||||
} else if filepath.IsAbs(p.path) {
|
|
||||||
p.base = p.path
|
|
||||||
} else {
|
|
||||||
p.base = filepath.Join(wd, p.path)
|
|
||||||
}
|
|
||||||
for _, dir := range p.Watcher.Paths {
|
|
||||||
base := filepath.Join(p.base, dir)
|
|
||||||
if _, err := os.Stat(base); err == nil {
|
|
||||||
if err := filepath.Walk(base, walk); err != nil {
|
|
||||||
log.Println(style.Red.Bold(err.Error()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.New(base + " path doesn't exist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 1), ":", style.Blue.Bold("Watching"), style.Magenta.Bold(files), "file/s", style.Magenta.Bold(folders), "folder/s")
|
|
||||||
out = BufferOut{Time: time.Now(), Text: "Watching " + strconv.FormatInt(files, 10) + " files/s " + strconv.FormatInt(folders, 10) + " folder/s"}
|
|
||||||
p.print("log", out, msg, "")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install calls an implementation of "go install"
|
|
||||||
func (p *Project) install() error {
|
|
||||||
if p.Cmds.Bin.Status {
|
|
||||||
start := time.Now()
|
|
||||||
log.Println(p.pname(p.Name, 1), ":", "Installing..")
|
|
||||||
stream, err := p.goInstall()
|
|
||||||
if err != nil {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Bold("Go Install"), style.Red.Regular(err.Error()))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Install", Stream: stream}
|
|
||||||
p.print("error", out, msg, stream)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", style.Green.Regular("Installed")+" after", style.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s"))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: "Installed after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"}
|
|
||||||
p.print("log", out, msg, stream)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install calls an implementation of "go run"
|
|
||||||
func (p *Project) run(channel chan bool, wr *sync.WaitGroup) {
|
|
||||||
if p.Cmds.Run {
|
|
||||||
start := time.Now()
|
|
||||||
runner := make(chan bool, 1)
|
|
||||||
log.Println(p.pname(p.Name, 1), ":", "Running..")
|
|
||||||
go p.goRun(channel, runner, wr)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-runner:
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", style.Green.Regular("Started")+" after", style.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s"))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: "Started after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"}
|
|
||||||
p.print("log", out, msg, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build calls an implementation of the "go build"
|
|
||||||
func (p *Project) build() error {
|
|
||||||
if p.Cmds.Build.Status {
|
|
||||||
start := time.Now()
|
|
||||||
log.Println(p.pname(p.Name, 1), ":", "Building..")
|
|
||||||
stream, err := p.goBuild()
|
|
||||||
if err != nil {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Bold("Go Build"), style.Red.Regular(err.Error()))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Build", Stream: stream}
|
|
||||||
p.print("error", out, msg, stream)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", style.Green.Regular("Built")+" after", style.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s"))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: "Built after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"}
|
|
||||||
p.print("log", out, msg, stream)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Project) tool(path string, tool tool) error {
|
|
||||||
if tool.status != nil {
|
|
||||||
v := reflect.ValueOf(tool.status).Elem()
|
|
||||||
if v.Interface().(bool) && (strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "")) {
|
|
||||||
if strings.HasSuffix(path, ".go") {
|
|
||||||
tool.options = append(tool.options, path)
|
|
||||||
path = p.base
|
|
||||||
}
|
|
||||||
if stream, err := p.goTools(path, tool.cmd, tool.options...); err != nil {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Bold(tool.name), style.Red.Regular("there are some errors in"), ":", style.Magenta.Bold(path))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: "there are some errors in", Path: path, Type: tool.name, Stream: stream}
|
|
||||||
p.print("error", out, msg, stream)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cmd calls an wrapper for execute the commands after/before
|
|
||||||
func (p *Project) cmd(flag string, changed bool) {
|
|
||||||
for _, cmd := range p.Watcher.Scripts {
|
|
||||||
if strings.ToLower(cmd.Type) == flag {
|
|
||||||
if changed && cmd.Changed || !changed && cmd.Startup {
|
|
||||||
errors, logs := p.command(cmd)
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", style.Green.Bold("Command"), style.Green.Bold("\"")+cmd.Command+style.Green.Bold("\""))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: cmd.Command, Type: flag}
|
|
||||||
if logs != "" {
|
|
||||||
p.print("log", out, msg, "")
|
|
||||||
}
|
|
||||||
if errors != "" {
|
|
||||||
p.print("error", out, msg, "")
|
|
||||||
}
|
|
||||||
if logs != "" {
|
|
||||||
msg = fmt.Sprintln(logs)
|
|
||||||
out = BufferOut{Time: time.Now(), Text: logs, Type: flag}
|
|
||||||
p.print("log", out, "", msg)
|
|
||||||
}
|
|
||||||
if errors != "" {
|
|
||||||
msg = fmt.Sprintln(style.Red.Regular(errors))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: errors, Type: flag}
|
|
||||||
p.print("error", out, "", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore and validate a path
|
|
||||||
func (p *Project) ignore(str string) bool {
|
|
||||||
for _, v := range p.Watcher.Ignore {
|
|
||||||
if strings.Contains(str, filepath.Join(p.base, v)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routines launches the toolchain run, build, install
|
|
||||||
func (p *Project) routines(wr *sync.WaitGroup, channel chan bool, watcher watcher, file string) {
|
|
||||||
if len(file) > 0 {
|
|
||||||
p.cmd("before", true)
|
|
||||||
path := filepath.Dir(file)
|
|
||||||
p.tool(file, p.tools.Fmt)
|
|
||||||
p.tool(path, p.tools.Vet)
|
|
||||||
p.tool(path, p.tools.Test)
|
|
||||||
p.tool(path, p.tools.Generate)
|
|
||||||
} else {
|
|
||||||
p.cmd("before", false)
|
|
||||||
p.Fatal(p.watch(watcher))
|
|
||||||
}
|
|
||||||
install := p.install()
|
|
||||||
build := p.build()
|
|
||||||
wr.Add(1)
|
|
||||||
if install == nil && build == nil {
|
|
||||||
go p.run(channel, wr)
|
|
||||||
}
|
|
||||||
wr.Wait()
|
|
||||||
if len(file) > 0 {
|
|
||||||
p.cmd("after", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the colors scheme for the project name
|
|
||||||
func (p *Project) pname(name string, color int) string {
|
|
||||||
switch color {
|
|
||||||
case 1:
|
|
||||||
name = style.Yellow.Regular("[") + strings.ToUpper(name) + style.Yellow.Regular("]")
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
name = style.Yellow.Regular("[") + style.Red.Bold(strings.ToUpper(name)) + style.Yellow.Regular("]")
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
name = style.Yellow.Regular("[") + style.Blue.Bold(strings.ToUpper(name)) + style.Yellow.Regular("]")
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
name = style.Yellow.Regular("[") + style.Magenta.Bold(strings.ToUpper(name)) + style.Yellow.Regular("]")
|
|
||||||
break
|
|
||||||
case 5:
|
|
||||||
name = style.Yellow.Regular("[") + style.Green.Bold(strings.ToUpper(name)) + style.Yellow.Regular("]")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print on files, cli, ws
|
|
||||||
func (p *Project) print(t string, o BufferOut, msg string, stream string) {
|
|
||||||
switch t {
|
|
||||||
case "out":
|
|
||||||
p.Buffer.StdOut = append(p.Buffer.StdOut, o)
|
|
||||||
if p.Streams.FileOut {
|
|
||||||
f := p.Create(p.base, p.parent.Resources.Outputs)
|
|
||||||
t := time.Now()
|
|
||||||
s := []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n"}
|
|
||||||
if _, err := f.WriteString(strings.Join(s, " ")); err != nil {
|
|
||||||
p.Fatal(err, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "log":
|
|
||||||
p.Buffer.StdLog = append(p.Buffer.StdLog, o)
|
|
||||||
if p.Streams.FileLog {
|
|
||||||
f := p.Create(p.base, p.parent.Resources.Logs)
|
|
||||||
t := time.Now()
|
|
||||||
s := []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n"}
|
|
||||||
if stream != "" {
|
|
||||||
s = []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n", stream}
|
|
||||||
}
|
|
||||||
if _, err := f.WriteString(strings.Join(s, " ")); err != nil {
|
|
||||||
p.Fatal(err, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "error":
|
|
||||||
p.Buffer.StdErr = append(p.Buffer.StdErr, o)
|
|
||||||
if p.Streams.FileErr {
|
|
||||||
f := p.Create(p.base, p.parent.Resources.Errors)
|
|
||||||
t := time.Now()
|
|
||||||
s := []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Type, o.Text, o.Path, "\r\n"}
|
|
||||||
if stream != "" {
|
|
||||||
s = []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Type, o.Text, o.Path, "\r\n", stream}
|
|
||||||
}
|
|
||||||
if _, err := f.WriteString(strings.Join(s, " ")); err != nil {
|
|
||||||
p.Fatal(err, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if msg != "" {
|
|
||||||
log.Print(msg)
|
|
||||||
}
|
|
||||||
if stream != "" {
|
|
||||||
fmt.Print(stream)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
p.parent.Sync <- "sync"
|
|
||||||
}()
|
|
||||||
}
|
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
Loading…
Reference in New Issue