commit a701a93b815d18a755c8d69e19d2234e6d89bacd Author: jolheiser Date: Sun Aug 8 23:26:02 2021 -0500 Initial commit Signed-off-by: jolheiser diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8d4542 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +/playground* \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c2a7c4d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 John Olheiser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f420ae --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# go-playground + +A library for interacting with the [Go playground](https://play.golang.org). + +This library only supports the `share` endpoint of the playground. + +If you want to format your code, use `go fmt`. + +If you want to evaluate some code, consider using [yaegi](https://github.com/traefik/yaegi). + +## Installation + +`go install git.jojodev.com/golang/go-playground/cmd/playground@main` + +## License + +[MIT](LICENSE) \ No newline at end of file diff --git a/cmd/playground/main.go b/cmd/playground/main.go new file mode 100644 index 0000000..7c0eb15 --- /dev/null +++ b/cmd/playground/main.go @@ -0,0 +1,81 @@ +package main + +import ( + "context" + "errors" + "fmt" + "io" + "os" + + "git.jojodev.com/golang/go-playground" +) + +func main() { + contents, err := readStdin() + if err != nil { + fmt.Println(err) + return + } + + if contents == "" { + contents, err = readFile() + if err != nil { + fmt.Println(err) + return + } + } + + if contents == "" { + fmt.Println("did not receive any data from stdin or a file") + return + } + + code, err := playground.Share(context.Background(), contents) + if err != nil { + fmt.Printf("could not get share URL: %v\n", err) + return + } + + fmt.Println(code.URL()) +} + +func readStdin() (string, error) { + fi, err := os.Stdin.Stat() + if err != nil { + return "", fmt.Errorf("could not stat stdin: %v", err) + } + + if fi.Mode()&os.ModeNamedPipe != 0 { + contents, err := io.ReadAll(os.Stdin) + if err != nil { + return "", fmt.Errorf("could not read from stdin: %w", err) + } + return string(contents), nil + } + return "", nil +} + +func readFile() (string, error) { + if len(os.Args) == 1 { + return "", nil + } + + path := os.Args[1] + fi, err := os.Stat(path) + if err != nil { + return "", fmt.Errorf("could not stat %s: %v", path, err) + } + if fi.IsDir() { + return "", errors.New("can not share a directory") + } + if fi.Size() > 500000 { + return "", errors.New("this library limits the share payload to ~500kb") + } + + contents, err := os.ReadFile(path) + if err != nil { + return "", fmt.Errorf("could not read file at %s: %w", path, err) + } + + return string(contents), nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9fe9dff --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.jojodev.com/golang/go-playground + +go 1.16 diff --git a/playground.go b/playground.go new file mode 100644 index 0000000..d80419f --- /dev/null +++ b/playground.go @@ -0,0 +1,83 @@ +package playground + +import ( + "context" + "crypto/md5" + "fmt" + "io" + "net/http" + "os" + "strings" +) + +var shareURL = "https://play.golang.org/share" + +var cache = make(map[string]Code) + +// Code is a playground code +type Code string + +func (c Code) URL() string { + return fmt.Sprintf("https://play.golang.org/p/%s", c) +} + +// Share creates a share link from text +func Share(ctx context.Context, contents string) (Code, error) { + return share(ctx, contents) +} + +// ShareFile creates a share link from a file +func ShareFile(ctx context.Context, path string) (Code, error) { + stat, err := os.Stat(path) + if err != nil { + return "", fmt.Errorf("could not stat file: %w", err) + } + + if stat.IsDir() { + return "", fmt.Errorf("%s is a directory, not a file", path) + } + + if !strings.HasSuffix(stat.Name(), ".go") { + return "", fmt.Errorf("%s is not a Go file", path) + } + + fi, err := os.Open(path) + if err != nil { + return "", fmt.Errorf("could not open file: %w", err) + } + defer fi.Close() + + contents, err := io.ReadAll(fi) + if err != nil { + return "", fmt.Errorf("could not read file: %w", err) + } + + return share(ctx, string(contents)) +} + +func share(ctx context.Context, contents string) (Code, error) { + hash := fmt.Sprintf("%x", md5.Sum([]byte(contents))) + if link, ok := cache[hash]; ok { + return link, nil + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, shareURL, strings.NewReader(contents)) + if err != nil { + return "", fmt.Errorf("could not create request: %w", err) + } + req.Header.Set("User-Agent", "git.jojodev.com/golang/go-playground") + + res, err := http.DefaultClient.Do(req) + if err != nil { + return "", fmt.Errorf("could not create share link: %w", err) + } + defer res.Body.Close() + + b, err := io.ReadAll(res.Body) + if err != nil { + return "", fmt.Errorf("could not read response body: %w", err) + } + + cache[hash] = Code(b) + return cache[hash], nil +} diff --git a/playground_test.go b/playground_test.go new file mode 100644 index 0000000..4fe298e --- /dev/null +++ b/playground_test.go @@ -0,0 +1,41 @@ +package playground + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestShare(t *testing.T) { + var resp string + contents := "test" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp += "test-" + _, _ = w.Write([]byte(resp)) + })) + shareURL = server.URL + + link, err := Share(context.Background(), contents) + if err != nil { + t.Log(err) + t.FailNow() + } + + if !strings.EqualFold(link, resp) { + t.Logf("Expected `%s` but got `%s`\n", resp, link) + t.FailNow() + } + + link2, err := Share(context.Background(), contents) + if err != nil { + t.Log(err) + t.FailNow() + } + + if !strings.EqualFold(link2, "test-") { + t.Logf("Expected `test-` but got `%s`\n", link2) + t.FailNow() + } +}