├── LICENSE ├── README.md ├── backend.go ├── client.go ├── cmd └── gp │ ├── main.go │ └── usage.go ├── download.go ├── format.go ├── go.mod ├── go.sum ├── httpclient.go ├── run.go ├── share.go └── version.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Takuya Ueda 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 Playground Client 2 | 3 | This is a client of [The Go Playground](https://go.dev/play). 4 | 5 | ## Use as CLI 6 | 7 | ### Install 8 | 9 | ``` 10 | $ go install github.com/tenntenn/goplayground/cmd/gp@latest 11 | ``` 12 | 13 | ### Usage 14 | 15 | ``` 16 | $ gp help 17 | ``` 18 | 19 | ### Run 20 | 21 | ``` 22 | $ gp run main.go 23 | ``` 24 | 25 | ``` 26 | $ gp run a.go b.go 27 | ``` 28 | 29 | ``` 30 | $ find . -type f | xargs gp run 31 | ``` 32 | 33 | ``` 34 | $ find . -type f -not -path '*/\.*' | xargs gp run 35 | ``` 36 | 37 | ### Format 38 | 39 | ``` 40 | $ gp format [-imports] main.go 41 | ``` 42 | 43 | ``` 44 | $ gp format [-imports] -output main.go main.go 45 | ``` 46 | 47 | ``` 48 | $ gp format a.go b.go 49 | ``` 50 | 51 | ``` 52 | $ find . -type f | xargs gp format 53 | ``` 54 | 55 | ``` 56 | $ find . -type f -not -path '*/\.*' | xargs gp format 57 | ``` 58 | 59 | ### Share 60 | 61 | ``` 62 | $ gp share main.go 63 | ``` 64 | 65 | ``` 66 | $ gp share a.go b.go 67 | ``` 68 | 69 | ``` 70 | $ find . -type f | xargs gp share 71 | ``` 72 | 73 | ``` 74 | $ find . -type f -not -path '*/\.*' | xargs gp share 75 | ``` 76 | 77 | ### Download 78 | 79 | ``` 80 | $ gp download https://go.dev/play/p/sTkdodLtokQ 81 | ``` 82 | 83 | ``` 84 | $ gp dl https://play.golang.org/p/sTkdodLtokQ 85 | ``` 86 | 87 | ``` 88 | $ gp dl -dldir=output https://go.dev/play/p/sTkdodLtokQ 89 | ``` 90 | 91 | ## Version 92 | 93 | `version` prints Go version of playground. 94 | 95 | ``` 96 | $ gp version 97 | Version: go1.17.5 98 | Release: go1.17 99 | Name: Go 1.17 100 | ``` 101 | 102 | ``` 103 | $ gp version -backend gotip 104 | Version: devel go1.18-2c58bb2e42 Wed Jan 5 09:50:29 2022 +0000 105 | Release: go1.18 106 | Name: Go dev branch 107 | ``` 108 | 109 | ## With Go dev branch 110 | 111 | ``` 112 | $ gp format -backend gotip example.go 113 | $ gp run -backend gotip example.go 114 | $ gp share -backend gotip example.go 115 | $ gp download -backend gotip hYtdQPeKUC3 116 | ``` 117 | 118 | ## Use as a libary 119 | 120 | See: https://pkg.go.dev/github.com/tenntenn/goplayground 121 | -------------------------------------------------------------------------------- /backend.go: -------------------------------------------------------------------------------- 1 | package goplayground 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | // Backend indicates run Go environment. 9 | type Backend string 10 | 11 | const ( 12 | // BackendDefault indicates default environment (latest release version). 13 | BackendDefault Backend = "" 14 | // BackendGotip indicates using the develop branch. 15 | BackendGotip Backend = "gotip" 16 | ) 17 | 18 | // String implements flag.Value.String. 19 | func (b Backend) String() string { 20 | return string(b) 21 | } 22 | 23 | // Set implements flag.Value.Set. 24 | func (b *Backend) Set(s string) error { 25 | switch Backend(s) { 26 | case BackendDefault, BackendGotip: 27 | *b = Backend(s) 28 | return nil 29 | default: 30 | return fmt.Errorf("unexpected backend: %s", s) 31 | } 32 | } 33 | 34 | var _ flag.Value = (*Backend)(nil) 35 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package goplayground 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | ) 12 | 13 | const ( 14 | // FrontBaseURL is frontend of the Go Playground. 15 | FrontBaseURL = "https://go.dev/play" 16 | // BaseURL is the default base URL of the Go Playground. 17 | BaseURL = "https://play.golang.org" 18 | // Deprecated: Go2GoBaseURL is the base URL of go2goplay.golang.org. 19 | Go2GoBaseURL = "https://go2goplay.golang.org" 20 | // Version is version of using Go Playground. 21 | Version = "2" 22 | ) 23 | 24 | // Client is a client of Go Playground. 25 | // If BaseURL is empty, Client uses default BaseURL. 26 | // HTTPClient can be set instead of http.DefaultClient. 27 | type Client struct { 28 | FrontBaseURL string 29 | BaseURL string 30 | Backend Backend 31 | HTTPClient HTTPClient 32 | } 33 | 34 | func (cli *Client) baseURL() string { 35 | baseURL := BaseURL 36 | if cli.BaseURL != "" { 37 | baseURL = cli.BaseURL 38 | } 39 | 40 | if cli.Backend != BackendGotip { 41 | return baseURL 42 | } 43 | 44 | urlBase, err := url.Parse(baseURL) 45 | if err != nil { 46 | return baseURL 47 | } 48 | 49 | urlBase.Host = cli.Backend.String() + urlBase.Host 50 | 51 | return urlBase.String() 52 | } 53 | 54 | func (cli *Client) frontBaseURL() string { 55 | if cli.FrontBaseURL != "" { 56 | return cli.FrontBaseURL 57 | } 58 | return FrontBaseURL 59 | } 60 | 61 | func (cli *Client) httpClient() HTTPClient { 62 | if cli.HTTPClient != nil { 63 | return cli.HTTPClient 64 | } 65 | return http.DefaultClient 66 | } 67 | 68 | func srcToString(src interface{}) (string, error) { 69 | switch src := src.(type) { 70 | case io.Reader: 71 | bs, err := ioutil.ReadAll(src) 72 | if err != nil { 73 | return "", err 74 | } 75 | return srcToString(bs) 76 | case []byte: 77 | return string(src), nil 78 | case string: 79 | return src, nil 80 | } 81 | return "", errors.New("does not support src type") 82 | } 83 | 84 | func srcToReader(src interface{}) (io.Reader, error) { 85 | switch src := src.(type) { 86 | case io.Reader: 87 | return src, nil 88 | case []byte: 89 | return bytes.NewReader(src), nil 90 | case string: 91 | return strings.NewReader(src), nil 92 | } 93 | return nil, errors.New("does not support src type") 94 | } 95 | -------------------------------------------------------------------------------- /cmd/gp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "net/url" 11 | "os" 12 | "path" 13 | "path/filepath" 14 | "strings" 15 | "time" 16 | 17 | "github.com/pkg/browser" 18 | "github.com/tenntenn/goplayground" 19 | "golang.org/x/tools/txtar" 20 | ) 21 | 22 | func main() { 23 | if len(os.Args) < 2 { 24 | fmt.Fprintln(os.Stderr, "subcomand (run/share/format/version/help) should be given") 25 | os.Exit(1) 26 | } 27 | 28 | cmdname := strings.Join(os.Args[:1], " ") 29 | fset := flag.NewFlagSet(cmdname, flag.ExitOnError) 30 | 31 | fset.Usage = usage 32 | 33 | var ( 34 | go2go, asJSON, imports, open bool 35 | dldir string 36 | backend goplayground.Backend 37 | outputpath string 38 | ) 39 | fset.BoolVar(&go2go, "go2", false, "Deprecated: use go2goplay.golang.org") 40 | fset.BoolVar(&asJSON, "json", false, "output as JSON for run or format") 41 | fset.BoolVar(&imports, "imports", false, "use goimports for format") 42 | fset.BoolVar(&open, "open", false, "open url in browser for share") 43 | fset.StringVar(&dldir, "dldir", "", "output directory for download") 44 | fset.Var(&backend, "backend", `go version: empty is release version and "gotip" is the developer branch`) 45 | fset.StringVar(&outputpath, "output", "", "output file path for format") 46 | fset.Parse(os.Args[2:]) 47 | 48 | p := &playground{ 49 | cli: &goplayground.Client{ 50 | Backend: backend, 51 | }, 52 | asJSON: asJSON, 53 | imports: imports, 54 | open: open, 55 | dldir: dldir, 56 | path: outputpath, 57 | } 58 | 59 | if go2go { 60 | fmt.Fprintln(os.Stderr, "The option -go2 is deprecated.") 61 | fmt.Fprintln(os.Stderr, "Please use -backend=gotip instead of it.") 62 | os.Exit(1) 63 | } 64 | 65 | switch os.Args[1] { 66 | case "run": 67 | if err := p.run(fset.Args()...); err != nil { 68 | fmt.Fprintln(os.Stderr, "Error:", err) 69 | os.Exit(1) 70 | } 71 | case "fmt", "format": 72 | if err := p.format(fset.Args()...); err != nil { 73 | fmt.Fprintln(os.Stderr, "Error:", err) 74 | os.Exit(1) 75 | } 76 | case "share": 77 | if err := p.share(fset.Args()...); err != nil { 78 | fmt.Fprintln(os.Stderr, "Error:", err) 79 | os.Exit(1) 80 | } 81 | case "dl", "download": 82 | if err := p.download(fset.Args()); err != nil { 83 | fmt.Fprintln(os.Stderr, "Error:", err) 84 | os.Exit(1) 85 | } 86 | case "version": 87 | if err := p.version(); err != nil { 88 | fmt.Fprintln(os.Stderr, "Error:", err) 89 | os.Exit(1) 90 | } 91 | case "-h", "help": 92 | help(fset.Arg(0)) 93 | default: 94 | fmt.Fprintln(os.Stderr, "does not support subcomand", os.Args[1]) 95 | fset.Usage() 96 | os.Exit(1) 97 | } 98 | } 99 | 100 | func toReader(paths ...string) (io.Reader, error) { 101 | if len(paths) == 0 { 102 | return os.Stdin, nil 103 | } 104 | 105 | if len(paths) == 1 { 106 | data, err := ioutil.ReadFile(paths[0]) 107 | if err != nil { 108 | return nil, fmt.Errorf("cannot read file (%s): %w", paths[0], err) 109 | } 110 | return bytes.NewReader(data), nil 111 | } 112 | 113 | var a txtar.Archive 114 | for _, p := range paths { 115 | data, err := ioutil.ReadFile(p) 116 | if err != nil { 117 | return nil, fmt.Errorf("cannot read file (%s): %w", p, err) 118 | } 119 | a.Files = append(a.Files, txtar.File{ 120 | Name: filepath.ToSlash(filepath.Clean(p)), 121 | Data: data, 122 | }) 123 | } 124 | 125 | return bytes.NewReader(txtar.Format(&a)), nil 126 | } 127 | 128 | type playground struct { 129 | cli *goplayground.Client 130 | asJSON bool 131 | imports bool 132 | open bool 133 | dldir string 134 | path string 135 | } 136 | 137 | func (p *playground) run(paths ...string) error { 138 | src, err := toReader(paths...) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | r, err := p.cli.Run(src) 144 | if err != nil { 145 | return fmt.Errorf("run: %w", err) 146 | } 147 | 148 | if p.asJSON { 149 | if err := json.NewEncoder(os.Stdout).Encode(r); err != nil { 150 | return fmt.Errorf("result of run cannot encode as JSON: %w", err) 151 | } 152 | return nil 153 | } 154 | 155 | if r.Errors != "" { 156 | fmt.Fprintln(os.Stderr, r.Errors) 157 | return nil 158 | } 159 | 160 | for i := range r.Events { 161 | time.Sleep(r.Events[i].Delay) 162 | switch r.Events[i].Kind { 163 | case "stdout": 164 | fmt.Print(r.Events[i].Message) 165 | case "stderr": 166 | fmt.Fprint(os.Stderr, r.Events[i].Message) 167 | } 168 | } 169 | 170 | return nil 171 | } 172 | 173 | func (p *playground) format(paths ...string) error { 174 | src, err := toReader(paths...) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | r, err := p.cli.Format(src, p.imports) 180 | if err != nil { 181 | return fmt.Errorf("format: %w", err) 182 | } 183 | 184 | if p.asJSON { 185 | if err := json.NewEncoder(os.Stdout).Encode(r); err != nil { 186 | return fmt.Errorf("result of format cannot encode as JSON: %w", err) 187 | } 188 | return nil 189 | } 190 | 191 | if r.Error != "" { 192 | fmt.Fprintln(os.Stderr, r.Error) 193 | return nil 194 | } 195 | 196 | if p.path != "" { 197 | if err := ioutil.WriteFile(p.path, []byte(r.Body), os.ModePerm); err != nil { 198 | return fmt.Errorf("failed to write file: %w", err) 199 | } 200 | } 201 | fmt.Println(r.Body) 202 | return nil 203 | } 204 | 205 | func (p *playground) share(paths ...string) error { 206 | 207 | src, err := toReader(paths...) 208 | if err != nil { 209 | return err 210 | } 211 | 212 | shareURL, err := p.cli.Share(src) 213 | if err != nil { 214 | return fmt.Errorf("share: %w", err) 215 | } 216 | 217 | if p.cli.Backend != goplayground.BackendDefault { 218 | params := shareURL.Query() 219 | params.Set("v", p.cli.Backend.String()) 220 | shareURL.RawQuery = params.Encode() 221 | } 222 | 223 | if p.open { 224 | if err = browser.OpenURL(shareURL.String()); err != nil { 225 | return err 226 | } 227 | } 228 | 229 | fmt.Println(shareURL) 230 | 231 | return nil 232 | } 233 | 234 | func toHashOrURL(r io.Reader) (string, error) { 235 | b, err := ioutil.ReadAll(r) 236 | if err != nil { 237 | return "", fmt.Errorf("cannot read hash or URL: %w", err) 238 | } 239 | return strings.TrimSpace(string(b)), nil 240 | } 241 | 242 | func (p *playground) download(args []string) error { 243 | var hashOrURL string 244 | if len(args) <= 0 { 245 | s, err := toHashOrURL(os.Stdin) 246 | if err != nil { 247 | return fmt.Errorf("download: %w", err) 248 | } 249 | hashOrURL = s 250 | } else { 251 | hashOrURL = args[0] 252 | } 253 | 254 | var buf bytes.Buffer 255 | if err := p.cli.Download(&buf, hashOrURL); err != nil { 256 | return fmt.Errorf("download: %w", err) 257 | } 258 | 259 | if p.dldir == "" { 260 | if _, err := io.Copy(os.Stdout, &buf); err != nil { 261 | return fmt.Errorf("download: %w", err) 262 | } 263 | return nil 264 | } 265 | 266 | data := buf.Bytes() 267 | a := txtar.Parse(data) 268 | if len(a.Files) == 0 { 269 | fname := hashOrURL 270 | dlURL, err := url.Parse(fname) 271 | if err == nil { // hashOrURL is URL 272 | fname = path.Base(dlURL.Path) 273 | if !strings.HasSuffix(fname, ".go") { 274 | fname += ".go" 275 | } 276 | } 277 | 278 | f, err := os.Create(filepath.Join(p.dldir, fname)) 279 | if err != nil { 280 | return fmt.Errorf("download: %w", err) 281 | } 282 | 283 | if _, err := io.Copy(f, bytes.NewReader(data)); err != nil { 284 | return fmt.Errorf("download: %w", err) 285 | } 286 | 287 | if err := f.Close(); err != nil { 288 | return fmt.Errorf("download: %w", err) 289 | } 290 | 291 | return nil 292 | } 293 | 294 | if v := bytes.TrimSpace(a.Comment); len(v) > 0 { 295 | a.Files = append([]txtar.File{txtar.File{ 296 | Name: "prog.go", 297 | Data: a.Comment, 298 | }}, a.Files...) 299 | a.Comment = nil 300 | } 301 | 302 | for _, f := range a.Files { 303 | fpath := filepath.Join(p.dldir, filepath.FromSlash(f.Name)) 304 | fmt.Printf("output %s ... ", fpath) 305 | 306 | if err := os.MkdirAll(filepath.Dir(fpath), 0o777); err != nil { 307 | return fmt.Errorf("download: %w", err) 308 | } 309 | 310 | dst, err := os.Create(fpath) 311 | if err != nil { 312 | return fmt.Errorf("download: %w", err) 313 | } 314 | 315 | if _, err := io.Copy(dst, bytes.NewReader(f.Data)); err != nil { 316 | return fmt.Errorf("download: %w", err) 317 | } 318 | 319 | if err := dst.Close(); err != nil { 320 | return fmt.Errorf("download: %w", err) 321 | } 322 | 323 | fmt.Println("ok") 324 | } 325 | 326 | return nil 327 | } 328 | 329 | func (p *playground) version() error { 330 | r, err := p.cli.Version() 331 | if err != nil { 332 | return fmt.Errorf("version: %w", err) 333 | } 334 | 335 | fmt.Println("Version:", r.Version) 336 | fmt.Println("Release:", r.Release) 337 | fmt.Println("Name:", r.Name) 338 | 339 | return nil 340 | } 341 | 342 | func help(cmd string) { 343 | switch cmd { 344 | case "run": 345 | usageRun() 346 | case "format": 347 | usageFormat() 348 | case "share": 349 | usageShare() 350 | case "download": 351 | usageDownload() 352 | default: 353 | usage() 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /cmd/gp/usage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func usage() { 6 | fmt.Println(`gp is client of the Go Playground. 7 | 8 | Usage: 9 | 10 | gp command [arguments] 11 | 12 | The commands are: 13 | run compiles and runs on the Go Playground 14 | format formats Go code on the Go Playground 15 | share generates share URL on the Go Playground 16 | download downloads Go code specified by hash or share URL 17 | version prints Go version of the Go Playground 18 | help prints this help 19 | 20 | Use "go help [command]" for more information about a command.`) 21 | } 22 | 23 | func usageRun() { 24 | fmt.Println(`usage: gp run [-json] [gofile] 25 | 26 | "run" compiles and runs on the Go Playground. 27 | If [gofile] is not specify, it compiles and runs from stdin. 28 | 29 | The flags are: 30 | -json output result of run as JSON`) 31 | } 32 | 33 | func usageFormat() { 34 | fmt.Println(`usage: gp format [-json] [gofile] 35 | 36 | "format" formats Go code on the Go Playground. 37 | If [gofile] is not specify, it compiles and runs from stdin. 38 | 39 | The flags are: 40 | -json output result of run as JSON`) 41 | } 42 | 43 | func usageShare() { 44 | fmt.Println(`usage: goplayground share [gofile] 45 | 46 | "share" generates share URL on the Go Playground. 47 | If [gofile] is not specify, it compiles and runs from stdin. 48 | 49 | The flags are: 50 | -open open url in browser for share`) 51 | } 52 | 53 | func usageDownload() { 54 | fmt.Println(`usage: gp download [hash|share URL] 55 | 56 | "download" downloads Go code corresponds to given hash or URL. 57 | If hash or share URL is not specify, it compiles and runs from stdin.`) 58 | } 59 | -------------------------------------------------------------------------------- /download.go: -------------------------------------------------------------------------------- 1 | package goplayground 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | "path" 9 | "strings" 10 | ) 11 | 12 | // Download downloads source code hosted on Playground. 13 | // The source would be written into w. 14 | func (cli *Client) Download(w io.Writer, hashOrURL string) error { 15 | dlURL := cli.createDownloadURL(hashOrURL) 16 | req, err := http.NewRequest(http.MethodGet, dlURL, nil) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | resp, err := cli.httpClient().Do(req) 22 | if err != nil { 23 | return err 24 | } 25 | defer resp.Body.Close() 26 | 27 | if resp.StatusCode != http.StatusOK { 28 | return fmt.Errorf("cannot download %s with %s", dlURL, resp.Status) 29 | } 30 | 31 | if _, err := io.Copy(w, resp.Body); err != nil { 32 | return err 33 | } 34 | 35 | return nil 36 | } 37 | 38 | func (cli *Client) createDownloadURL(hashOrURL string) string { 39 | switch { 40 | case strings.HasPrefix(hashOrURL, cli.baseURL()+"/p/"): 41 | return hashOrURL + ".go" 42 | case strings.HasPrefix(hashOrURL, cli.frontBaseURL()+"/p/"): 43 | dlURL, err := url.Parse(hashOrURL) 44 | if err == nil { 45 | hash := path.Base(dlURL.Path) 46 | if !strings.HasSuffix(hash, ".go") { 47 | hash += ".go" 48 | } 49 | return cli.baseURL() + "/p/" + hash 50 | } 51 | } 52 | // hash 53 | return cli.baseURL() + "/p/" + hashOrURL + ".go" 54 | } 55 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | package goplayground 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "net/url" 7 | ) 8 | 9 | // FormatResult is result of Client.Format. 10 | type FormatResult struct { 11 | // Body is the formatted source code. 12 | Body string 13 | // Error is a gofmt error. 14 | Error string 15 | } 16 | 17 | // Format formats the given src by gofmt or goimports. 18 | // src can be set string, []byte and io.Reader value. 19 | // If imports is true, Format formats and imports unimport packages with goimports. 20 | func (cli *Client) Format(src interface{}, imports bool) (*FormatResult, error) { 21 | values := url.Values{} 22 | if imports { 23 | values.Set("imports", "true") 24 | } 25 | body, err := srcToString(src) 26 | if err != nil { 27 | return nil, err 28 | } 29 | values.Set("body", body) 30 | 31 | req, err := http.NewRequest(http.MethodPost, cli.baseURL()+"/fmt?"+values.Encode(), nil) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | resp, err := cli.httpClient().Do(req) 37 | if err != nil { 38 | return nil, err 39 | } 40 | defer resp.Body.Close() 41 | 42 | var result FormatResult 43 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 44 | return nil, err 45 | } 46 | 47 | return &result, nil 48 | } 49 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/goplayground 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 7 | golang.org/x/tools v0.1.8 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= 2 | github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= 3 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 4 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 5 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 6 | golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 7 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 8 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 9 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 10 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 11 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 12 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 13 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 15 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 16 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 18 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 19 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 20 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 21 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 22 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 23 | golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= 24 | golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= 25 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 26 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 27 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 28 | -------------------------------------------------------------------------------- /httpclient.go: -------------------------------------------------------------------------------- 1 | package goplayground 2 | 3 | import "net/http" 4 | 5 | // HTTPClient is an interface of minimum HTTP client. 6 | // net/http.Client implements this interface. 7 | type HTTPClient interface { 8 | // Do method send a HTTP request. 9 | Do(*http.Request) (*http.Response, error) 10 | } 11 | 12 | // HTTPClientFunc implements HTTPClient. 13 | type HTTPClientFunc func(*http.Request) (*http.Response, error) 14 | 15 | // Do implements HTTPClient.Do. 16 | func (f HTTPClientFunc) Do(req *http.Request) (*http.Response, error) { 17 | return f(req) 18 | } 19 | -------------------------------------------------------------------------------- /run.go: -------------------------------------------------------------------------------- 1 | package goplayground 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "net/url" 7 | "time" 8 | ) 9 | 10 | // RunResult is result of Client.Run. 11 | type RunResult struct { 12 | // Errors is compile or runtime error on Go Playground. 13 | Errors string 14 | // Events has output events on Go Playground. 15 | Events []*RunEvent 16 | } 17 | 18 | // RunEvent represents output events to stdout or stderr of Client.Run. 19 | type RunEvent struct { 20 | // Message is a message which is outputed to stdout or stderr. 21 | Message string 22 | // Kind has stdout or stderr value. 23 | Kind string 24 | // Delay represents delay time to print the message to stdout or stderr. 25 | Delay time.Duration 26 | } 27 | 28 | // Run compiles and runs the given src. 29 | // src can be set string, []byte and io.Reader value. 30 | func (cli *Client) Run(src interface{}) (*RunResult, error) { 31 | values := url.Values{} 32 | values.Set("version", Version) 33 | body, err := srcToString(src) 34 | if err != nil { 35 | return nil, err 36 | } 37 | values.Set("body", body) 38 | 39 | req, err := http.NewRequest(http.MethodPost, cli.baseURL()+"/compile?"+values.Encode(), nil) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | resp, err := cli.httpClient().Do(req) 45 | if err != nil { 46 | return nil, err 47 | } 48 | defer resp.Body.Close() 49 | 50 | var result RunResult 51 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 52 | return nil, err 53 | } 54 | 55 | return &result, nil 56 | } 57 | -------------------------------------------------------------------------------- /share.go: -------------------------------------------------------------------------------- 1 | package goplayground 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/url" 7 | ) 8 | 9 | // Share generates share URL of the given src. 10 | // src can be set string, []byte and io.Reader value. 11 | func (cli *Client) Share(src interface{}) (*url.URL, error) { 12 | r, err := srcToReader(src) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | req, err := http.NewRequest(http.MethodPost, cli.baseURL()+"/share", r) 18 | resp, err := cli.httpClient().Do(req) 19 | if err != nil { 20 | return nil, err 21 | } 22 | defer resp.Body.Close() 23 | 24 | bs, err := ioutil.ReadAll(resp.Body) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | shareURL, err := url.Parse(cli.frontBaseURL() + "/p/" + string(bs)) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | if cli.Backend != BackendDefault { 35 | q := shareURL.Query() 36 | q.Set("v", cli.Backend.String()) 37 | shareURL.RawQuery = q.Encode() 38 | } 39 | 40 | return shareURL, nil 41 | } 42 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package goplayground 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | // VersionResult is result of Client.Format. 9 | type VersionResult struct { 10 | Version string 11 | Release string 12 | Name string 13 | } 14 | 15 | // Version gets version and release tags which is used in the Go Playground. 16 | func (cli *Client) Version() (*VersionResult, error) { 17 | req, err := http.NewRequest(http.MethodGet, cli.baseURL()+"/version", nil) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | resp, err := cli.httpClient().Do(req) 23 | if err != nil { 24 | return nil, err 25 | } 26 | defer resp.Body.Close() 27 | 28 | var result VersionResult 29 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 30 | return nil, err 31 | } 32 | 33 | return &result, nil 34 | } 35 | --------------------------------------------------------------------------------