├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── main.go └── wercker.yml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: mattn # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@master 13 | - name: Setup Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.x 17 | - name: Build 18 | run: go build -v . 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | - name: Setup Go 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: 1.x 19 | - name: Cross build 20 | run: make cross 21 | - name: Create Release 22 | id: create_release 23 | uses: actions/create-release@master 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | tag_name: ${{ github.ref }} 28 | release_name: Release ${{ github.ref }} 29 | - name: Upload 30 | run: make upload 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /files 2 | /goxz 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := files 2 | VERSION := $$(make -s show-version) 3 | CURRENT_REVISION := $(shell git rev-parse --short HEAD) 4 | BUILD_LDFLAGS := "-s -w -X main.revision=$(CURRENT_REVISION)" 5 | GOBIN ?= $(shell go env GOPATH)/bin 6 | export GO111MODULE=on 7 | 8 | .PHONY: all 9 | all: clean build 10 | 11 | .PHONY: build 12 | build: 13 | go build -ldflags=$(BUILD_LDFLAGS) -o $(BIN) . 14 | 15 | .PHONY: install 16 | install: 17 | go install -ldflags=$(BUILD_LDFLAGS) . 18 | 19 | .PHONY: show-version 20 | show-version: $(GOBIN)/gobump 21 | gobump show -r . 22 | 23 | $(GOBIN)/gobump: 24 | go install github.com/x-motemen/gobump/cmd/gobump@latest 25 | 26 | .PHONY: cross 27 | cross: $(GOBIN)/goxz 28 | goxz -n $(BIN) -pv=v$(VERSION) -build-ldflags=$(BUILD_LDFLAGS) . 29 | 30 | $(GOBIN)/goxz: 31 | go install github.com/Songmu/goxz/cmd/goxz@latest 32 | 33 | .PHONY: test 34 | test: build 35 | go test -v ./... 36 | 37 | .PHONY: clean 38 | clean: 39 | rm -rf $(BIN) goxz 40 | go clean 41 | 42 | .PHONY: bump 43 | bump: $(GOBIN)/gobump 44 | ifneq ($(shell git status --porcelain),) 45 | $(error git workspace is dirty) 46 | endif 47 | ifneq ($(shell git rev-parse --abbrev-ref HEAD),master) 48 | $(error current branch is not master) 49 | endif 50 | gobump up -w . 51 | git commit -am "bump up version to $(VERSION)" 52 | git tag "v$(VERSION)" 53 | git push origin master 54 | git push origin "refs/tags/v$(VERSION)" 55 | 56 | .PHONY: upload 57 | upload: $(GOBIN)/ghr 58 | ghr "v$(VERSION)" goxz 59 | 60 | $(GOBIN)/ghr: 61 | go install github.com/tcnksm/ghr@latest 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # files 2 | 3 | Find files 4 | 5 | ## Usage 6 | 7 | ``` 8 | $ files 9 | ``` 10 | 11 | ## Requirements 12 | 13 | golang 14 | 15 | ## Installation 16 | 17 | ``` 18 | go get github.com/mattn/files 19 | ``` 20 | 21 | ## Tips 22 | 23 | ### ctrlp.vim 24 | 25 | ```vim 26 | let g:ctrlp_user_command = 'files -a %s' 27 | ``` 28 | 29 | ## License 30 | 31 | MIT 32 | 33 | ## Author 34 | 35 | Yasuhiro Matsumoto (a.k.a mattn) 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mattn/files 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/saracen/walker v0.1.4 7 | golang.org/x/sync v0.11.0 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/saracen/walker v0.1.4 h1:/WCOt98GRkQ0KgL6hXJFBpoH21XY6iCD2N6LQWBFiaU= 2 | github.com/saracen/walker v0.1.4/go.mod h1:2F+hfOidTHfXP2AmlKOqpO+yewf8fIvNUDBNJogpJbk= 3 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 4 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= 5 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 6 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "path/filepath" 10 | "regexp" 11 | "runtime" 12 | "sort" 13 | "strings" 14 | 15 | "github.com/saracen/walker" 16 | ) 17 | 18 | const ( 19 | name = "files" 20 | version = "0.3.9" 21 | revision = "HEAD" 22 | ) 23 | 24 | type config struct { 25 | ignore string 26 | ignoreenv string 27 | hidden bool 28 | absolute bool 29 | fsort bool 30 | match string 31 | directoryOnly bool 32 | slash bool 33 | 34 | base string 35 | left string 36 | ignorere *regexp.Regexp 37 | matchre *regexp.Regexp 38 | showVersion bool 39 | } 40 | 41 | func env(key, def string) string { 42 | if v := os.Getenv(key); v != "" { 43 | return v 44 | } 45 | return def 46 | } 47 | 48 | type walkFn func(path string, info os.FileInfo) error 49 | 50 | func isHidden(cfg *config, path string) bool { 51 | return cfg.hidden && cfg.base != path && filepath.Base(path)[0] == '.' 52 | } 53 | 54 | func makeWalkFn(cfg *config, processMatch walkFn) walkFn { 55 | if cfg.directoryOnly { 56 | return func(path string, info os.FileInfo) error { 57 | path = filepath.Clean(path) 58 | if path == "." { 59 | return nil 60 | } 61 | if isHidden(cfg, path) { 62 | if info.IsDir() { 63 | return filepath.SkipDir 64 | } 65 | return nil 66 | } 67 | if info.IsDir() { 68 | if cfg.ignorere != nil && cfg.ignorere.MatchString(path) { 69 | return filepath.SkipDir 70 | } 71 | return processMatch(path, info) 72 | } 73 | return nil 74 | } 75 | } 76 | return func(path string, info os.FileInfo) error { 77 | path = filepath.Clean(path) 78 | if path == "." { 79 | return nil 80 | } 81 | if isHidden(cfg, path) { 82 | if info.IsDir() { 83 | return filepath.SkipDir 84 | } 85 | return nil 86 | } 87 | if !info.IsDir() { 88 | if cfg.ignorere != nil && cfg.ignorere.MatchString(path) { 89 | return filepath.SkipDir 90 | } 91 | return processMatch(path, info) 92 | } 93 | return nil 94 | } 95 | } 96 | 97 | func makeMatchFn(cfg *config, q chan string) walkFn { 98 | if cfg.matchre != nil { 99 | return func(path string, info os.FileInfo) error { 100 | if !cfg.matchre.MatchString(info.Name()) { 101 | return nil 102 | } 103 | q <- filepath.ToSlash(path) 104 | return nil 105 | } 106 | } 107 | return func(path string, info os.FileInfo) error { 108 | q <- filepath.ToSlash(path) 109 | return nil 110 | } 111 | } 112 | 113 | func files(ctx context.Context, cfg *config) chan string { 114 | q := make(chan string, 20) 115 | 116 | base := cfg.base 117 | sep := string(os.PathSeparator) 118 | if !strings.HasSuffix(base, sep) { 119 | base += sep 120 | } 121 | 122 | cb := walker.WithErrorCallback(func(pathname string, err error) error { 123 | return nil 124 | }) 125 | go func() { 126 | defer close(q) 127 | err := walker.WalkWithContext(ctx, base, makeWalkFn(cfg, makeMatchFn(cfg, q)), cb) 128 | if err != nil { 129 | fmt.Fprintln(os.Stderr, err) 130 | os.Exit(1) 131 | } 132 | }() 133 | return q 134 | } 135 | 136 | func makePrintFn(cfg *config) func(string) { 137 | var err error 138 | if cfg.absolute && !filepath.IsAbs(cfg.base) { 139 | if cfg.slash { 140 | return func(s string) { 141 | if _, err = os.Stdout.Write([]byte(filepath.ToSlash(filepath.Join(cfg.left, s)) + "\n")); err != nil { 142 | os.Exit(2) 143 | } 144 | } 145 | } 146 | return func(s string) { 147 | if _, err = os.Stdout.Write([]byte(filepath.Join(cfg.left, s) + "\n")); err != nil { 148 | os.Exit(2) 149 | } 150 | } 151 | } 152 | if cfg.slash { 153 | return func(s string) { 154 | if s, err = filepath.Rel(cfg.left, s); err != nil { 155 | os.Exit(2) 156 | } 157 | if _, err := os.Stdout.Write([]byte(filepath.ToSlash(s) + "\n")); err != nil { 158 | os.Exit(2) 159 | } 160 | } 161 | } 162 | return func(s string) { 163 | if s, err = filepath.Rel(cfg.left, s); err != nil { 164 | os.Exit(2) 165 | } 166 | if _, err := os.Stdout.Write([]byte(s + "\n")); err != nil { 167 | os.Exit(2) 168 | } 169 | } 170 | } 171 | 172 | func (cfg *config) doPrint(q chan string) { 173 | printFn := makePrintFn(cfg) 174 | if cfg.fsort { 175 | fs := []string{} 176 | for s := range q { 177 | fs = append(fs, s) 178 | } 179 | sort.Strings(fs) 180 | for _, s := range fs { 181 | printFn(s) 182 | } 183 | } else { 184 | for s := range q { 185 | printFn(s) 186 | } 187 | } 188 | } 189 | 190 | func run() int { 191 | var cfg config 192 | flag.StringVar(&cfg.ignore, "i", env(`FILES_IGNORE_PATTERN`, `(\.git|\.hg|\.svn|_darcs|\.bzr)/`), "Ignore directory") 193 | flag.StringVar(&cfg.ignoreenv, "I", "", "Custom environment key for ignore") 194 | flag.BoolVar(&cfg.hidden, "H", true, "Ignore hidden") 195 | flag.BoolVar(&cfg.absolute, "a", false, "Display absolute path") 196 | flag.BoolVar(&cfg.fsort, "s", false, "Sort results") 197 | flag.StringVar(&cfg.match, "m", "", "Display matched files") 198 | flag.BoolVar(&cfg.directoryOnly, "d", false, "Directory only") 199 | flag.BoolVar(&cfg.slash, "S", false, "Display slash as separator") 200 | flag.BoolVar(&cfg.showVersion, "v", false, "Show version") 201 | flag.Parse() 202 | 203 | if cfg.showVersion { 204 | fmt.Fprintf(os.Stdout, "%s\n", version) 205 | os.Exit(0) 206 | } 207 | 208 | var err error 209 | 210 | if cfg.match != "" { 211 | cfg.matchre, err = regexp.Compile(cfg.match) 212 | if err != nil { 213 | fmt.Fprintln(os.Stderr, err) 214 | os.Exit(1) 215 | } 216 | } 217 | if cfg.ignoreenv != "" { 218 | cfg.ignore = os.Getenv(cfg.ignoreenv) 219 | } 220 | if cfg.ignore != "" { 221 | cfg.ignorere, err = regexp.Compile(cfg.ignore) 222 | if err != nil { 223 | fmt.Fprintln(os.Stderr, err) 224 | os.Exit(1) 225 | } 226 | } 227 | base := "." 228 | if flag.NArg() > 0 { 229 | base = flag.Arg(0) 230 | 231 | base = filepath.FromSlash(filepath.Clean(base)) 232 | if runtime.GOOS == "windows" && base != "" && base[0] == '~' { 233 | base = filepath.Join(os.Getenv("USERPROFILE"), base[1:]) 234 | } 235 | } 236 | 237 | if tmp, err := filepath.EvalSymlinks(base); err == nil { 238 | base = tmp 239 | } 240 | 241 | left := base 242 | if cfg.absolute { 243 | if left, err = filepath.Abs(base); err != nil { 244 | left = filepath.Dir(left) 245 | } 246 | } else if !filepath.IsAbs(base) { 247 | if cwd, err := os.Getwd(); err == nil { 248 | if left, err = filepath.Rel(cwd, base); err == nil { 249 | base = left 250 | } 251 | } 252 | } 253 | cfg.base = base 254 | cfg.left = left 255 | 256 | ctx, cancel := context.WithCancel(context.Background()) 257 | defer cancel() 258 | 259 | sc := make(chan os.Signal, 1) 260 | signal.Notify(sc, os.Interrupt) 261 | go func() { 262 | <-sc 263 | cancel() 264 | sc = nil 265 | }() 266 | 267 | cfg.doPrint(files(ctx, &cfg)) 268 | 269 | if sc == nil { 270 | return 1 271 | } 272 | return 0 273 | } 274 | 275 | func main() { 276 | os.Exit(run()) 277 | } 278 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: tcnksm/gox 2 | build: 3 | steps: 4 | - setup-go-workspace 5 | - script: 6 | name: show environments 7 | code: | 8 | git version 9 | go version 10 | - script: 11 | name: go get 12 | code: | 13 | go get -t ./... 14 | - script: 15 | name: go test 16 | code: | 17 | go test -v ./... 18 | deploy: 19 | steps: 20 | - setup-go-workspace 21 | - script: 22 | name: go get 23 | code: | 24 | go get ./... 25 | - wercker/gox: 26 | os: darwin linux windows 27 | arch: 386 amd64 28 | output: '{{.Dir}}_{{.OS}}_{{.Arch}}/{{.Dir}}' 29 | dest: $WERCKER_OUTPUT_DIR/pkg 30 | - tcnksm/zip: 31 | input: $WERCKER_OUTPUT_DIR/pkg 32 | output: $WERCKER_OUTPUT_DIR/dist 33 | - script: 34 | name: set release tag 35 | code: | 36 | if [ -n "$GOBUMP_NEW_VERSION" ]; then 37 | export RELEASE_TAG="v$GOBUMP_NEW_VERSION" 38 | fi 39 | - tcnksm/ghr: 40 | token: $GITHUB_TOKEN 41 | input: $WERCKER_OUTPUT_DIR/dist 42 | replace: true 43 | version: $RELEASE_TAG 44 | opt: --draft 45 | 46 | --------------------------------------------------------------------------------