ソースを参照

init imports

master
jimzhan 9年前
コミット
438c241618
4個のファイルの変更407行の追加2行の削除
  1. +13
    -2
      README.md
  2. +183
    -0
      fs.go
  3. +85
    -0
      fs_test.go
  4. +126
    -0
      watcher.go

+ 13
- 2
README.md ファイルの表示

@@ -1,2 +1,13 @@
# fs
File system toolkit for Golang
FS
===
[![Build Status](https://travis-ci.org/goanywhere/fs.svg?branch=master)](https://travis-ci.org/goanywhere/fs) [![GoDoc](https://godoc.org/github.com/goanywhere/fs?status.svg)](http://godoc.org/github.com/goanywhere/fs)

FS is a file system toolkit for the [Go](http://golang.org) programming language.

## Getting Started

Install the package, along with executable binary helper (**go 1.4** and greater is required):

```shell
$ go get -v github.com/goanywhere/fs
```

+ 183
- 0
fs.go ファイルの表示

@@ -0,0 +1,183 @@
package fs

import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
)

// Getcd returns absolute path of the caller.
func Getcd(skip int) string {
if dir := Geted(); strings.HasPrefix(dir, os.TempDir()) {
pc, _, _, _ := runtime.Caller(skip + 1)
function := runtime.FuncForPC(pc)
filename, _ := function.FileLine(0)
return path.Dir(filename)

} else {
return dir
}
}

// Geted returns an absolute path to the executable.
func Geted() string {
if dir, err := filepath.Abs(filepath.Dir(os.Args[0])); err == nil {
return dir
} else {
panic("Failed to retrieve executable directory")
}
}

// Getwd returns a absolute path of the current directory.
func Getwd() string {
if cwd, err := os.Getwd(); err == nil {
cwd, _ = filepath.Abs(cwd)
return cwd
} else {
panic("Failed to retrieve current working directory")
}
}

// Abs finds the absolute path for the given path.
// Supported Formats:
// * empty path => current working directory.
// * '.', '..' & '~'
// *NOTE* Abs does NOT check the existence of the path.
func Abs(path string) string {
var abs string
cwd, _ := os.Getwd()

if path == "" || path == "." {
abs = cwd

} else if path == ".." {
abs = filepath.Join(cwd, path)

} else if strings.HasPrefix(path, "~/") {
abs = filepath.Join(UserDir(), path[2:])

} else if strings.HasPrefix(path, "./") {
abs = filepath.Join(cwd, path[2:])

} else if strings.HasPrefix(path, "../") {
abs = filepath.Join(cwd, "..", path[2:])

} else {
return path
}
return abs
}

// Copy recursively copies files/(sub)directoires into the given path.
// *NOTE* It uses platform's native copy commands (windows: copy, *nix: rsync).
func Copy(src, dst string) (err error) {
var cmd *exec.Cmd
src, dst = Abs(src), Abs(dst)
// Determine the command we need to use.
if runtime.GOOS == "windows" {
// *NOTE* Not sure this will work correctly, we don't have Windows to test.
if IsFile(src) {
cmd = exec.Command("copy", src, dst)
} else {
cmd = exec.Command("xcopy", src, dst, "/S /E")
}
} else {
cmd = exec.Command("rsync", "-a", src, dst)
}

if stdout, err := cmd.StdoutPipe(); err == nil {
if stderr, err := cmd.StderrPipe(); err == nil {
// Start capturing the stdout/err.
err = cmd.Start()
io.Copy(os.Stdout, stdout)
buffer := new(bytes.Buffer)
buffer.ReadFrom(stderr)
cmd.Wait()
if cmd.ProcessState.String() != "exit status 0" {
err = fmt.Errorf("\t%s\n", buffer.String())
}
}
}
return
}

// Exists check if the given path exists.
func Exists(path string) bool {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}

// Find matches files with regular expression pattern under the given root.
func Find(root string, pattern *regexp.Regexp) (paths []string) {
if Exists(root) {
filepath.Walk(root, func(path string, info os.FileInfo, e error) error {
if pattern.MatchString(path) {
paths = append(paths, info.Name())
}
return e
})
}
return
}

// Grep searches text files via regular expression under the given path,
// paths of the files contain matched line(s) will be returned.
func Grep(root string, pattern *regexp.Regexp) (paths []string) {
panic(fmt.Errorf("Not Implemented"))
}

// Glob recursively finds the names of all files matching pattern under the given path.
func Glob(path string, pattern string) (matches []string, err error) {
err = filepath.Walk(path, func(path string, info os.FileInfo, e error) error {
if e == nil {
if info.IsDir() {
if filenames, e := filepath.Glob(filepath.Join(path, pattern)); e == nil {
matches = append(matches, filenames...)
}
}
}
return e
})
return
}

// IsDir checks if the given path is a directory.
func IsDir(path string) bool {
src, err := os.Stat(path)
if os.IsNotExist(err) {
return false
}
return src.IsDir()
}

// IsFile checks if the given path is a file.
func IsFile(path string) bool {
src, err := os.Stat(path)
if os.IsNotExist(err) {
return false
}
return !src.IsDir()
}

// UserDir finds base path of current system user.
func UserDir() string {
if runtime.GOOS == "windows" {
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
return home
}
return os.Getenv("HOME")
}

+ 85
- 0
fs_test.go ファイルの表示

@@ -0,0 +1,85 @@
package fs

import (
"bufio"
"os"
"os/exec"
"testing"

. "github.com/smartystreets/goconvey/convey"
)

func setup(handler func(f string)) {
filename := "/tmp/tmpfile"
if file, err := os.Create(filename); err == nil {
defer file.Close()
defer os.Remove(filename)
buffer := bufio.NewWriter(file)
buffer.WriteString("I'm just a temp. file")
buffer.Flush()

handler(filename)
}
}

func TestAbs(t *testing.T) {
Convey("Absolute path check", t, func() {
So(Abs("/tmp"), ShouldEqual, "/tmp")
})
}

func TestCopy(t *testing.T) {
Convey("Copy files/directories recursively", t, func() {
filename := "GoAnywhereFake"
exec.Command("touch", Abs("~/"+filename)).Run()
defer os.Remove("/tmp/" + filename)
err := Copy("~/"+filename, "/tmp")
So(Exists("/tmp/"+filename), ShouldBeTrue)
So(err, ShouldBeNil)

exec.Command("mkdir", Abs("~/GoAnywhere")).Run()
exec.Command("touch", Abs("~/GoAnywhere/Fake")).Run()
defer os.RemoveAll("~/GoAnywhere")
err = Copy("~/GoAnywhere", "/tmp")
So(Exists("/tmp/GoAnywhere"), ShouldBeTrue)
So(err, ShouldBeNil)
})
}

func TestExists(t *testing.T) {
Convey("Checks if the given path exists", t, func() {
exists := Exists("/tmp")
So(exists, ShouldBeTrue)

exists = Exists("/NotExists")
So(exists, ShouldBeFalse)
})
}

func TestIsDir(t *testing.T) {
setup(func(filename string) {
flag := IsDir(filename)
Convey("Checks if the given path is a directory", t, func() {
So(flag, ShouldBeFalse)
})
})

flag := IsDir("/tmp")
Convey("Checks if the given path is a directory", t, func() {
So(flag, ShouldBeTrue)
})
}

func TestIsFile(t *testing.T) {
setup(func(filename string) {
flag := IsFile(filename)
Convey("Checks if the given path is a file", t, func() {
So(flag, ShouldBeTrue)
})
})

flag := IsFile("/tmp")
Convey("Checks if the given path is a file", t, func() {
So(flag, ShouldBeFalse)
})
}

+ 126
- 0
watcher.go ファイルの表示

@@ -0,0 +1,126 @@
package fs

import (
"os"
"path/filepath"
"regexp"
"sync"
"time"

log "github.com/Sirupsen/logrus"
"github.com/go-fsnotify/fsnotify"
)

type watcher struct {
dir string
mutex sync.Mutex

daemon *fsnotify.Watcher
event chan *fsnotify.Event

ignores []string
watchList map[*regexp.Regexp]func(string)
}

func NewWatcher(dir string) *watcher {
self := new(watcher)
self.dir = dir
self.event = make(chan *fsnotify.Event)
self.ignores = []string{}
self.watchList = make(map[*regexp.Regexp]func(string))
return self
}

// IsWrite checks if the triggered event is fsnotify.Write|fsnotify.Create.
func (self *watcher) isWrite(event *fsnotify.Event) bool {
// instead of MODIFY event, editors may only send CREATE.
// so we need to capture write & create.
if event.Op&fsnotify.Write == fsnotify.Write ||
event.Op&fsnotify.Create == fsnotify.Create {
return true
}
return false
}

// IsRemove checks if the triggered event is fsnotify.Remove.
func (self *watcher) isRemove(event *fsnotify.Event) bool {
return event.Op&fsnotify.Remove == fsnotify.Remove
}

// Add appends regular expression based pattern processor into the watch list.
func (self *watcher) Add(pattern *regexp.Regexp, process func(path string)) {
self.mutex.Lock()
defer self.mutex.Unlock()
self.watchList[pattern] = process
}

func (self *watcher) watch() {
self.daemon, _ = fsnotify.NewWatcher()
if self.daemon != nil {
self.daemon.Close()
}
// ensure we have a new daemon watcher eachtime we start watching.
self.daemon, _ = fsnotify.NewWatcher()
if err := self.daemon.Add(self.dir); err != nil {
log.Fatalf("Failed to create fs watcher for <%s>: %v", self.dir, err)
}

// watch all folders under the root.
filepath.Walk(self.dir, func(path string, info os.FileInfo, e error) error {
if info.IsDir() {
for _, ignore := range self.ignores {
if info.Name() == ignore {
return filepath.SkipDir
}
}
if err := self.daemon.Add(path); err != nil {
log.Fatalf("Failed create watch list for (%s): %v", info.Name(), err)
}
}
return e
})
}

func (self *watcher) startWatching() {
self.watch()

var evt *fsnotify.Event
// multiple events can be triggered on a successful write
// (e.g. Create followed by multiple CHMOD), just postpone
// a bit to let it calm before actual processing.
var delay <-chan time.Time
for {
select {
case event := <-self.daemon.Events:
// We only need "Write" event (modify | create | remove)
if self.isWrite(&event) || self.isRemove(&event) {
evt = &event
delay = time.After(500 * time.Millisecond)
}
case err := <-self.daemon.Errors:
log.Fatalf("Failed to watch the path %v", err)

case <-delay:
self.event <- evt
}
}
}

// Start watches all file changes under the root path & dispatch
// to corresonding handlers (added via Add function)
func (self *watcher) Start() {
go self.startWatching()
// listens the catched event & start processing.
for event := range self.event {
if event == nil {
continue
}
// start processing the event
var filename = filepath.Base(event.Name)
for pattern, process := range self.watchList {
if pattern.MatchString(filename) {
process(event.Name)
}
}
}
}

読み込み中…
キャンセル
保存