├── 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 | [![Go Reference](https://pkg.go.dev/badge/github.com/shurcooL/go.svg)](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 | --------------------------------------------------------------------------------