├── LICENSE
├── README.md
├── browser
├── LICENSE
└── browser.go
├── gddo
└── gddo.go
├── generated
└── generated.go
├── gfmutil
└── gfmutil.go
├── go.mod
├── gopherjs_http
├── gopherjs_http.go
├── jsutil
│ ├── jsutil.go
│ └── v2
│ │ └── jsutil.go
├── package.go
└── vfs.go
├── importgraphutil
└── importgraphutil.go
├── indentwriter
├── indentwriter.go
└── indentwriter_test.go
├── open
└── open.go
├── openutil
└── openutil.go
├── ospath
└── ospath.go
├── osutil
├── environ.go
└── environ_test.go
├── parserutil
├── parserutil.go
└── parserutil_test.go
├── pipeutil
├── dir.go
└── pipeutil.go
├── printerutil
└── printerutil.go
├── reflectfind
└── reflectfind.go
├── reflectsource
├── callername.go
├── callername_test.go
├── doc.go
├── funcsource.go
├── funcsource_test.go
├── indicies.go
└── indicies_test.go
├── timeutil
└── timeutil.go
├── trash
├── doc.go
├── trash.go
└── trash_darwin.go
└── vfs
└── godocfs
├── godocfs
└── godocfs.go
├── html
└── vfstemplate
│ └── vfstemplate.go
├── path
└── vfspath
│ └── match.go
└── vfsutil
├── walk.go
└── walk_test.go
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2013 Dmitri Shuralyov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | go
2 | ==
3 |
4 | [](https://pkg.go.dev/github.com/shurcooL/go)
5 |
6 | Common Go code.
7 |
8 | Installation
9 | ------------
10 |
11 | ```sh
12 | go get github.com/shurcooL/go
13 | ```
14 |
15 | Directories
16 | -----------
17 |
18 | | Path | Synopsis |
19 | |--------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
20 | | [browser](https://pkg.go.dev/github.com/shurcooL/go/browser) | Package browser provides utilities for interacting with users' browsers. |
21 | | [gddo](https://pkg.go.dev/github.com/shurcooL/go/gddo) | Package gddo is a simple client library for accessing the godoc.org API. |
22 | | [generated](https://pkg.go.dev/github.com/shurcooL/go/generated) | Package generated provides a function that parses a Go file and reports whether it contains a "// Code generated … DO NOT EDIT." line comment. |
23 | | [gfmutil](https://pkg.go.dev/github.com/shurcooL/go/gfmutil) | Package gfmutil offers functionality to render GitHub Flavored Markdown to io.Writer. |
24 | | [gopherjs_http](https://pkg.go.dev/github.com/shurcooL/go/gopherjs_http) | Package gopherjs_http provides helpers for compiling Go using GopherJS and serving it over HTTP. |
25 | | [gopherjs_http/jsutil](https://pkg.go.dev/github.com/shurcooL/go/gopherjs_http/jsutil) | Package jsutil provides utility functions for interacting with native JavaScript APIs via github.com/gopherjs/gopherjs/js API. |
26 | | [gopherjs_http/jsutil/v2](https://pkg.go.dev/github.com/shurcooL/go/gopherjs_http/jsutil/v2) | Package jsutil provides utility functions for interacting with native JavaScript APIs via syscall/js API. |
27 | | [importgraphutil](https://pkg.go.dev/github.com/shurcooL/go/importgraphutil) | Package importgraphutil augments "golang.org/x/tools/refactor/importgraph" with a way to build graphs ignoring tests. |
28 | | [indentwriter](https://pkg.go.dev/github.com/shurcooL/go/indentwriter) | Package indentwriter implements an io.Writer wrapper that indents every non-empty line with specified number of tabs. |
29 | | [open](https://pkg.go.dev/github.com/shurcooL/go/open) | Package open offers ability to open files or URLs as if user double-clicked it in their OS. |
30 | | [openutil](https://pkg.go.dev/github.com/shurcooL/go/openutil) | Package openutil displays Markdown or HTML in a new browser tab. |
31 | | [ospath](https://pkg.go.dev/github.com/shurcooL/go/ospath) | Package ospath provides utilities to get OS-specific directories. |
32 | | [osutil](https://pkg.go.dev/github.com/shurcooL/go/osutil) | Package osutil offers a utility for manipulating a set of environment variables. |
33 | | [parserutil](https://pkg.go.dev/github.com/shurcooL/go/parserutil) | Package parserutil offers convenience functions for parsing Go code to AST. |
34 | | [pipeutil](https://pkg.go.dev/github.com/shurcooL/go/pipeutil) | Package pipeutil provides additional functionality for gopkg.in/pipe.v2 package. |
35 | | [printerutil](https://pkg.go.dev/github.com/shurcooL/go/printerutil) | Package printerutil provides formatted printing of AST nodes. |
36 | | [reflectfind](https://pkg.go.dev/github.com/shurcooL/go/reflectfind) | Package reflectfind offers funcs to perform deep-search via reflect to find instances that satisfy given query. |
37 | | [reflectsource](https://pkg.go.dev/github.com/shurcooL/go/reflectsource) | Package sourcereflect implements run-time source reflection, allowing a program to look up string representation of objects from the underlying .go source files. |
38 | | [timeutil](https://pkg.go.dev/github.com/shurcooL/go/timeutil) | Package timeutil provides a func for getting start of week of given time. |
39 | | [trash](https://pkg.go.dev/github.com/shurcooL/go/trash) | Package trash implements functionality to move files into trash. |
40 | | [vfs/godocfs/godocfs](https://pkg.go.dev/github.com/shurcooL/go/vfs/godocfs/godocfs) | Package godocfs implements vfs.FileSystem using a http.FileSystem. |
41 | | [vfs/godocfs/html/vfstemplate](https://pkg.go.dev/github.com/shurcooL/go/vfs/godocfs/html/vfstemplate) | Package vfstemplate offers html/template helpers that use vfs.FileSystem. |
42 | | [vfs/godocfs/path/vfspath](https://pkg.go.dev/github.com/shurcooL/go/vfs/godocfs/path/vfspath) | Package vfspath implements utility routines for manipulating virtual file system paths. |
43 | | [vfs/godocfs/vfsutil](https://pkg.go.dev/github.com/shurcooL/go/vfs/godocfs/vfsutil) | Package vfsutil implements some I/O utility functions for vfs.FileSystem. |
44 |
45 | License
46 | -------
47 |
48 | - [MIT License](LICENSE)
49 |
--------------------------------------------------------------------------------
/browser/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 The Go Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/browser/browser.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // Package browser provides utilities for interacting with users' browsers.
6 | // It's a copy of the cmd/internal/browser package, with a simpler API.
7 | package browser
8 |
9 | import (
10 | "os"
11 | "os/exec"
12 | "runtime"
13 | "time"
14 | )
15 |
16 | // Open tries to open url in a browser and reports whether it succeeded.
17 | func Open(url string) bool {
18 | for _, args := range commands() {
19 | cmd := exec.Command(args[0], append(args[1:], url)...)
20 | if cmd.Start() == nil && appearsSuccessful(cmd, 3*time.Second) {
21 | return true
22 | }
23 | }
24 | return false
25 | }
26 |
27 | // commands returns a list of possible commands to use to open a url.
28 | func commands() [][]string {
29 | var cmds [][]string
30 | if exe := os.Getenv("BROWSER"); exe != "" {
31 | cmds = append(cmds, []string{exe})
32 | }
33 | switch runtime.GOOS {
34 | case "darwin":
35 | cmds = append(cmds, []string{"/usr/bin/open"})
36 | case "windows":
37 | cmds = append(cmds, []string{"cmd", "/c", "start"})
38 | default:
39 | if os.Getenv("DISPLAY") != "" {
40 | // xdg-open is only for use in a desktop environment.
41 | cmds = append(cmds, []string{"xdg-open"})
42 | }
43 | }
44 | cmds = append(cmds,
45 | []string{"chrome"},
46 | []string{"google-chrome"},
47 | []string{"chromium"},
48 | []string{"firefox"},
49 | )
50 | return cmds
51 | }
52 |
53 | // appearsSuccessful reports whether the command appears to have run successfully.
54 | // If the command runs longer than the timeout, it's deemed successful.
55 | // If the command runs within the timeout, it's deemed successful if it exited cleanly.
56 | func appearsSuccessful(cmd *exec.Cmd, timeout time.Duration) bool {
57 | errc := make(chan error, 1)
58 | go func() {
59 | errc <- cmd.Wait()
60 | }()
61 |
62 | select {
63 | case <-time.After(timeout):
64 | return true
65 | case err := <-errc:
66 | return err == nil
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/gddo/gddo.go:
--------------------------------------------------------------------------------
1 | // Package gddo is a simple client library for accessing the godoc.org API.
2 | //
3 | // It provides a single utility to fetch the importers of a Go package.
4 | package gddo
5 |
6 | import (
7 | "encoding/json"
8 | "fmt"
9 | "net/http"
10 | )
11 |
12 | // Client manages communication with the godoc.org API.
13 | type Client struct {
14 | // UserAgent is used for outbound requests to godoc.org API, if set to non-empty value.
15 | UserAgent string
16 | }
17 |
18 | // GetImporters fetches the importers of Go package with specified importPath via godoc.org API.
19 | func (c *Client) GetImporters(importPath string) (Importers, error) {
20 | req, err := http.NewRequest("GET", "https://api.godoc.org/importers/"+importPath, nil)
21 | if err != nil {
22 | return Importers{}, err
23 | }
24 | if c.UserAgent != "" {
25 | req.Header.Set("User-Agent", c.UserAgent)
26 | }
27 | resp, err := http.DefaultClient.Do(req)
28 | if err != nil {
29 | return Importers{}, err
30 | }
31 | defer resp.Body.Close()
32 | if resp.StatusCode != http.StatusOK {
33 | return Importers{}, fmt.Errorf("non-200 status code: %v", resp.StatusCode)
34 | }
35 | var importers Importers
36 | err = json.NewDecoder(resp.Body).Decode(&importers)
37 | if err != nil {
38 | return Importers{}, err
39 | }
40 | return importers, nil
41 | }
42 |
43 | // Importers contains the list of Go packages that import a given Go package.
44 | type Importers struct {
45 | Results []Package
46 | }
47 |
48 | // Package represents a Go package.
49 | type Package struct {
50 | Path string // Import path of the package.
51 | Synopsis string // Synopsis of the package.
52 | }
53 |
--------------------------------------------------------------------------------
/generated/generated.go:
--------------------------------------------------------------------------------
1 | // Package generated provides a function that parses a Go file and reports
2 | // whether it contains a "// Code generated … DO NOT EDIT." line comment.
3 | //
4 | // It implements the specification at https://golang.org/s/generatedcode.
5 | //
6 | // The first priority is correctness (no false negatives, no false positives).
7 | // It must return accurate results even if the input Go source code is not gofmted.
8 | //
9 | // The second priority is performance. The current version uses bufio.Reader and
10 | // ReadBytes. Performance can be optimized further by using lower level I/O
11 | // primitives and allocating less. That can be explored later. A lot of the time
12 | // is spent on reading the entire file without being able to stop early,
13 | // since the specification allows the comment to appear anywhere in the file.
14 | //
15 | // Deprecated: This package has moved to dmitri.shuralyov.com/go/generated.
16 | // Use that package instead.
17 | package generated
18 |
19 | import (
20 | "io"
21 |
22 | "dmitri.shuralyov.com/go/generated"
23 | )
24 |
25 | // Parse parses the source code of a single Go source file
26 | // provided via src, and reports whether the file contains
27 | // a "// Code generated ... DO NOT EDIT." line comment
28 | // matching the specification at https://golang.org/s/generatedcode:
29 | //
30 | // Generated files are marked by a line of text that matches
31 | // the regular expression, in Go syntax:
32 | //
33 | // ^// Code generated .* DO NOT EDIT\.$
34 | //
35 | // The .* means the tool can put whatever folderol it wants in there,
36 | // but the comment must be a single line and must start with Code generated
37 | // and end with DO NOT EDIT., with a period.
38 | //
39 | // The text may appear anywhere in the file.
40 | func Parse(src io.Reader) (hasGeneratedComment bool, err error) {
41 | return generated.Parse(src)
42 | }
43 |
44 | // ParseFile opens the file specified by filename and uses Parse to parse it.
45 | // If the source couldn't be read, the error indicates the specific failure.
46 | func ParseFile(filename string) (hasGeneratedComment bool, err error) {
47 | return generated.ParseFile(filename)
48 | }
49 |
--------------------------------------------------------------------------------
/gfmutil/gfmutil.go:
--------------------------------------------------------------------------------
1 | // Package gfmutil offers functionality to render GitHub Flavored Markdown to io.Writer.
2 | package gfmutil
3 |
4 | import (
5 | "bytes"
6 | "io"
7 | "net/http"
8 |
9 | "github.com/shurcooL/github_flavored_markdown"
10 | )
11 |
12 | // TODO: Change API to return errors rather than panicking.
13 |
14 | // WriteGitHubFlavoredMarkdownViaLocal converts GitHub Flavored Markdown to full HTML page and writes it to w.
15 | // It assumes that GFM CSS is available at /assets/gfm/gfm.css.
16 | func WriteGitHubFlavoredMarkdownViaLocal(w io.Writer, markdown []byte) {
17 | io.WriteString(w, `
`)
18 | w.Write(github_flavored_markdown.Markdown(markdown))
19 | io.WriteString(w, ``)
20 | }
21 |
22 | // WriteGitHubFlavoredMarkdownViaGitHub converts GitHub Flavored Markdown to full HTML page and writes it to w
23 | // by using GitHub API.
24 | // It assumes that GFM CSS is available at /assets/gfm/gfm.css.
25 | func WriteGitHubFlavoredMarkdownViaGitHub(w io.Writer, markdown []byte) {
26 | io.WriteString(w, ``)
27 |
28 | // Convert GitHub Flavored Markdown to HTML (includes syntax highlighting for diff, Go, etc.)
29 | resp, err := http.Post("https://api.github.com/markdown/raw", "text/x-markdown", bytes.NewReader(markdown))
30 | if err != nil {
31 | panic(err)
32 | }
33 | defer resp.Body.Close()
34 | _, err = io.Copy(w, resp.Body)
35 | if err != nil {
36 | panic(err)
37 | }
38 |
39 | io.WriteString(w, ``)
40 | }
41 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/shurcooL/go
2 |
3 | go 1.19
4 |
--------------------------------------------------------------------------------
/gopherjs_http/gopherjs_http.go:
--------------------------------------------------------------------------------
1 | // Package gopherjs_http provides helpers for compiling Go using GopherJS and serving it over HTTP.
2 | package gopherjs_http
3 |
4 | import (
5 | "bytes"
6 | "fmt"
7 | "html/template"
8 | "io"
9 | "os"
10 | "sync"
11 | "time"
12 |
13 | "github.com/shurcooL/gopherjslib"
14 | )
15 |
16 | func handleJsError(jsCode string, err error) string {
17 | if err != nil {
18 | fmt.Fprintln(os.Stderr, err)
19 | return `console.error("` + template.JSEscapeString(err.Error()) + `");`
20 | }
21 | return jsCode
22 | }
23 |
24 | // Needed to prevent race condition until https://github.com/go-on/gopherjslib/issues/2 is resolved.
25 | var gopherjslibLock sync.Mutex
26 |
27 | const minify = true
28 |
29 | func goReadersToJS(names []string, goReaders []io.Reader) (jsCode string, err error) {
30 | started := time.Now()
31 | defer func() { fmt.Printf("goReadersToJS (minify=%v) taken: %v\n", minify, time.Since(started)) }()
32 | gopherjslibLock.Lock()
33 | defer gopherjslibLock.Unlock()
34 |
35 | var out bytes.Buffer
36 | builder := gopherjslib.NewBuilder(&out, &gopherjslib.Options{Minify: minify})
37 |
38 | for i, goReader := range goReaders {
39 | builder.Add(names[i], goReader)
40 | }
41 |
42 | err = builder.Build()
43 | if err != nil {
44 | return "", err
45 | }
46 |
47 | return out.String(), nil
48 | }
49 |
--------------------------------------------------------------------------------
/gopherjs_http/jsutil/jsutil.go:
--------------------------------------------------------------------------------
1 | // Package jsutil provides utility functions for interacting with
2 | // native JavaScript APIs via github.com/gopherjs/gopherjs/js API.
3 | // It has support for common types in honnef.co/go/js/dom.
4 | package jsutil
5 |
6 | import (
7 | "encoding/json"
8 | "fmt"
9 | "reflect"
10 |
11 | "github.com/gopherjs/gopherjs/js"
12 | "honnef.co/go/js/dom"
13 | )
14 |
15 | // Wrap returns a wrapper func that handles the conversion from native JavaScript *js.Object parameters
16 | // to the following types.
17 | //
18 | // It supports *js.Object (left unmodified), dom.Document, dom.Element, dom.Event, dom.HTMLElement, dom.Node.
19 | // It has to be one of those types exactly; it can't be another type that implements the interface like *dom.BasicElement.
20 | //
21 | // For other types, the input is assumed to be a JSON string which is then unmarshalled into that type.
22 | //
23 | // If the number of arguments provided to the wrapped func doesn't match
24 | // the number of arguments for original func, it panics.
25 | //
26 | // Here is example usage:
27 | //
28 | // Example
29 | //
30 | // func Handler(event dom.Event, htmlElement dom.HTMLElement, data someStruct) {
31 | // data.Foo = ... // Use event, htmlElement, data.
32 | // }
33 | //
34 | // func main() {
35 | // js.Global.Set("Handler", jsutil.Wrap(Handler))
36 | // }
37 | func Wrap(fn interface{}) func(...*js.Object) {
38 | v := reflect.ValueOf(fn)
39 | return func(args ...*js.Object) {
40 | if len(args) != v.Type().NumIn() {
41 | panic(fmt.Errorf("wrapped %v got %v arguments, want %v", v.Type().String(), len(args), v.Type().NumIn()))
42 | }
43 | in := make([]reflect.Value, v.Type().NumIn())
44 | for i := range in {
45 | switch t := v.Type().In(i); t {
46 | // *js.Object is passed through.
47 | case typeOf((**js.Object)(nil)):
48 | in[i] = reflect.ValueOf(args[i])
49 |
50 | // dom types are wrapped.
51 | case typeOf((*dom.Document)(nil)):
52 | in[i] = reflect.ValueOf(dom.WrapDocument(args[i]))
53 | case typeOf((*dom.Element)(nil)):
54 | in[i] = reflect.ValueOf(dom.WrapElement(args[i]))
55 | case typeOf((*dom.Event)(nil)):
56 | in[i] = reflect.ValueOf(dom.WrapEvent(args[i]))
57 | case typeOf((*dom.HTMLElement)(nil)):
58 | in[i] = reflect.ValueOf(dom.WrapHTMLElement(args[i]))
59 | case typeOf((*dom.Node)(nil)):
60 | in[i] = reflect.ValueOf(dom.WrapNode(args[i]))
61 |
62 | // Unmarshal incoming encoded JSON into the Go type.
63 | default:
64 | p := reflect.New(t)
65 | err := json.Unmarshal([]byte(args[i].String()), p.Interface())
66 | if err != nil {
67 | panic(fmt.Errorf("jsutil: unmarshaling JSON %q into type %s failed: %v", args[i], t, err))
68 | }
69 | in[i] = reflect.Indirect(p)
70 | }
71 | }
72 | v.Call(in)
73 | }
74 | }
75 |
76 | // typeOf returns the reflect.Type of what the pointer points to.
77 | func typeOf(pointer interface{}) reflect.Type {
78 | return reflect.TypeOf(pointer).Elem()
79 | }
80 |
--------------------------------------------------------------------------------
/gopherjs_http/jsutil/v2/jsutil.go:
--------------------------------------------------------------------------------
1 | //go:build js
2 |
3 | // Package jsutil provides utility functions for interacting with
4 | // native JavaScript APIs via syscall/js API.
5 | // It has support for common types in honnef.co/go/js/dom/v2.
6 | package jsutil
7 |
8 | import (
9 | "encoding/json"
10 | "fmt"
11 | "reflect"
12 | "syscall/js"
13 |
14 | "honnef.co/go/js/dom/v2"
15 | )
16 |
17 | // Wrap returns a wrapper func that handles the conversion from native JavaScript js.Value parameters
18 | // to the following types.
19 | //
20 | // It supports js.Value (left unmodified), dom.Document, dom.Element, dom.Event, dom.HTMLElement, dom.Node.
21 | // It has to be one of those types exactly; it can't be another type that implements the interface like *dom.BasicElement.
22 | //
23 | // For other types, the input is assumed to be a JSON string which is then unmarshalled into that type.
24 | //
25 | // If the number of arguments provided to the wrapped func doesn't match
26 | // the number of arguments for original func, it panics.
27 | //
28 | // Here is example usage:
29 | //
30 | // Example
31 | //
32 | // func Handler(event dom.Event, htmlElement dom.HTMLElement, data someStruct) {
33 | // data.Foo = ... // Use event, htmlElement, data.
34 | // }
35 | //
36 | // func main() {
37 | // js.Global().Set("Handler", jsutil.Wrap(Handler))
38 | // }
39 | func Wrap(fn interface{}) js.Func {
40 | v := reflect.ValueOf(fn)
41 | return js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
42 | if len(args) != v.Type().NumIn() {
43 | panic(fmt.Errorf("wrapped %v got %v arguments, want %v", v.Type().String(), len(args), v.Type().NumIn()))
44 | }
45 | in := make([]reflect.Value, v.Type().NumIn())
46 | for i := range in {
47 | switch t := v.Type().In(i); t {
48 | // js.Value is passed through.
49 | case typeOf((*js.Value)(nil)):
50 | in[i] = reflect.ValueOf(args[i])
51 |
52 | // dom types are wrapped.
53 | case typeOf((*dom.Document)(nil)):
54 | in[i] = reflect.ValueOf(dom.WrapDocument(args[i]))
55 | case typeOf((*dom.Element)(nil)):
56 | in[i] = reflect.ValueOf(dom.WrapElement(args[i]))
57 | case typeOf((*dom.Event)(nil)):
58 | in[i] = reflect.ValueOf(dom.WrapEvent(args[i]))
59 | case typeOf((*dom.HTMLElement)(nil)):
60 | in[i] = reflect.ValueOf(dom.WrapHTMLElement(args[i]))
61 | case typeOf((*dom.Node)(nil)):
62 | in[i] = reflect.ValueOf(dom.WrapNode(args[i]))
63 |
64 | // Unmarshal incoming encoded JSON into the Go type.
65 | default:
66 | if args[i].Type() != js.TypeString {
67 | panic(fmt.Errorf("jsutil: incoming value type is %s; want a string with JSON content", args[i].Type()))
68 | }
69 | p := reflect.New(t)
70 | err := json.Unmarshal([]byte(args[i].String()), p.Interface())
71 | if err != nil {
72 | panic(fmt.Errorf("jsutil: unmarshaling JSON %q into type %s failed: %v", args[i], t, err))
73 | }
74 | in[i] = reflect.Indirect(p)
75 | }
76 | }
77 | v.Call(in)
78 | return nil
79 | })
80 | }
81 |
82 | // typeOf returns the reflect.Type of what the pointer points to.
83 | func typeOf(pointer interface{}) reflect.Type {
84 | return reflect.TypeOf(pointer).Elem()
85 | }
86 |
--------------------------------------------------------------------------------
/gopherjs_http/package.go:
--------------------------------------------------------------------------------
1 | package gopherjs_http
2 |
3 | import (
4 | "go/build"
5 | "net/http"
6 | "os"
7 | )
8 |
9 | // Package returns an http.FileSystem that contains a single file at root,
10 | // containing result of building package with importPath using GopherJS.
11 | func Package(importPath string) http.FileSystem {
12 | return packageFS{importPath: importPath}
13 | }
14 |
15 | type packageFS struct {
16 | importPath string
17 | }
18 |
19 | func (fs packageFS) Open(name string) (http.File, error) {
20 | if name != "/" {
21 | return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
22 | }
23 | p, err := build.Import(fs.importPath, "", build.FindOnly)
24 | if err != nil {
25 | return nil, &os.PathError{Op: `"go/build".Import`, Path: fs.importPath, Err: err}
26 | }
27 | return (&gopherJSFS{source: http.Dir(p.SrcRoot)}).compileGoPackage(p.ImportPath)
28 | }
29 |
--------------------------------------------------------------------------------
/gopherjs_http/vfs.go:
--------------------------------------------------------------------------------
1 | package gopherjs_http
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "os"
9 | pathpkg "path"
10 | "strings"
11 | "time"
12 | )
13 |
14 | // NewFS returns an http.FileSystem that is exactly like source, except all Go packages are compiled to JavaScript with GopherJS.
15 | //
16 | // For example:
17 | //
18 | // /mypkg/foo.go
19 | // /mypkg/bar.go
20 | //
21 | // Become replaced with:
22 | //
23 | // /mypkg/mypkg.js
24 | //
25 | // Where mypkg.js is the result of building mypkg with GopherJS.
26 | func NewFS(source http.FileSystem) http.FileSystem {
27 | return &gopherJSFS{source: source}
28 | }
29 |
30 | type gopherJSFS struct {
31 | source http.FileSystem
32 | }
33 |
34 | func (fs *gopherJSFS) Open(path string) (http.File, error) {
35 | switch dir, file := pathpkg.Split(path); {
36 | case file == pathpkg.Base(dir)+".js":
37 | return fs.compileGoPackage(dir)
38 | default:
39 | return fs.openSource(path)
40 | }
41 | }
42 |
43 | func (fs *gopherJSFS) openSource(path string) (http.File, error) {
44 | f, err := fs.source.Open(path)
45 | if err != nil {
46 | return nil, err
47 | }
48 |
49 | fi, err := f.Stat()
50 | if err != nil {
51 | f.Close()
52 | return nil, err
53 | }
54 |
55 | switch {
56 | // Files with .go and ".inc.js" extensions are consumed and no longer exist
57 | // in output filesystem.
58 | case !fi.IsDir() && pathpkg.Ext(fi.Name()) == ".go":
59 | fallthrough
60 | case !fi.IsDir() && strings.HasSuffix(fi.Name(), ".inc.js"):
61 | f.Close()
62 | return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
63 | case !fi.IsDir():
64 | return f, nil
65 | }
66 | defer f.Close()
67 |
68 | fis, err := f.Readdir(0)
69 | if err != nil {
70 | return nil, err
71 | }
72 |
73 | // Include all subfolders, non-.go files.
74 | var entries []os.FileInfo
75 | var haveGo []os.FileInfo
76 | for _, fi := range fis {
77 | switch {
78 | case !fi.IsDir() && pathpkg.Ext(fi.Name()) == ".go":
79 | haveGo = append(haveGo, fi)
80 | case !fi.IsDir() && strings.HasSuffix(fi.Name(), ".inc.js"):
81 | // TODO: Handle ".inc.js" files correctly.
82 | entries = append(entries, fi)
83 | default:
84 | entries = append(entries, fi)
85 | }
86 | }
87 |
88 | // If it has any .go files, present the Go package compiled with GopherJS as an additional virtual file.
89 | if len(haveGo) > 0 {
90 | entries = append(entries, &file{
91 | name: fi.Name() + ".js",
92 | size: 0, // TODO.
93 | modTime: time.Time{}, // TODO.
94 | })
95 | }
96 |
97 | return &dir{
98 | name: fi.Name(),
99 | entries: entries,
100 | modTime: fi.ModTime(),
101 | }, nil
102 | }
103 |
104 | func (fs *gopherJSFS) compileGoPackage(dir string) (http.File, error) {
105 | f, err := fs.source.Open(dir)
106 | if err != nil {
107 | return nil, err
108 | }
109 | defer f.Close()
110 |
111 | fi, err := f.Stat()
112 | if err != nil {
113 | return nil, err
114 | }
115 |
116 | if !fi.IsDir() {
117 | return nil, fmt.Errorf("%s is not a dir", dir)
118 | }
119 |
120 | fis, err := f.Readdir(0)
121 | if err != nil {
122 | return nil, err
123 | }
124 |
125 | var goFiles []os.FileInfo
126 | for _, f := range fis {
127 | // TODO: Use build.Import or equivalent to get GoFiles and CgoFiles. The below approximates it, but fails to use build tags correctly, etc.
128 | if f.IsDir() {
129 | continue
130 | }
131 | if pathpkg.Ext(f.Name()) != ".go" {
132 | continue
133 | }
134 | if strings.HasPrefix(f.Name(), ".") || strings.HasPrefix(f.Name(), "_") {
135 | continue
136 | }
137 | if strings.HasSuffix(f.Name(), "_test.go") {
138 | continue
139 | }
140 | goFiles = append(goFiles, f)
141 | }
142 | if len(goFiles) == 0 {
143 | return nil, fmt.Errorf("%s has no matching .go files", dir)
144 | }
145 |
146 | // TODO: Clean this up.
147 | {
148 | name := pathpkg.Base(dir) + ".js"
149 |
150 | var names []string
151 | var goReaders []io.Reader
152 | var goClosers []io.Closer
153 | for _, goFile := range goFiles {
154 | file, err := fs.source.Open(pathpkg.Join(dir, goFile.Name()))
155 | if err != nil {
156 | return nil, err
157 | }
158 | names = append(names, goFile.Name())
159 | goReaders = append(goReaders, file)
160 | goClosers = append(goClosers, file)
161 | }
162 |
163 | fmt.Printf("REBUILDING SOURCE for: %s using %+v\n", name, names)
164 | content := []byte(handleJsError(goReadersToJS(names, goReaders)))
165 |
166 | for _, closer := range goClosers {
167 | closer.Close()
168 | }
169 |
170 | return &file{
171 | name: name,
172 | size: int64(len(content)),
173 | modTime: time.Now(),
174 | Reader: bytes.NewReader(content),
175 | }, nil
176 | }
177 | }
178 |
179 | // file is an opened file instance.
180 | type file struct {
181 | name string
182 | modTime time.Time
183 | size int64
184 | *bytes.Reader
185 | }
186 |
187 | func (f *file) Readdir(count int) ([]os.FileInfo, error) {
188 | return nil, fmt.Errorf("cannot Readdir from file %s", f.name)
189 | }
190 | func (f *file) Stat() (os.FileInfo, error) { return f, nil }
191 |
192 | func (f *file) Name() string { return f.name }
193 | func (f *file) Size() int64 { return f.size }
194 | func (f *file) Mode() os.FileMode { return 0444 }
195 | func (f *file) ModTime() time.Time { return f.modTime }
196 | func (f *file) IsDir() bool { return false }
197 | func (f *file) Sys() interface{} { return nil }
198 |
199 | func (f *file) Close() error {
200 | return nil
201 | }
202 |
203 | // dir is an opened dir instance.
204 | type dir struct {
205 | name string
206 | modTime time.Time
207 | entries []os.FileInfo
208 | pos int // Position within entries for Seek and Readdir.
209 | }
210 |
211 | func (d *dir) Read([]byte) (int, error) {
212 | return 0, fmt.Errorf("cannot Read from directory %s", d.name)
213 | }
214 | func (d *dir) Close() error { return nil }
215 | func (d *dir) Stat() (os.FileInfo, error) { return d, nil }
216 |
217 | func (d *dir) Name() string { return d.name }
218 | func (d *dir) Size() int64 { return 0 }
219 | func (d *dir) Mode() os.FileMode { return 0755 | os.ModeDir }
220 | func (d *dir) ModTime() time.Time { return d.modTime }
221 | func (d *dir) IsDir() bool { return true }
222 | func (d *dir) Sys() interface{} { return nil }
223 |
224 | func (d *dir) Seek(offset int64, whence int) (int64, error) {
225 | if offset == 0 && whence == io.SeekStart {
226 | d.pos = 0
227 | return 0, nil
228 | }
229 | return 0, fmt.Errorf("unsupported Seek in directory %s", d.name)
230 | }
231 |
232 | func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
233 | if d.pos >= len(d.entries) && count > 0 {
234 | return nil, io.EOF
235 | }
236 | if count <= 0 || count > len(d.entries)-d.pos {
237 | count = len(d.entries) - d.pos
238 | }
239 | e := d.entries[d.pos : d.pos+count]
240 | d.pos += count
241 | return e, nil
242 | }
243 |
--------------------------------------------------------------------------------
/importgraphutil/importgraphutil.go:
--------------------------------------------------------------------------------
1 | // Package importgraphutil augments "golang.org/x/tools/refactor/importgraph" with a way to build graphs ignoring tests.
2 | package importgraphutil
3 |
4 | import (
5 | "go/build"
6 | "sync"
7 |
8 | "golang.org/x/tools/go/buildutil"
9 | "golang.org/x/tools/refactor/importgraph"
10 | )
11 |
12 | // BuildNoTests is like "golang.org/x/tools/refactor/importgraph".Build but doesn't consider test imports.
13 | func BuildNoTests(ctxt *build.Context) (forward, reverse importgraph.Graph, errors map[string]error) {
14 | type importEdge struct {
15 | from, to string
16 | }
17 | type pathError struct {
18 | path string
19 | err error
20 | }
21 |
22 | ch := make(chan interface{})
23 |
24 | var wg sync.WaitGroup
25 | buildutil.ForEachPackage(ctxt, func(path string, err error) {
26 | wg.Add(1)
27 | go func() {
28 | defer wg.Done()
29 | if err != nil {
30 | ch <- pathError{path, err}
31 | return
32 | }
33 | bp, err := ctxt.Import(path, "", 0)
34 | if _, ok := err.(*build.NoGoError); ok {
35 | return // empty directory is not an error
36 | }
37 | if err != nil {
38 | ch <- pathError{path, err}
39 | return
40 | }
41 | for _, imp := range bp.Imports {
42 | ch <- importEdge{path, imp}
43 | }
44 | // Ignore test imports.
45 | }()
46 | })
47 | go func() {
48 | wg.Wait()
49 | close(ch)
50 | }()
51 |
52 | forward = make(importgraph.Graph)
53 | reverse = make(importgraph.Graph)
54 |
55 | for e := range ch {
56 | switch e := e.(type) {
57 | case pathError:
58 | if errors == nil {
59 | errors = make(map[string]error)
60 | }
61 | errors[e.path] = e.err
62 |
63 | case importEdge:
64 | if e.to == "C" {
65 | continue // "C" is fake
66 | }
67 | addEdge(forward, e.from, e.to)
68 | addEdge(reverse, e.to, e.from)
69 | }
70 | }
71 |
72 | return forward, reverse, errors
73 | }
74 |
75 | func addEdge(g importgraph.Graph, from, to string) {
76 | edges := g[from]
77 | if edges == nil {
78 | edges = make(map[string]bool)
79 | g[from] = edges
80 | }
81 | edges[to] = true
82 | }
83 |
--------------------------------------------------------------------------------
/indentwriter/indentwriter.go:
--------------------------------------------------------------------------------
1 | // Package indentwriter implements an io.Writer wrapper that indents
2 | // every non-empty line with specified number of tabs.
3 | package indentwriter
4 |
5 | import (
6 | "bytes"
7 | "io"
8 | )
9 |
10 | type indentWriter struct {
11 | w io.Writer
12 | prefix []byte
13 |
14 | wroteIndent bool
15 | }
16 |
17 | // New creates a new indent writer that indents non-empty lines with indent number of tabs.
18 | func New(w io.Writer, indent int) io.Writer {
19 | return &indentWriter{
20 | w: w,
21 | prefix: bytes.Repeat([]byte{'\t'}, indent),
22 | }
23 | }
24 |
25 | func (iw *indentWriter) Write(p []byte) (n int, err error) {
26 | for i, b := range p {
27 | if b == '\n' {
28 | iw.wroteIndent = false
29 | } else {
30 | if !iw.wroteIndent {
31 | _, err = iw.w.Write(iw.prefix)
32 | if err != nil {
33 | return n, err
34 | }
35 | iw.wroteIndent = true
36 | }
37 | }
38 | _, err = iw.w.Write(p[i : i+1])
39 | if err != nil {
40 | return n, err
41 | }
42 | n++
43 | }
44 | return len(p), nil
45 | }
46 |
--------------------------------------------------------------------------------
/indentwriter/indentwriter_test.go:
--------------------------------------------------------------------------------
1 | package indentwriter_test
2 |
3 | import (
4 | "io"
5 | "os"
6 |
7 | "github.com/shurcooL/go/indentwriter"
8 | )
9 |
10 | func Example() {
11 | iw := indentwriter.New(os.Stdout, 1)
12 |
13 | io.WriteString(iw, `IndentWriter is simple Go package you can import for the following task.
14 |
15 | You take an existing io.Writer, and an integer "indent",
16 | and create this IndentWriter that implements io.Writer too, but it prepends every line with
17 | indent number of tabs.
18 |
19 | Note that only non-empty lines get indented.
20 | `)
21 |
22 | // Output:
23 | // IndentWriter is simple Go package you can import for the following task.
24 | //
25 | // You take an existing io.Writer, and an integer "indent",
26 | // and create this IndentWriter that implements io.Writer too, but it prepends every line with
27 | // indent number of tabs.
28 | //
29 | // Note that only non-empty lines get indented.
30 | //
31 | }
32 |
--------------------------------------------------------------------------------
/open/open.go:
--------------------------------------------------------------------------------
1 | // Package open offers ability to open files or URLs as if user double-clicked it in their OS.
2 | //
3 | // Deprecated: Use github.com/shurcooL/go/browser package if you need to open URLs.
4 | // It respects the BROWSER environment variable.
5 | package open
6 |
7 | import (
8 | "log"
9 | "os/exec"
10 | "runtime"
11 | )
12 |
13 | // Open opens a file (or a directory or url), just as if the user had double-clicked the file's icon.
14 | // It uses the default application, as determined by the OS.
15 | func Open(path string) {
16 | var args []string
17 | switch runtime.GOOS {
18 | case "darwin":
19 | args = []string{"open", path}
20 | case "windows":
21 | args = []string{"cmd", "/c", "start", path}
22 | default:
23 | args = []string{"xdg-open", path}
24 | }
25 | cmd := exec.Command(args[0], args[1:]...)
26 | err := cmd.Run()
27 | if err != nil {
28 | log.Println("open.Open:", err)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/openutil/openutil.go:
--------------------------------------------------------------------------------
1 | // Package openutil displays Markdown or HTML in a new browser tab.
2 | package openutil
3 |
4 | import (
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "time"
9 |
10 | "github.com/shurcooL/github_flavored_markdown/gfmstyle"
11 | "github.com/shurcooL/go/gfmutil"
12 | "github.com/shurcooL/go/open"
13 | )
14 |
15 | // TODO: This code is extremely hacky and ridden with race conditions. :(
16 | // Ideally, it should be rewritten in a clean way, or deleted.
17 | // At this time, it's needed by goimporters and goimportgraph commands.
18 |
19 | // DisplayMarkdownInBrowser displays given Markdown in a new browser window/tab.
20 | func DisplayMarkdownInBrowser(markdown []byte) {
21 | stopServerChan := make(chan struct{})
22 |
23 | handler := func(w http.ResponseWriter, req *http.Request) {
24 | gfmutil.WriteGitHubFlavoredMarkdownViaLocal(w, markdown)
25 |
26 | // TODO: A better way to fix: /assets/gfm/gfm.css Failed to load resource: net::ERR_CONNECTION_REFUSED.
27 | // HACK: Give some time for other assets to finish loading.
28 | go func() {
29 | time.Sleep(1 * time.Second)
30 | stopServerChan <- struct{}{}
31 | }()
32 | }
33 |
34 | http.HandleFunc("/index", handler)
35 | http.Handle("/assets/gfm/", http.StripPrefix("/assets/gfm", http.FileServer(gfmstyle.Assets))) // Serve the "/assets/gfm/gfm.css" file.
36 | http.Handle("/favicon.ico", http.NotFoundHandler())
37 |
38 | // TODO: Start TCP listener before launching the browser to navigate to the page (else it's a race).
39 | // TODO: Aquire a free port similarly to using ioutil.TempFile() for files.
40 | // TODO: Consider using httptest.NewServer.
41 | open.Open("http://localhost:7044/index")
42 |
43 | server := &http.Server{Addr: "localhost:7044"}
44 | go func() {
45 | <-stopServerChan
46 | err := server.Close()
47 | if err != nil {
48 | log.Println("server.Close:", err)
49 | }
50 | }()
51 | err := server.ListenAndServe()
52 | if err != http.ErrServerClosed {
53 | panic(fmt.Errorf("server.ListenAndServe: %v", err))
54 | }
55 | }
56 |
57 | // DisplayHTMLInBrowser displays the /index HTML page of given mux in a new browser window/tab.
58 | // query can be empty, otherwise it should begin with "?", like "?key=value".
59 | func DisplayHTMLInBrowser(mux *http.ServeMux, stopServerChan <-chan struct{}, query string) {
60 | // TODO: Start TCP listener before launching the browser to navigate to the page (else it's a race).
61 | // TODO: Aquire a free port similarly to using ioutil.TempFile() for files.
62 | // TODO: Consider using httptest.NewServer.
63 | open.Open("http://localhost:7044/index" + query)
64 |
65 | server := &http.Server{Addr: "localhost:7044", Handler: mux}
66 | go func() {
67 | <-stopServerChan
68 | err := server.Close()
69 | if err != nil {
70 | log.Println("server.Close:", err)
71 | }
72 | }()
73 | err := server.ListenAndServe()
74 | if err != http.ErrServerClosed {
75 | panic(fmt.Errorf("server.ListenAndServe: %v", err))
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/ospath/ospath.go:
--------------------------------------------------------------------------------
1 | // Package ospath provides utilities to get OS-specific directories.
2 | //
3 | // Deprecated: Use package [os] instead.
4 | package ospath
5 |
6 | import (
7 | "fmt"
8 | "os"
9 | "os/user"
10 | "path/filepath"
11 | "runtime"
12 | )
13 |
14 | // CacheDir tries to acquire an OS-specific app cache directory for the given importPath.
15 | // Cache directory contains the app's cached data that can be regenerated as needed.
16 | // Apps should never rely on the existence of cache files.
17 | //
18 | // It's guaranteed to be a unique directory for the importPath.
19 | // Before returning the directory's path, CacheDir creates the directory if it
20 | // doesn't already exist, so it can be used right away.
21 | //
22 | // Deprecated: Use [os.UserCacheDir] instead.
23 | func CacheDir(importPath string) (string, error) {
24 | var home string
25 | if u, err := user.Current(); err != nil {
26 | home = os.Getenv("HOME")
27 | if home == "" {
28 | return "", err
29 | }
30 | } else {
31 | home = u.HomeDir
32 | }
33 | // TODO: Support Windows in analogous ways. Also support mobile devices (iOS, Android).
34 | // Think about web? While HTML5 Local Storage could be used, it's not going to be compatible
35 | // with filepaths; so maybe consider returning a webdav.FileSystem or so instead? Needs consideration.
36 | switch {
37 | case runtime.GOOS == "darwin" && (runtime.GOARCH == "arm64" || runtime.GOARCH == "amd64"):
38 | dir := filepath.Join(home, "Library", "Caches", filepath.FromSlash(importPath))
39 | if err := os.MkdirAll(dir, 0700); err != nil {
40 | return "", err
41 | }
42 | return dir, nil
43 | case runtime.GOOS == "linux" && (runtime.GOARCH == "386" || runtime.GOARCH == "amd64"):
44 | // $HOME/.cache path is based on https://github.com/rsc/gt/blob/ee152ddc9ec2a99d345cf81b6ba0f40a83c9dd6f/main.go#L22.
45 | dir := filepath.Join(home, ".cache", filepath.FromSlash(importPath))
46 | if err := os.MkdirAll(dir, 0700); err != nil {
47 | return "", err
48 | }
49 | return dir, nil
50 | default:
51 | return "", fmt.Errorf("ospath.CacheDir not implemented for %s/%s", runtime.GOOS, runtime.GOARCH)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/osutil/environ.go:
--------------------------------------------------------------------------------
1 | // Package osutil offers a utility for manipulating a set of environment variables.
2 | package osutil
3 |
4 | import "strings"
5 |
6 | // Environ is a slice of strings representing the environment, in the form "key=value".
7 | type Environ []string
8 |
9 | // Set environment variable key to value.
10 | func (e *Environ) Set(key, value string) {
11 | for i := range *e {
12 | if strings.HasPrefix((*e)[i], key+"=") {
13 | (*e)[i] = key + "=" + value
14 | return
15 | }
16 | }
17 | // If we get here, it's because the key isn't already present, so add a new one.
18 | *e = append(*e, key+"="+value)
19 | }
20 |
21 | // Unset environment variable key.
22 | func (e *Environ) Unset(key string) {
23 | for i := range *e {
24 | if strings.HasPrefix((*e)[i], key+"=") {
25 | (*e)[i] = (*e)[len(*e)-1]
26 | *e = (*e)[:len(*e)-1]
27 | return
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/osutil/environ_test.go:
--------------------------------------------------------------------------------
1 | package osutil_test
2 |
3 | import (
4 | "os"
5 | "os/exec"
6 |
7 | "github.com/shurcooL/go/osutil"
8 | )
9 |
10 | func ExampleEnviron() {
11 | cmd := exec.Command("example")
12 | env := osutil.Environ(os.Environ())
13 | env.Set("USER", "gopher")
14 | env.Set("HOME", "/usr/gopher")
15 | env.Unset("TMPDIR")
16 | cmd.Env = env
17 | }
18 |
--------------------------------------------------------------------------------
/parserutil/parserutil.go:
--------------------------------------------------------------------------------
1 | // Package parserutil offers convenience functions for parsing Go code to AST.
2 | package parserutil
3 |
4 | import (
5 | "errors"
6 | "go/ast"
7 | "go/parser"
8 | "go/token"
9 | )
10 |
11 | // ParseStmt is a convenience function for obtaining the AST of a statement x.
12 | // The position information recorded in the AST is undefined. The filename used
13 | // in error messages is the empty string.
14 | func ParseStmt(x string) (ast.Stmt, error) {
15 | file, err := parser.ParseFile(token.NewFileSet(), "", "package p;func _(){\n//line :1\n"+x+"\n;}", 0)
16 | if err != nil {
17 | return nil, err
18 | }
19 | return file.Decls[0].(*ast.FuncDecl).Body.List[0], nil
20 | }
21 |
22 | // ParseDecl is a convenience function for obtaining the AST of a declaration x.
23 | // The position information recorded in the AST is undefined. The filename used
24 | // in error messages is the empty string.
25 | func ParseDecl(x string) (ast.Decl, error) {
26 | file, err := parser.ParseFile(token.NewFileSet(), "", "package p\n//line :1\n"+x+"\n", 0)
27 | if err != nil {
28 | return nil, err
29 | }
30 | if len(file.Decls) == 0 {
31 | return nil, errors.New("no declaration")
32 | }
33 | return file.Decls[0], nil
34 | }
35 |
--------------------------------------------------------------------------------
/parserutil/parserutil_test.go:
--------------------------------------------------------------------------------
1 | package parserutil_test
2 |
3 | import (
4 | "fmt"
5 | "go/ast"
6 | "os"
7 | "reflect"
8 | "testing"
9 |
10 | "github.com/shurcooL/go/parserutil"
11 | )
12 |
13 | func Example() {
14 | stmt, err := parserutil.ParseStmt("var x int")
15 | if err != nil {
16 | panic(err)
17 | }
18 |
19 | ast.Fprint(os.Stdout, nil, stmt, nil)
20 |
21 | // Output:
22 | // 0 *ast.DeclStmt {
23 | // 1 . Decl: *ast.GenDecl {
24 | // 2 . . Doc: nil
25 | // 3 . . TokPos: 31
26 | // 4 . . Tok: var
27 | // 5 . . Lparen: 0
28 | // 6 . . Specs: []ast.Spec (len = 1) {
29 | // 7 . . . 0: *ast.ValueSpec {
30 | // 8 . . . . Doc: nil
31 | // 9 . . . . Names: []*ast.Ident (len = 1) {
32 | // 10 . . . . . 0: *ast.Ident {
33 | // 11 . . . . . . NamePos: 35
34 | // 12 . . . . . . Name: "x"
35 | // 13 . . . . . . Obj: *ast.Object {
36 | // 14 . . . . . . . Kind: var
37 | // 15 . . . . . . . Name: "x"
38 | // 16 . . . . . . . Decl: *(obj @ 7)
39 | // 17 . . . . . . . Data: 0
40 | // 18 . . . . . . . Type: nil
41 | // 19 . . . . . . }
42 | // 20 . . . . . }
43 | // 21 . . . . }
44 | // 22 . . . . Type: *ast.Ident {
45 | // 23 . . . . . NamePos: 37
46 | // 24 . . . . . Name: "int"
47 | // 25 . . . . . Obj: nil
48 | // 26 . . . . }
49 | // 27 . . . . Values: nil
50 | // 28 . . . . Comment: nil
51 | // 29 . . . }
52 | // 30 . . }
53 | // 31 . . Rparen: 0
54 | // 32 . }
55 | // 33 }
56 | }
57 |
58 | func TestParseStmt(t *testing.T) {
59 | tests := []struct {
60 | in string
61 | want ast.Stmt
62 | wantError error
63 | }{
64 | {"", &ast.EmptyStmt{Semicolon: 32}, nil},
65 | }
66 | for _, tc := range tests {
67 | stmt, err := parserutil.ParseStmt(tc.in)
68 | if got, want := err, tc.wantError; !equalError(got, want) {
69 | t.Errorf("got error: %v, want: %v", got, want)
70 | continue
71 | }
72 | if tc.wantError != nil {
73 | continue
74 | }
75 | if got, want := stmt, tc.want; !reflect.DeepEqual(got, want) {
76 | t.Errorf("got: %v, want: %v", got, want)
77 | }
78 | }
79 | }
80 |
81 | func TestParseDecl(t *testing.T) {
82 | tests := []struct {
83 | in string
84 | want ast.Decl
85 | wantError error
86 | }{
87 | {"", nil, fmt.Errorf("no declaration")},
88 | }
89 | for _, tc := range tests {
90 | decl, err := parserutil.ParseDecl(tc.in)
91 | if got, want := err, tc.wantError; !equalError(got, want) {
92 | t.Errorf("got error: %v, want: %v", got, want)
93 | continue
94 | }
95 | if tc.wantError != nil {
96 | continue
97 | }
98 | if got, want := decl, tc.want; !reflect.DeepEqual(got, want) {
99 | t.Errorf("got: %v, want: %v", got, want)
100 | }
101 | }
102 | }
103 |
104 | // equalError reports whether errors a and b are considered equal.
105 | // They're equal if both are nil, or both are not nil and a.Error() == b.Error().
106 | func equalError(a, b error) bool {
107 | return a == nil && b == nil || a != nil && b != nil && a.Error() == b.Error()
108 | }
109 |
--------------------------------------------------------------------------------
/pipeutil/dir.go:
--------------------------------------------------------------------------------
1 | package pipeutil
2 |
3 | import "gopkg.in/pipe.v2"
4 |
5 | // OutputDir is identical to pipe.Output, except it sets the starting dir.
6 | func OutputDir(p pipe.Pipe, dir string) ([]byte, error) {
7 | outb := &pipe.OutputBuffer{}
8 | s := pipe.NewState(outb, nil)
9 | s.Dir = dir
10 | err := p(s)
11 | if err == nil {
12 | err = s.RunTasks()
13 | }
14 | return outb.Bytes(), err
15 | }
16 |
17 | // DividedOutputDir is identical to pipe.DividedOutput, except it sets the starting dir.
18 | func DividedOutputDir(p pipe.Pipe, dir string) (stdout []byte, stderr []byte, err error) {
19 | outb := &pipe.OutputBuffer{}
20 | errb := &pipe.OutputBuffer{}
21 | s := pipe.NewState(outb, errb)
22 | s.Dir = dir
23 | err = p(s)
24 | if err == nil {
25 | err = s.RunTasks()
26 | }
27 | return outb.Bytes(), errb.Bytes(), err
28 | }
29 |
--------------------------------------------------------------------------------
/pipeutil/pipeutil.go:
--------------------------------------------------------------------------------
1 | // Package pipeutil provides additional functionality for gopkg.in/pipe.v2 package.
2 | package pipeutil
3 |
4 | import (
5 | "fmt"
6 | "os"
7 | "os/exec"
8 | "sync"
9 |
10 | "gopkg.in/pipe.v2"
11 | )
12 |
13 | // ExecCombinedOutput returns a pipe that runs the named program with the given arguments,
14 | // while forwarding stderr to stdout.
15 | func ExecCombinedOutput(name string, args ...string) pipe.Pipe {
16 | return func(s *pipe.State) error {
17 | s.AddTask(&execCombinedOutputTask{name: name, args: args})
18 | return nil
19 | }
20 | }
21 |
22 | type execCombinedOutputTask struct {
23 | name string
24 | args []string
25 |
26 | m sync.Mutex
27 | p *os.Process
28 | cancel bool
29 | }
30 |
31 | func (f *execCombinedOutputTask) Run(s *pipe.State) error {
32 | f.m.Lock()
33 | if f.cancel {
34 | f.m.Unlock()
35 | return nil
36 | }
37 | cmd := exec.Command(f.name, f.args...)
38 | cmd.Dir = s.Dir
39 | cmd.Env = s.Env
40 | cmd.Stdin = s.Stdin
41 | cmd.Stdout = s.Stdout
42 | cmd.Stderr = s.Stdout
43 | err := cmd.Start()
44 | f.p = cmd.Process
45 | f.m.Unlock()
46 | if err != nil {
47 | return err
48 | }
49 | if err := cmd.Wait(); err != nil {
50 | return &execError{f.name, err}
51 | }
52 | return nil
53 | }
54 |
55 | func (f *execCombinedOutputTask) Kill() {
56 | f.m.Lock()
57 | p := f.p
58 | f.cancel = true
59 | f.m.Unlock()
60 | if p != nil {
61 | p.Kill()
62 | }
63 | }
64 |
65 | type execError struct {
66 | name string
67 | err error
68 | }
69 |
70 | func (e *execError) Error() string {
71 | return fmt.Sprintf("command %q: %v", e.name, e.err)
72 | }
73 |
--------------------------------------------------------------------------------
/printerutil/printerutil.go:
--------------------------------------------------------------------------------
1 | // Package printerutil provides formatted printing of AST nodes.
2 | package printerutil
3 |
4 | import (
5 | "bytes"
6 | "fmt"
7 | "go/printer"
8 | "go/token"
9 | )
10 |
11 | // Consistent with the default gofmt behavior.
12 | var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
13 |
14 | // SprintAst prints node, using fset, and returns it as string.
15 | func SprintAst(fset *token.FileSet, node interface{}) string {
16 | var buf bytes.Buffer
17 | config.Fprint(&buf, fset, node)
18 | return buf.String()
19 | }
20 |
21 | // SprintAstBare prints node and returns it as string.
22 | func SprintAstBare(node interface{}) string {
23 | fset := token.NewFileSet()
24 | return SprintAst(fset, node)
25 | }
26 |
27 | // PrintlnAst prints node, using fset, to stdout.
28 | func PrintlnAst(fset *token.FileSet, node interface{}) {
29 | fmt.Println(SprintAst(fset, node))
30 | }
31 |
32 | // PrintlnAstBare prints node to stdout.
33 | func PrintlnAstBare(node interface{}) {
34 | fset := token.NewFileSet()
35 | PrintlnAst(fset, node)
36 | }
37 |
--------------------------------------------------------------------------------
/reflectfind/reflectfind.go:
--------------------------------------------------------------------------------
1 | // Package reflectfind offers funcs to perform deep-search via reflect to find instances that satisfy given query.
2 | package reflectfind
3 |
4 | import "reflect"
5 |
6 | // First finds the first instances of i that satisfies query within d.
7 | func First(d interface{}, query func(i interface{}) bool) interface{} {
8 | s := state{Visited: make(map[uintptr]struct{})}
9 | return s.findFirst(reflect.ValueOf(d), query)
10 | }
11 |
12 | type state struct {
13 | Visited map[uintptr]struct{}
14 | }
15 |
16 | func (s *state) findFirst(v reflect.Value, query func(i interface{}) bool) interface{} {
17 | // TODO: Should I check v.CanInterface()? It seems like I might be able to get away without it...
18 | if query(v.Interface()) {
19 | return v.Interface()
20 | }
21 |
22 | switch v.Kind() {
23 | case reflect.Struct:
24 | for i := 0; i < v.NumField(); i++ {
25 | if q := s.findFirst(v.Field(i), query); q != nil {
26 | return q
27 | }
28 | }
29 | case reflect.Map:
30 | for _, key := range v.MapKeys() {
31 | if q := s.findFirst(v.MapIndex(key), query); q != nil {
32 | return q
33 | }
34 | }
35 | case reflect.Array, reflect.Slice:
36 | for i := 0; i < v.Len(); i++ {
37 | if q := s.findFirst(v.Index(i), query); q != nil {
38 | return q
39 | }
40 | }
41 | case reflect.Ptr:
42 | if !v.IsNil() {
43 | if _, visited := s.Visited[v.Pointer()]; !visited {
44 | s.Visited[v.Pointer()] = struct{}{}
45 | if q := s.findFirst(v.Elem(), query); q != nil {
46 | return q
47 | }
48 | }
49 | }
50 | case reflect.Interface:
51 | if !v.IsNil() {
52 | if q := s.findFirst(v.Elem(), query); q != nil {
53 | return q
54 | }
55 | }
56 | }
57 |
58 | return nil
59 | }
60 |
61 | // All finds all instances of i that satisfy query within d.
62 | func All(d interface{}, query func(i interface{}) bool) map[interface{}]struct{} {
63 | s := stateAll{state: state{Visited: make(map[uintptr]struct{})}, Found: make(map[interface{}]struct{})}
64 | s.findAll(reflect.ValueOf(d), query)
65 | return s.Found
66 | }
67 |
68 | type stateAll struct {
69 | state
70 | Found map[interface{}]struct{}
71 | }
72 |
73 | func (s *stateAll) findAll(v reflect.Value, query func(i interface{}) bool) {
74 | switch v.Kind() {
75 | case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
76 | // TODO: Instead of skipping nil values, maybe pass the info as a bool parameter to query?
77 | if v.IsNil() {
78 | return
79 | }
80 | }
81 |
82 | // TODO: Should I check v.CanInterface()? It seems like I might be able to get away without it...
83 | if query(v.Interface()) {
84 | s.Found[v.Interface()] = struct{}{}
85 | }
86 |
87 | switch v.Kind() {
88 | case reflect.Struct:
89 | for i := 0; i < v.NumField(); i++ {
90 | s.findAll(v.Field(i), query)
91 | }
92 | case reflect.Map:
93 | for _, key := range v.MapKeys() {
94 | s.findAll(v.MapIndex(key), query)
95 | }
96 | case reflect.Array, reflect.Slice:
97 | for i := 0; i < v.Len(); i++ {
98 | s.findAll(v.Index(i), query)
99 | }
100 | case reflect.Ptr:
101 | if !v.IsNil() {
102 | if _, visited := s.Visited[v.Pointer()]; !visited {
103 | s.Visited[v.Pointer()] = struct{}{}
104 | s.findAll(v.Elem(), query)
105 | }
106 | }
107 | case reflect.Interface:
108 | if !v.IsNil() {
109 | s.findAll(v.Elem(), query)
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/reflectsource/callername.go:
--------------------------------------------------------------------------------
1 | package reflectsource
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "go/ast"
7 | "io/ioutil"
8 | "runtime"
9 | "strings"
10 |
11 | "github.com/shurcooL/go/parserutil"
12 | "github.com/shurcooL/go/printerutil"
13 | "github.com/shurcooL/go/reflectfind"
14 | )
15 |
16 | // GetParentFuncAsString gets the parent func as a string.
17 | func GetParentFuncAsString() string {
18 | // TODO: Replace use of debug.Stack() with direct use of runtime package...
19 | // TODO: Use runtime.FuncForPC(runtime.Caller()).Name() to get func name if source code not found.
20 | stack := string(stack())
21 |
22 | funcName := getLine(stack, 3)
23 | funcName = funcName[1:strings.Index(funcName, ": ")]
24 | if dotPos := strings.LastIndex(funcName, "."); dotPos != -1 { // Trim package prefix.
25 | funcName = funcName[dotPos+1:]
26 | }
27 |
28 | funcArgs := getLine(stack, 5)
29 | funcArgs = funcArgs[strings.Index(funcArgs, ": ")+len(": "):]
30 | funcArgs = funcArgs[strings.Index(funcArgs, "(") : strings.LastIndex(funcArgs, ")")+len(")")] // TODO: This may fail if there are 2+ func calls on one line.
31 |
32 | return funcName + funcArgs
33 | }
34 |
35 | // GetParentFuncArgsAsString gets the parent func with its args as a string.
36 | func GetParentFuncArgsAsString(args ...interface{}) string {
37 | // TODO: Replace use of debug.Stack() with direct use of runtime package...
38 | // TODO: Use runtime.FuncForPC(runtime.Caller()).Name() to get func name if source code not found.
39 | stack := string(stack())
40 |
41 | funcName := getLine(stack, 3)
42 | funcName = funcName[1:strings.Index(funcName, ": ")]
43 | if dotPos := strings.LastIndex(funcName, "."); dotPos != -1 { // Trim package prefix.
44 | funcName = funcName[dotPos+1:]
45 | }
46 |
47 | funcArgs := "("
48 | for i, arg := range args {
49 | // TODO: Add arg names. Maybe not?
50 | if i != 0 {
51 | funcArgs += ", "
52 | }
53 | funcArgs += fmt.Sprintf("%#v", arg) // TODO: Maybe use goon instead. Need to move elsewhere to avoid import cycle.
54 | }
55 | funcArgs += ")"
56 |
57 | return funcName + funcArgs
58 | }
59 |
60 | // GetExprAsString gets the expression as a string.
61 | func GetExprAsString(_ interface{}) string {
62 | return GetParentArgExprAsString(0)
63 | }
64 |
65 | func getParent2ArgExprAllAsAst() []ast.Expr {
66 | // TODO: Replace use of debug.Stack() with direct use of runtime package...
67 | stack := string(stack())
68 |
69 | // TODO: Bounds error checking, get rid of GetLine gists, etc.
70 | parentName := getLine(stack, 5)
71 | if !strings.Contains(parentName, ": ") {
72 | // TODO: This happens when source file isn't present in same location as when built. See if can do anything better
73 | // via direct use of runtime package (instead of debug.Stack(), which will exclude any func names)...
74 | return nil
75 | }
76 | parentName = parentName[1:strings.Index(parentName, ": ")]
77 | if dotPos := strings.LastIndex(parentName, "."); dotPos != -1 { // Trim package prefix.
78 | parentName = parentName[dotPos+1:]
79 | }
80 |
81 | str := getLine(stack, 7)
82 | str = str[strings.Index(str, ": ")+len(": "):]
83 | p, err := parserutil.ParseStmt(str)
84 | if err != nil {
85 | return nil
86 | }
87 |
88 | innerQuery := func(i interface{}) bool {
89 | if ident, ok := i.(*ast.Ident); ok && ident.Name == parentName {
90 | return true
91 | }
92 | return false
93 | }
94 |
95 | query := func(i interface{}) bool {
96 | if c, ok := i.(*ast.CallExpr); ok && nil != reflectfind.First(c.Fun, innerQuery) {
97 | return true
98 | }
99 | return false
100 | }
101 | callExpr, _ := reflectfind.First(p, query).(*ast.CallExpr)
102 |
103 | if callExpr == nil {
104 | return nil
105 | }
106 | return callExpr.Args
107 | }
108 |
109 | // GetParentArgExprAsString gets the argIndex argument expression of parent func call as a string.
110 | func GetParentArgExprAsString(argIndex uint32) string {
111 | args := getParent2ArgExprAllAsAst()
112 | if args == nil {
113 | return ""
114 | }
115 | if argIndex >= uint32(len(args)) {
116 | return ""
117 | }
118 |
119 | return printerutil.SprintAstBare(args[argIndex])
120 | }
121 |
122 | // GetParentArgExprAllAsString gets all argument expressions of parent func call as a string.
123 | func GetParentArgExprAllAsString() []string {
124 | args := getParent2ArgExprAllAsAst()
125 | if args == nil {
126 | return nil
127 | }
128 |
129 | out := make([]string, len(args))
130 | for i := range args {
131 | out[i] = printerutil.SprintAstBare(args[i])
132 | }
133 | return out
134 | }
135 |
136 | func getMySecondArgExprAsString(int, int) string {
137 | return GetParentArgExprAsString(1)
138 | }
139 |
140 | func getLine(s string, lineIndex int) string {
141 | return strings.Split(s, "\n")[lineIndex]
142 | }
143 |
144 | var (
145 | dunno = []byte("???")
146 | centerDot = []byte("·")
147 | dot = []byte(".")
148 | slash = []byte("/")
149 | )
150 |
151 | // stack returns a formatted stack trace of the goroutine that calls it.
152 | // For each routine, it includes the source line information and PC value,
153 | // then attempts to discover, for Go functions, the calling function or
154 | // method and the text of the line containing the invocation.
155 | //
156 | // It was deprecated in Go 1.5, suggested to use package runtime's Stack instead,
157 | // and replaced by another implementation in Go 1.6.
158 | //
159 | // stack implements the Go 1.5 version of debug.Stack(), skipping 1 frame,
160 | // instead of 2, since it's being called directly (rather than via debug.Stack()).
161 | func stack() []byte {
162 | buf := new(bytes.Buffer) // the returned data
163 | // As we loop, we open files and read them. These variables record the currently
164 | // loaded file.
165 | var lines [][]byte
166 | var lastFile string
167 | for i := 1; ; i++ { // Caller we care about is the user, 1 frame up
168 | pc, file, line, ok := runtime.Caller(i)
169 | if !ok {
170 | break
171 | }
172 | // Print this much at least. If we can't find the source, it won't show.
173 | fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
174 | if file != lastFile {
175 | data, err := ioutil.ReadFile(file)
176 | if err != nil {
177 | continue
178 | }
179 | lines = bytes.Split(data, []byte{'\n'})
180 | lastFile = file
181 | }
182 | line-- // in stack trace, lines are 1-indexed but our array is 0-indexed
183 | fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
184 | }
185 | return buf.Bytes()
186 | }
187 |
188 | // source returns a space-trimmed slice of the n'th line.
189 | func source(lines [][]byte, n int) []byte {
190 | if n < 0 || n >= len(lines) {
191 | return dunno
192 | }
193 | return bytes.Trim(lines[n], " \t")
194 | }
195 |
196 | // function returns, if possible, the name of the function containing the PC.
197 | func function(pc uintptr) []byte {
198 | fn := runtime.FuncForPC(pc)
199 | if fn == nil {
200 | return dunno
201 | }
202 | name := []byte(fn.Name())
203 | // The name includes the path name to the package, which is unnecessary
204 | // since the file name is already included. Plus, it has center dots.
205 | // That is, we see
206 | // runtime/debug.*T·ptrmethod
207 | // and want
208 | // *T.ptrmethod
209 | // Since the package path might contains dots (e.g. code.google.com/...),
210 | // we first remove the path prefix if there is one.
211 | if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
212 | name = name[lastslash+1:]
213 | }
214 | if period := bytes.Index(name, dot); period >= 0 {
215 | name = name[period+1:]
216 | }
217 | name = bytes.Replace(name, centerDot, dot, -1)
218 | return name
219 | }
220 |
--------------------------------------------------------------------------------
/reflectsource/callername_test.go:
--------------------------------------------------------------------------------
1 | package reflectsource
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | func ExampleGetExprAsString() {
9 | var thisIsAFunkyVarName int
10 |
11 | fmt.Println("Name of var:", GetExprAsString(thisIsAFunkyVarName))
12 |
13 | // Output:
14 | // Name of var: thisIsAFunkyVarName
15 | }
16 |
17 | func Example() {
18 | var thisIsAFunkyVarName int
19 | var name string = GetExprAsString(thisIsAFunkyVarName)
20 | fmt.Println("Name of var:", name)
21 | fmt.Println("Some func name:", GetExprAsString(strings.HasPrefix))
22 | fmt.Println("Name of second arg:", getMySecondArgExprAsString(5, thisIsAFunkyVarName))
23 |
24 | // Output:
25 | // Name of var: thisIsAFunkyVarName
26 | // Some func name: strings.HasPrefix
27 | // Name of second arg: thisIsAFunkyVarName
28 | }
29 |
30 | func Example_trickyCases() {
31 | var thisIsAFunkyVarName int
32 | fmt.Println("1 2 3 4:", getMySecondArgExprAsString(1, 2), getMySecondArgExprAsString(3, 4)) // TODO: This should be 2, 4, not 2, 2
33 | fmt.Println("Name of second arg:", // TODO: This should work
34 | getMySecondArgExprAsString(5, thisIsAFunkyVarName))
35 |
36 | // Output:
37 | // 1 2 3 4: 2 2
38 | // Name of second arg:
39 | }
40 |
--------------------------------------------------------------------------------
/reflectsource/doc.go:
--------------------------------------------------------------------------------
1 | // Package sourcereflect implements run-time source reflection, allowing a program to
2 | // look up string representation of objects from the underlying .go source files.
3 | //
4 | // Specifically, it implements ability to get name of caller funcs and their parameters.
5 | // It also implements functionality to get a string containing source code of provided func.
6 | //
7 | // In order to succeed, it expects the program's source code to be available in normal location.
8 | // It's intended to be used for development purposes, or for experimental programs.
9 | package reflectsource
10 |
--------------------------------------------------------------------------------
/reflectsource/funcsource.go:
--------------------------------------------------------------------------------
1 | package reflectsource
2 |
3 | import (
4 | "fmt"
5 | "go/ast"
6 | "go/parser"
7 | "go/token"
8 | "io/ioutil"
9 | "reflect"
10 | "runtime"
11 |
12 | "github.com/shurcooL/go/printerutil"
13 | "github.com/shurcooL/go/reflectfind"
14 | )
15 |
16 | // GetSourceAsString returns the source of the func f.
17 | func GetSourceAsString(f interface{}) string {
18 | // No need to check for f being nil, since that's handled below.
19 | fv := reflect.ValueOf(f)
20 | return GetFuncValueSourceAsString(fv)
21 | }
22 |
23 | // GetFuncValueSourceAsString returns the source of the func value fv.
24 | func GetFuncValueSourceAsString(fv reflect.Value) string {
25 | // Checking the kind catches cases where f was nil, resulting in fv being a zero Value (i.e. invalid kind),
26 | // as well as when fv is non-func.
27 | if fv.Kind() != reflect.Func {
28 | return "kind not func"
29 | }
30 | pc := fv.Pointer()
31 | if pc == 0 {
32 | return "nil"
33 | }
34 | function := runtime.FuncForPC(pc)
35 | if function == nil {
36 | return "nil"
37 | }
38 | file, line := function.FileLine(pc)
39 |
40 | var startIndex, endIndex int
41 | {
42 | b, err := ioutil.ReadFile(file)
43 | if err != nil {
44 | return ""
45 | }
46 | startIndex, endIndex = getLineStartEndIndicies(b, line-1)
47 | }
48 |
49 | fs := token.NewFileSet()
50 | fileAst, err := parser.ParseFile(fs, file, nil, 0*parser.ParseComments)
51 | if err != nil {
52 | return ""
53 | }
54 |
55 | // TODO: Consider using ast.Walk() instead of custom FindFirst()
56 | query := func(i interface{}) bool {
57 | // TODO: Factor-out the unusual overlap check
58 | if f, ok := i.(*ast.FuncLit); ok && ((startIndex <= int(f.Pos())-1 && int(f.Pos())-1 <= endIndex) || (int(f.Pos())-1 <= startIndex && startIndex <= int(f.End())-1)) {
59 | return true
60 | }
61 | return false
62 | }
63 | funcAst := reflectfind.First(fileAst, query)
64 |
65 | // If func literal wasn't found, try again looking for func declaration
66 | if funcAst == nil {
67 | query := func(i interface{}) bool {
68 | // TODO: Factor-out the unusual overlap check
69 | if f, ok := i.(*ast.FuncDecl); ok && ((startIndex <= int(f.Pos())-1 && int(f.Pos())-1 <= endIndex) || (int(f.Pos())-1 <= startIndex && startIndex <= int(f.End())-1)) {
70 | return true
71 | }
72 | return false
73 | }
74 | funcAst = reflectfind.First(fileAst, query)
75 | }
76 |
77 | if funcAst == nil {
78 | return fmt.Sprintf("", file, line)
79 | }
80 |
81 | return printerutil.SprintAst(fs, funcAst)
82 | }
83 |
--------------------------------------------------------------------------------
/reflectsource/funcsource_test.go:
--------------------------------------------------------------------------------
1 | package reflectsource
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | func ExampleGetSourceAsString() {
9 | var f func()
10 | f1 := func() {
11 | panic(123)
12 | }
13 | f2 := func() {
14 | println("Hello from anon func!") // Comments are currently not preserved.
15 | }
16 | if 5*5 > 30 {
17 | f = f1
18 | } else {
19 | f = f2
20 | }
21 |
22 | fmt.Println(GetSourceAsString(f))
23 |
24 | // Output:
25 | //func() {
26 | // println("Hello from anon func!")
27 | //}
28 | }
29 |
30 | func Example_two() {
31 | f := func(a int, b int) int {
32 | c := a + b
33 | return c
34 | }
35 |
36 | fmt.Println(GetSourceAsString(f))
37 |
38 | // Output:
39 | //func(a int, b int) int {
40 | // c := a + b
41 | // return c
42 | //}
43 | }
44 |
45 | func Example_nil() {
46 | var f func()
47 |
48 | fmt.Println(GetSourceAsString(f))
49 |
50 | // Output:
51 | //nil
52 | }
53 |
54 | func ExampleGetFuncValueSourceAsString() {
55 | f := func(a int, b int) int {
56 | c := a + b
57 | return c
58 | }
59 |
60 | fv := reflect.ValueOf(f)
61 |
62 | fmt.Println(GetFuncValueSourceAsString(fv))
63 |
64 | // Output:
65 | //func(a int, b int) int {
66 | // c := a + b
67 | // return c
68 | //}
69 | }
70 |
--------------------------------------------------------------------------------
/reflectsource/indicies.go:
--------------------------------------------------------------------------------
1 | package reflectsource
2 |
3 | import (
4 | "bytes"
5 | )
6 |
7 | // getLineStartEndIndicies gets the starting and ending caret indicies of line with specified lineIndex.
8 | // Does not include newline character.
9 | // First line has index 0.
10 | // Returns (-1, -1) if line is not found.
11 | func getLineStartEndIndicies(b []byte, lineIndex int) (startIndex, endIndex int) {
12 | index := 0
13 | for line := 0; ; line++ {
14 | lineLength := bytes.IndexByte(b[index:], '\n')
15 | if line == lineIndex {
16 | if lineLength == -1 {
17 | return index, len(b)
18 | } else {
19 | return index, index + lineLength
20 | }
21 | }
22 | if lineLength == -1 {
23 | break
24 | }
25 | index += lineLength + 1
26 | }
27 |
28 | return -1, -1
29 | }
30 |
--------------------------------------------------------------------------------
/reflectsource/indicies_test.go:
--------------------------------------------------------------------------------
1 | package reflectsource
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func Example_getLineStartEndIndicies() {
8 | b := []byte(`this
9 |
10 | this is a longer line
11 | and
12 | stuff
13 | last`)
14 |
15 | for lineIndex := 0; ; lineIndex++ {
16 | s, e := getLineStartEndIndicies(b, lineIndex)
17 | fmt.Printf("%v: [%v, %v]\n", lineIndex, s, e)
18 | if s == -1 {
19 | break
20 | }
21 | }
22 |
23 | // Output:
24 | // 0: [0, 4]
25 | // 1: [5, 5]
26 | // 2: [6, 27]
27 | // 3: [28, 31]
28 | // 4: [32, 37]
29 | // 5: [38, 42]
30 | // 6: [-1, -1]
31 | }
32 |
--------------------------------------------------------------------------------
/timeutil/timeutil.go:
--------------------------------------------------------------------------------
1 | // Package timeutil provides a func for getting start of week of given time.
2 | package timeutil
3 |
4 | import "time"
5 |
6 | // StartOfDay returns time at start of day of t.
7 | func StartOfDay(t time.Time) time.Time {
8 | return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
9 | }
10 |
11 | // StartOfWeek returns time at start of week of t.
12 | func StartOfWeek(t time.Time) time.Time {
13 | return time.Date(t.Year(), t.Month(), t.Day()-int(t.Weekday()), 0, 0, 0, 0, t.Location())
14 | }
15 |
--------------------------------------------------------------------------------
/trash/doc.go:
--------------------------------------------------------------------------------
1 | // Package trash implements functionality to move files into trash.
2 | package trash
3 |
--------------------------------------------------------------------------------
/trash/trash.go:
--------------------------------------------------------------------------------
1 | //go:build !darwin
2 |
3 | package trash
4 |
5 | import "errors"
6 |
7 | // MoveTo moves named file or directory to trash.
8 | func MoveTo(name string) error {
9 | return errors.New("MoveToTrash: not yet implemented on non-darwin")
10 | }
11 |
--------------------------------------------------------------------------------
/trash/trash_darwin.go:
--------------------------------------------------------------------------------
1 | package trash
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | // MoveTo moves named file or directory to trash.
10 | func MoveTo(name string) error {
11 | name = filepath.Clean(name)
12 | home := os.Getenv("HOME")
13 | dir, file := filepath.Split(name)
14 | target := filepath.Join(home, ".Trash", file)
15 |
16 | // TODO: If target name exists in Trash, come up with a unique one (perhaps append a timestamp) instead of overwriting.
17 | // TODO: Support OS X "Put Back". Figure out how it's done and do it.
18 |
19 | err := os.Rename(name, target)
20 | if err != nil {
21 | return err
22 | }
23 |
24 | // If directory became empty, remove it (recursively up).
25 | for {
26 | // Ensure it's an empty directory.
27 | if dirEntries, err := ioutil.ReadDir(dir); err != nil || len(dirEntries) != 0 {
28 | break
29 | }
30 |
31 | // Remove directory if it's (now) empty.
32 | err := os.Remove(dir)
33 | if err != nil {
34 | break
35 | }
36 |
37 | dir, _ = filepath.Split(dir)
38 | }
39 |
40 | return nil
41 | }
42 |
--------------------------------------------------------------------------------
/vfs/godocfs/godocfs/godocfs.go:
--------------------------------------------------------------------------------
1 | // Package godocfs implements vfs.FileSystem using a http.FileSystem.
2 | package godocfs
3 |
4 | import (
5 | "net/http"
6 | "os"
7 |
8 | "github.com/shurcooL/httpfs/vfsutil"
9 | "golang.org/x/tools/godoc/vfs"
10 | )
11 |
12 | // New returns a vfs.FileSystem adapter for the provided http.FileSystem.
13 | func New(fs http.FileSystem) vfs.FileSystem {
14 | return &godocFS{fs: fs}
15 | }
16 |
17 | type godocFS struct {
18 | fs http.FileSystem
19 | }
20 |
21 | func (v *godocFS) Open(name string) (vfs.ReadSeekCloser, error) {
22 | return v.fs.Open(name)
23 | }
24 |
25 | func (v *godocFS) Lstat(path string) (os.FileInfo, error) {
26 | return v.Stat(path)
27 | }
28 |
29 | func (v *godocFS) Stat(path string) (os.FileInfo, error) {
30 | return vfsutil.Stat(v.fs, path)
31 | }
32 |
33 | func (v *godocFS) ReadDir(path string) ([]os.FileInfo, error) {
34 | return vfsutil.ReadDir(v.fs, path)
35 | }
36 |
37 | func (*godocFS) RootType(string) vfs.RootType { return "" }
38 |
39 | func (*godocFS) String() string { return "godocfs" }
40 |
--------------------------------------------------------------------------------
/vfs/godocfs/html/vfstemplate/vfstemplate.go:
--------------------------------------------------------------------------------
1 | // Package vfstemplate offers html/template helpers that use vfs.FileSystem.
2 | package vfstemplate
3 |
4 | import (
5 | "fmt"
6 | "html/template"
7 | "path"
8 |
9 | "github.com/shurcooL/go/vfs/godocfs/path/vfspath"
10 | "golang.org/x/tools/godoc/vfs"
11 | )
12 |
13 | // ParseFiles creates a new Template if t is nil and parses the template definitions from
14 | // the named files. The returned template's name will have the (base) name and
15 | // (parsed) contents of the first file. There must be at least one file.
16 | // If an error occurs, parsing stops and the returned *Template is nil.
17 | func ParseFiles(fs vfs.FileSystem, t *template.Template, filenames ...string) (*template.Template, error) {
18 | return parseFiles(fs, t, filenames...)
19 | }
20 |
21 | // ParseGlob parses the template definitions in the files identified by the
22 | // pattern and associates the resulting templates with t. The pattern is
23 | // processed by vfspath.Glob and must match at least one file. ParseGlob is
24 | // equivalent to calling t.ParseFiles with the list of files matched by the
25 | // pattern.
26 | func ParseGlob(fs vfs.FileSystem, t *template.Template, pattern string) (*template.Template, error) {
27 | filenames, err := vfspath.Glob(fs, pattern)
28 | if err != nil {
29 | return nil, err
30 | }
31 | if len(filenames) == 0 {
32 | return nil, fmt.Errorf("vfs/html/vfstemplate: pattern matches no files: %#q", pattern)
33 | }
34 | return parseFiles(fs, t, filenames...)
35 | }
36 |
37 | // parseFiles is the helper for the method and function. If the argument
38 | // template is nil, it is created from the first file.
39 | func parseFiles(fs vfs.FileSystem, t *template.Template, filenames ...string) (*template.Template, error) {
40 | if len(filenames) == 0 {
41 | // Not really a problem, but be consistent.
42 | return nil, fmt.Errorf("vfs/html/vfstemplate: no files named in call to ParseFiles")
43 | }
44 | for _, filename := range filenames {
45 | b, err := vfs.ReadFile(fs, filename)
46 | if err != nil {
47 | return nil, err
48 | }
49 | s := string(b)
50 | name := path.Base(filename)
51 | // First template becomes return value if not already defined,
52 | // and we use that one for subsequent New calls to associate
53 | // all the templates together. Also, if this file has the same name
54 | // as t, this file becomes the contents of t, so
55 | // t, err := New(name).Funcs(xxx).ParseFiles(name)
56 | // works. Otherwise we create a new template associated with t.
57 | var tmpl *template.Template
58 | if t == nil {
59 | t = template.New(name)
60 | }
61 | if name == t.Name() {
62 | tmpl = t
63 | } else {
64 | tmpl = t.New(name)
65 | }
66 | _, err = tmpl.Parse(s)
67 | if err != nil {
68 | return nil, err
69 | }
70 | }
71 | return t, nil
72 | }
73 |
--------------------------------------------------------------------------------
/vfs/godocfs/path/vfspath/match.go:
--------------------------------------------------------------------------------
1 | // Package vfspath implements utility routines for manipulating virtual file system paths.
2 | package vfspath
3 |
4 | import (
5 | "os"
6 | "path"
7 | "sort"
8 | "strings"
9 |
10 | "golang.org/x/tools/godoc/vfs"
11 | )
12 |
13 | const separator = "/"
14 |
15 | // Glob returns the names of all files matching pattern or nil
16 | // if there is no matching file. The syntax of patterns is the same
17 | // as in path.Match. The pattern may describe hierarchical names such as
18 | // /usr/*/bin/ed.
19 | //
20 | // Glob ignores file system errors such as I/O errors reading directories.
21 | // The only possible returned error is ErrBadPattern, when pattern
22 | // is malformed.
23 | func Glob(fs vfs.FileSystem, pattern string) (matches []string, err error) {
24 | if !hasMeta(pattern) {
25 | if _, err = fs.Lstat(pattern); err != nil {
26 | return nil, nil
27 | }
28 | return []string{pattern}, nil
29 | }
30 |
31 | dir, file := path.Split(pattern)
32 | switch dir {
33 | case "":
34 | dir = "."
35 | case string(separator):
36 | // nothing
37 | default:
38 | dir = dir[0 : len(dir)-1] // chop off trailing separator
39 | }
40 |
41 | if !hasMeta(dir) {
42 | return glob(fs, dir, file, nil)
43 | }
44 |
45 | var m []string
46 | m, err = Glob(fs, dir)
47 | if err != nil {
48 | return
49 | }
50 | for _, d := range m {
51 | matches, err = glob(fs, d, file, matches)
52 | if err != nil {
53 | return
54 | }
55 | }
56 | return
57 | }
58 |
59 | // glob searches for files matching pattern in the directory dir
60 | // and appends them to matches. If the directory cannot be
61 | // opened, it returns the existing matches. New matches are
62 | // added in lexicographical order.
63 | func glob(fs vfs.FileSystem, dir, pattern string, matches []string) (m []string, e error) {
64 | m = matches
65 | fi, err := fs.Stat(dir)
66 | if err != nil {
67 | return
68 | }
69 | if !fi.IsDir() {
70 | return
71 | }
72 | fis, err := fs.ReadDir(dir)
73 | if err != nil {
74 | return
75 | }
76 |
77 | sort.Sort(byName(fis))
78 |
79 | for _, fi := range fis {
80 | n := fi.Name()
81 | matched, err := path.Match(path.Clean(pattern), n)
82 | if err != nil {
83 | return m, err
84 | }
85 | if matched {
86 | m = append(m, path.Join(dir, n))
87 | }
88 | }
89 | return
90 | }
91 |
92 | // hasMeta returns true if path contains any of the magic characters
93 | // recognized by Match.
94 | func hasMeta(path string) bool {
95 | // TODO(niemeyer): Should other magic characters be added here?
96 | return strings.ContainsAny(path, "*?[")
97 | }
98 |
99 | // byName implements sort.Interface.
100 | type byName []os.FileInfo
101 |
102 | func (f byName) Len() int { return len(f) }
103 | func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
104 | func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
105 |
--------------------------------------------------------------------------------
/vfs/godocfs/vfsutil/walk.go:
--------------------------------------------------------------------------------
1 | // Package vfsutil implements some I/O utility functions for vfs.FileSystem.
2 | package vfsutil
3 |
4 | import (
5 | "os"
6 | pathpkg "path"
7 | "path/filepath"
8 | "sort"
9 |
10 | "golang.org/x/tools/godoc/vfs"
11 | )
12 |
13 | // Walk walks the filesystem rooted at root, calling walkFn for each file or
14 | // directory in the filesystem, including root. All errors that arise visiting files
15 | // and directories are filtered by walkFn. The files are walked in lexical
16 | // order.
17 | func Walk(fs vfs.FileSystem, root string, walkFn filepath.WalkFunc) error {
18 | info, err := fs.Lstat(root)
19 | if err != nil {
20 | return walkFn(root, nil, err)
21 | }
22 | return walk(fs, root, info, walkFn)
23 | }
24 |
25 | // readDirNames reads the directory named by dirname and returns
26 | // a sorted list of directory entries.
27 | func readDirNames(fs vfs.FileSystem, dirname string) ([]string, error) {
28 | fis, err := fs.ReadDir(dirname)
29 | if err != nil {
30 | return nil, err
31 | }
32 | names := make([]string, len(fis))
33 | for i := range fis {
34 | names[i] = fis[i].Name()
35 | }
36 | sort.Strings(names)
37 | return names, nil
38 | }
39 |
40 | // walk recursively descends path, calling walkFn.
41 | func walk(fs vfs.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
42 | err := walkFn(path, info, nil)
43 | if err != nil {
44 | if info.IsDir() && err == filepath.SkipDir {
45 | return nil
46 | }
47 | return err
48 | }
49 |
50 | if !info.IsDir() {
51 | return nil
52 | }
53 |
54 | names, err := readDirNames(fs, path)
55 | if err != nil {
56 | return walkFn(path, info, err)
57 | }
58 |
59 | for _, name := range names {
60 | filename := pathpkg.Join(path, name)
61 | fileInfo, err := fs.Lstat(filename)
62 | if err != nil {
63 | if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
64 | return err
65 | }
66 | } else {
67 | err = walk(fs, filename, fileInfo, walkFn)
68 | if err != nil {
69 | if !fileInfo.IsDir() || err != filepath.SkipDir {
70 | return err
71 | }
72 | }
73 | }
74 | }
75 | return nil
76 | }
77 |
--------------------------------------------------------------------------------
/vfs/godocfs/vfsutil/walk_test.go:
--------------------------------------------------------------------------------
1 | package vfsutil_test
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 |
8 | "github.com/shurcooL/go/vfs/godocfs/vfsutil"
9 | "golang.org/x/tools/godoc/vfs"
10 | "golang.org/x/tools/godoc/vfs/mapfs"
11 | )
12 |
13 | func ExampleWalk() {
14 | var fs vfs.FileSystem = mapfs.New(map[string]string{
15 | "zzz-last-file.txt": "It should be visited last.",
16 | "a-file.txt": "It has stuff.",
17 | "another-file.txt": "Also stuff.",
18 | "folderA/entry-A.txt": "Alpha.",
19 | "folderA/entry-B.txt": "Beta.",
20 | })
21 |
22 | walkFn := func(path string, fi os.FileInfo, err error) error {
23 | if err != nil {
24 | log.Printf("can't stat file %s: %v\n", path, err)
25 | return nil
26 | }
27 | fmt.Println(path)
28 | return nil
29 | }
30 |
31 | err := vfsutil.Walk(fs, "/", walkFn)
32 | if err != nil {
33 | panic(err)
34 | }
35 |
36 | // Output:
37 | // /
38 | // /a-file.txt
39 | // /another-file.txt
40 | // /folderA
41 | // /folderA/entry-A.txt
42 | // /folderA/entry-B.txt
43 | // /zzz-last-file.txt
44 | }
45 |
--------------------------------------------------------------------------------