Skip to content

Commit

Permalink
initial restructure according to #21
Browse files Browse the repository at this point in the history
Created packages for each component and tied them together in the main class
  • Loading branch information
zabawaba99 committed Mar 5, 2016
1 parent 7f88297 commit a84f25f
Show file tree
Hide file tree
Showing 6 changed files with 586 additions and 3 deletions.
151 changes: 151 additions & 0 deletions builder/builder.go
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()
}
30 changes: 30 additions & 0 deletions exchange/exchange.go
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()
}
20 changes: 17 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import (
"flag"
"log"
"os"

"github.com/Tonkpils/snag/builder"
"github.com/Tonkpils/snag/exchange"
"github.com/Tonkpils/snag/watcher"
)

const (
Expand Down Expand Up @@ -39,18 +43,28 @@ func main() {
log.Fatal(err)
}

b, err := NewBuilder(c)
ex := exchange.New()

bc := builder.Config{
Build: c.Build,
Run: c.Run,
DepWarning: c.DepWarnning,
Verbose: c.Verbose,
}
b := builder.New(ex, bc)
ex.Listen("rebuild", b.Build)

w, err := watcher.New(ex, c.IgnoredItems)
if err != nil {
log.Fatal(err)
}
defer b.Close()

wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}

b.Watch(wd)
w.Watch(wd)
}

func handleSubCommand(cmd string) error {
Expand Down
146 changes: 146 additions & 0 deletions watcher/gitglob.go
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
}
Loading

0 comments on commit a84f25f

Please sign in to comment.