-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial restructure according to #21
Created packages for each component and tied them together in the main class
- Loading branch information
1 parent
7f88297
commit a84f25f
Showing
6 changed files
with
586 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package builder | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"fmt" | ||
"os" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/Tonkpils/snag/exchange" | ||
"github.com/Tonkpils/snag/vow" | ||
"github.com/shiena/ansicolor" | ||
) | ||
|
||
var clearBuffer = func() { | ||
fmt.Print("\033c") | ||
} | ||
|
||
type Config struct { | ||
Build []string | ||
Run []string | ||
DepWarning string | ||
Verbose bool | ||
} | ||
|
||
type Builder struct { | ||
ex *exchange.Exchange | ||
mtx sync.RWMutex | ||
depWarning string | ||
buildCmds [][]string | ||
runCmds [][]string | ||
curVow *vow.Vow | ||
|
||
verbose bool | ||
} | ||
|
||
func New(ex *exchange.Exchange, c Config) *Builder { | ||
parseCmd := func(cmd string) (c []string) { | ||
s := bufio.NewScanner(strings.NewReader(cmd)) | ||
s.Split(splitFunc) | ||
for s.Scan() { | ||
c = append(c, s.Text()) | ||
} | ||
|
||
// check for environment variables inside script | ||
if strings.Contains(cmd, "$$") { | ||
replaceEnv(c) | ||
} | ||
return c | ||
} | ||
|
||
buildCmds := make([][]string, len(c.Build)) | ||
for i, s := range c.Build { | ||
buildCmds[i] = parseCmd(s) | ||
} | ||
|
||
runCmds := make([][]string, len(c.Run)) | ||
for i, s := range c.Run { | ||
runCmds[i] = parseCmd(s) | ||
} | ||
|
||
return &Builder{ | ||
buildCmds: buildCmds, | ||
runCmds: runCmds, | ||
depWarning: c.DepWarning, | ||
verbose: c.Verbose, | ||
} | ||
} | ||
|
||
func splitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) { | ||
advance, token, err = bufio.ScanWords(data, atEOF) | ||
if err != nil { | ||
return | ||
} | ||
|
||
if len(token) == 0 { | ||
return | ||
} | ||
|
||
b := token[0] | ||
if b != '"' && b != '\'' { | ||
return | ||
} | ||
|
||
if token[len(token)-1] == b { | ||
return | ||
} | ||
|
||
chunk := data[advance-1:] | ||
i := bytes.IndexByte(chunk, b) | ||
if i == -1 { | ||
advance = len(data) | ||
token = append(token, chunk...) | ||
return | ||
} | ||
|
||
advance += i | ||
token = append(token, chunk[:i+1]...) | ||
|
||
return | ||
} | ||
|
||
func replaceEnv(cmds []string) { | ||
for i, c := range cmds { | ||
if !strings.HasPrefix(c, "$$") { | ||
continue | ||
} | ||
|
||
cmds[i] = os.Getenv(strings.TrimPrefix(c, "$$")) | ||
} | ||
} | ||
|
||
func (b *Builder) stopCurVow() { | ||
b.mtx.Lock() | ||
if b.curVow != nil { | ||
b.curVow.Stop() | ||
} | ||
b.mtx.Unlock() | ||
} | ||
|
||
func (b *Builder) Build(_ interface{}) { | ||
b.stopCurVow() | ||
|
||
clearBuffer() | ||
b.mtx.Lock() | ||
|
||
if len(b.depWarning) > 0 { | ||
fmt.Printf("Deprecation Warnings!\n%s", b.depWarning) | ||
} | ||
|
||
// setup the first command | ||
firstCmd := b.buildCmds[0] | ||
b.curVow = vow.To(firstCmd[0], firstCmd[1:]...) | ||
|
||
// setup the remaining commands | ||
for i := 1; i < len(b.buildCmds); i++ { | ||
cmd := b.buildCmds[i] | ||
b.curVow = b.curVow.Then(cmd[0], cmd[1:]...) | ||
} | ||
|
||
// setup all parallel commands | ||
for i := 0; i < len(b.runCmds); i++ { | ||
cmd := b.runCmds[i] | ||
b.curVow = b.curVow.ThenAsync(cmd[0], cmd[1:]...) | ||
} | ||
b.curVow.Verbose = b.verbose | ||
go b.curVow.Exec(ansicolor.NewAnsiColorWriter(os.Stdout)) | ||
|
||
b.mtx.Unlock() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package exchange | ||
|
||
import "sync" | ||
|
||
type queue []func(interface{}) | ||
|
||
type Exchange struct { | ||
mtx sync.RWMutex | ||
queues map[string]queue | ||
} | ||
|
||
func New() *Exchange { | ||
return &Exchange{ | ||
queues: map[string]queue{}, | ||
} | ||
} | ||
|
||
func (ex *Exchange) Listen(event string, fn func(interface{})) { | ||
ex.mtx.Lock() | ||
ex.queues[event] = append(ex.queues[event], fn) | ||
ex.mtx.Unlock() | ||
} | ||
|
||
func (ex *Exchange) Send(event string, data interface{}) { | ||
ex.mtx.RLock() | ||
for _, fn := range ex.queues[event] { | ||
fn(data) | ||
} | ||
ex.mtx.RUnlock() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package watcher | ||
|
||
import ( | ||
"log" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
const dblAsterisks = "**" | ||
|
||
func globMatch(pattern, value string) bool { | ||
// A blank line matches no files, so it can serve as a separator for readability. | ||
if pattern == "" { | ||
return false | ||
} | ||
|
||
// A line starting with # serves as a comment. Put a backslash ("\") in front of the first hash for patterns that begin with a hash. | ||
if strings.HasPrefix(pattern, "#") { | ||
return false | ||
} | ||
|
||
// Trailing spaces are ignored unless they are quoted with backslash ("\"). | ||
pattern = strings.TrimSuffix(pattern, " ") | ||
|
||
// An optional prefix "!" which negates the pattern; any matching file | ||
// excluded by a previous pattern will become included again. It is not | ||
// possible to re-include a file if a parent directory of that file is excluded. | ||
// Git doesn’t list excluded directories for performance reasons, so any patterns | ||
// on contained files have no effect, no matter where they are defined. | ||
// Put a backslash ("\") in front of the first "!" for patterns that begin | ||
// with a literal "!", for example, "\!important!.txt". | ||
negate := strings.HasPrefix(pattern, "!") | ||
if negate { | ||
pattern = strings.TrimPrefix(pattern, "!") | ||
} | ||
|
||
// If the pattern ends with a slash, it is removed for the purpose of the | ||
// following description, but it would only find a match with a directory. | ||
// In other words, foo/ will match a directory foo and paths underneath it, | ||
// but will not match a regular file or a symbolic link foo (this is consistent | ||
// with the way how pathspec works in general in Git). | ||
pattern = strings.TrimSuffix(pattern, string(os.PathSeparator)) | ||
|
||
// Two consecutive asterisks ("**") in patterns matched | ||
// against full pathname may have special meaning: | ||
if strings.Contains(pattern, dblAsterisks) { | ||
result := evalDblAsterisk(pattern, value) | ||
if negate { | ||
result = !result | ||
} | ||
return result | ||
} | ||
|
||
// If the pattern does not contain a slash /, Git treats it as a shell glob | ||
// pattern and checks for a match against the pathname relative to the location | ||
// of the .gitignore file (relative to the toplevel of the work tree if not from | ||
// a .gitignore file). | ||
if !strings.Contains(pattern, string(os.PathSeparator)) { | ||
m, err := filepath.Glob(pattern) | ||
if err != nil { | ||
// maybe log this? | ||
log.Printf("ERROR %s\n", err) | ||
return false | ||
} | ||
|
||
var found bool | ||
for _, v := range m { | ||
if v == value { | ||
found = true | ||
break | ||
} | ||
} | ||
|
||
if negate { | ||
return !found | ||
} | ||
return found | ||
} | ||
|
||
// Otherwise, Git treats the pattern as a shell glob suitable for consumption by | ||
// fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match | ||
// a / in the pathname. For example, "Documentation/*.html" matches | ||
// "Documentation/git.html" but not "Documentation/ppc/ppc.html" or | ||
// "tools/perf/Documentation/perf.html". | ||
|
||
// A leading slash matches the beginning of the pathname. For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". | ||
|
||
matched, err := path.Match(pattern, value) | ||
if err != nil { | ||
// maybe log? | ||
return false | ||
} | ||
|
||
if negate { | ||
return !matched | ||
} | ||
return matched | ||
} | ||
|
||
func evalDblAsterisk(pattern, value string) bool { | ||
// A leading "**" followed by a slash means match in all directories. | ||
// For example, "**/foo" matches file or directory "foo" anywhere, | ||
// the same as pattern "foo". "**/foo/bar" matches file or directory | ||
// "bar" anywhere that is directly under directory "foo". | ||
if strings.HasPrefix(pattern, dblAsterisks) { | ||
pattern = strings.TrimPrefix(pattern, dblAsterisks) | ||
return strings.HasSuffix(value, pattern) | ||
} | ||
|
||
// A trailing "/**" matches everything inside. For example, "abc/**" | ||
// matches all files inside directory "abc", relative to the location | ||
// of the .gitignore file, with infinite depth. | ||
if strings.HasSuffix(pattern, dblAsterisks) { | ||
pattern = strings.TrimSuffix(pattern, dblAsterisks) | ||
return strings.HasPrefix(value, pattern) | ||
} | ||
|
||
// A slash followed by two consecutive asterisks then a slash matches | ||
// zero or more directories. For example, "a/**/b" matches "a/b", | ||
// /"a/x/b", "a/x/y/b" and so on. | ||
parts := strings.Split(pattern, dblAsterisks) | ||
for i, part := range parts { | ||
switch i { | ||
case 0: | ||
if !strings.HasPrefix(value, part) { | ||
return false | ||
} | ||
case len(parts) - 1: // last part | ||
part = strings.TrimPrefix(part, string(os.PathSeparator)) | ||
return strings.HasSuffix(value, part) | ||
default: | ||
if !strings.Contains(value, part) { | ||
return false | ||
} | ||
} | ||
|
||
// trim evaluated text | ||
index := strings.Index(value, part) + len(part) | ||
value = value[index:] | ||
} | ||
|
||
// Other consecutive asterisks are considered invalid. | ||
return false | ||
} |
Oops, something went wrong.