├── .config └── mise.toml ├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── doc.go ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── sub.go ├── sub ├── .spectral.yml ├── Makefile ├── doc.go ├── play │ ├── doc.go │ ├── play.go │ └── play_test.go ├── playground-openapi.yaml └── share │ ├── doc.go │ └── share.go ├── testdata ├── Makefile ├── favicon.ico ├── go-favicon-0.png ├── go-favicon-1.png ├── go-logo-blue.png └── go-logo-blue.svg └── useragent_buildinfo.go /.config/mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | "aqua:golangci/golangci-lint" = "2.1.2" 3 | "aqua:stoplightio/spectral" = "6.14.3" 4 | node = "23.11.0" 5 | "npm:openapicmd" = "2.6.1" 6 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build&test 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | go: 11 | - stable 12 | - oldstable 13 | steps: 14 | 15 | - name: Check out code 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: ${{ matrix.go }} 22 | id: go 23 | 24 | - name: Build 25 | run: go build -v . 26 | 27 | - name: Test 28 | run: ./goeval 'fmt.Println("Hello, world")' 29 | 30 | - name: Check version 31 | run: ./goeval 'fmt.Println(runtime.Version())' 32 | 33 | - name: Run tests 34 | run: go test -v ./... 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /goeval 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # goeval - Evaluate Go snippets instantly from the command line 3 | 4 | ## 🚀 Demo 5 | 6 | ```console 7 | $ goeval 'fmt.Println("Hello, world!")' 8 | Hello, world! 9 | $ goeval 'fmt.Println(os.Args[1])' 'Hello, world!' 10 | Hello, world! 11 | $ goeval -i .=fmt -i os 'Println(os.Args[1])' 'Hello, world!' 12 | Hello, world! 13 | $ goeval -i math/rand 'fmt.Println(rand.Int())' 14 | 5577006791947779410 15 | 16 | $ goeval -i fmt -i math/big -i os 'var x, y, z big.Int; x.SetString(os.Args[1], 10); y.SetString(os.Args[2], 10); fmt.Println(z.Mul(&x, &y).String())' 45673432245678899065433367889424354 136762347343433356789893322 17 | 6246405805150306996814033892780381988744339134177555648763988 18 | 19 | $ # Use os.Args 20 | $ goeval 'fmt.Printf("%x\n", sha256.Sum256([]byte(os.Args[1])))' abc 21 | ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 22 | 23 | $ # Download module in GOPATH mode and use with goeval 24 | $ GO111MODULE=off go get github.com/klauspost/cpuid && goeval -i github.com/klauspost/cpuid/v2 'fmt.Println(cpuid.CPU.X64Level())' 25 | 3 26 | 27 | $ # Serve the current directory over HTTP 28 | $ goeval 'http.Handle("/",http.FileServer(http.Dir(".")));http.ListenAndServe(":8084",nil)' 29 | 30 | $ # Import net/http symbols in package scope for shorter code 31 | $ goeval -i .=net/http 'Handle("/",FileServer(Dir(".")));ListenAndServe(":8084",nil)' 32 | ``` 33 | 34 | ### Go modules 35 | 36 | Use `-i @` to import a Go module. 37 | 38 | Use `-i =@` to import a Go module and import the package with the given alias. 39 | 40 | ```console 41 | $ goeval -i .=github.com/bitfield/script@v0.24.1 'ListFiles("./[^.]*").First(4).Stdout()' 42 | LICENSE 43 | README.md 44 | doc.go 45 | go.mod 46 | 47 | $ goeval -i github.com/klauspost/cpuid/v2@v2.2.3 -i github.com/klauspost/cpuid/v2 'fmt.Println(cpuid.CPU.X64Level())' 48 | 3 49 | $ goeval -i cpuid=github.com/klauspost/cpuid/v2@v2.2.3 'fmt.Println(cpuid.CPU.X64Level())' 50 | 3 51 | ``` 52 | 53 | 58 | 59 | 60 | ## ⬇️ Install 61 | 62 | ```console 63 | $ go install github.com/dolmen-go/goeval@latest 64 | ``` 65 | 66 | ## 🗑️ Uninstall 67 | 68 | ```console 69 | $ go clean -i github.com/dolmen-go/goeval 70 | ``` 71 | 72 | ## ❓ How does it work? 73 | 74 | ### GOPATH mode 75 | 76 | `goeval` just wraps your code with the necessary text to build a `main` package and a `main` func with the given imports, pass it through the [`goimports` tool](https://godoc.org/golang.org/x/tools/cmd/goimports) (to automatically add missing imports), writes in a temporary file and calls `go run` with [`GO111MODULE=off`](https://golang.org/ref/mod#mod-commands). 77 | 78 | `goimports` is enabled by default, but you can disable it to force explicit imports (for forward safety): 79 | 80 | ```console 81 | $ goeval -goimports= -i fmt 'fmt.Println("Hello, world!")' 82 | Hello, world! 83 | ``` 84 | 85 | ### Go module mode 86 | 87 | When at least one `module@version` is imported with `-i`, Go module mode is enabled. Two files are generated: `tmpxxxx.go` and `go.mod`. Then `go get .` is run to resolve and fetch dependencies, and then `go run`. 88 | 89 | ## 🛠️ Debugging 90 | 91 | To debug a syntax error: 92 | 93 | ```console 94 | $ goeval -E -goimports= ... | goimports 95 | ``` 96 | 97 | ## 🧙 Unsupported tricks 98 | 99 | Here are some tricks that have worked in the past, that may still work in the last version, but are not guaranteed to work later. 100 | 101 | ### Use functions 102 | 103 | The supported way: 104 | 105 | ```console 106 | $ goeval 'var fact func(int)int;fact=func(n int)int{if n==1{return 1};return n*fact(n-1)};fmt.Println(fact(5))' 107 | ``` 108 | 109 | The hacky way: 110 | 111 | ```console 112 | $ goeval 'fmt.Println(fact(5))};func fact(n int)int{if n==1{return 1};return n*fact(n-1)' 113 | ``` 114 | 115 | ### Use generics 116 | 117 | Needs: 118 | - goeval compiled with Go 1.18+ 119 | - Go 1.18+ installed. 120 | 121 | ```console 122 | $ goeval 'p(1);p("a");};func p[T any](x T){fmt.Println(x)' 123 | 1 124 | a 125 | $ goeval 'p(1);p(2.0);};func p[T int|float64](x T){x++;fmt.Println(x)' 126 | 2 127 | 3 128 | $ goeval -i golang.org/x/exp/constraints 'p(1);p(2.0);};func p[T constraints.Signed|constraints.Float](x T){x++;fmt.Println(x)' 129 | 2 130 | 3 131 | ``` 132 | 133 | ## 🔄 Alternatives 134 | 135 | * [gommand](https://github.com/sno6/gommand) Go one liner program. Similar to `python -c`. 136 | * [gorram](https://github.com/natefinch/gorram) Like `go run` for any Go function. 137 | * [goexec](https://github.com/shurcooL/goexec) A command line tool to execute Go functions. 138 | 139 | ## 🛡️ License 140 | 141 | Copyright 2019-2025 Olivier Mengué 142 | 143 | Licensed under the Apache License, Version 2.0 (the "License"); 144 | you may not use this file except in compliance with the License. 145 | You may obtain a copy of the License at 146 | 147 | http://www.apache.org/licenses/LICENSE-2.0 148 | 149 | Unless required by applicable law or agreed to in writing, software 150 | distributed under the License is distributed on an "AS IS" BASIS, 151 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 152 | See the License for the specific language governing permissions and 153 | limitations under the License. 154 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019-2025 Olivier Mengué. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Command goeval allows to run Go snippets given on the command line. 18 | // 19 | // A Go toolchain must be available in $PATH as goeval relies on "go run". 20 | // 21 | // The code, given either as the first argument or on stdin, is wrapped as 22 | // the body of a main() function in a main package, and executed with "go run". 23 | // 24 | // Imports are implicit (they are usually resolved automatically thanks to 25 | // [goimports]) but they can be explicitely specified using -i. 26 | // If at least one package import is given with a version (import-path@version), 27 | // a full Go module is assembled, and imports without version are resolved 28 | // as the latest version available in the local Go module cache (GOMODCACHE). 29 | // 30 | // In GOPATH mode (the default), the local Go context is involved only if the current 31 | // directory happens to be in GOPATH and the package is imported. 32 | // In Go module mode, the local Go context (go.mod, .go source files) is completely 33 | // ignored for resolving imports and compiling the snippet. 34 | // 35 | // -play runs the code in the sandbox of [the Go Playground] instead of the local 36 | // machine and replays the output. 37 | // 38 | // -share posts the code for storage on [the Go Playground] and displays the URL. 39 | // 40 | // 🚀 Quick Start 41 | // 42 | // go install github.com/dolmen-go/goeval@latest 43 | // goeval 'fmt.Println("Hello, world")' 44 | // 45 | // [goimports]: https://pkg.go.dev/golang.org/x/tools/imports 46 | // [the Go Playground]: https://go.dev/play 47 | package main 48 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dolmen-go/goeval 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | golang.org/x/mod v0.24.0 7 | golang.org/x/tools v0.31.0 8 | ) 9 | 10 | require golang.org/x/sync v0.12.0 // indirect 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 4 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 5 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 6 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 7 | golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= 8 | golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= 9 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019-2025 Olivier Mengué. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "bytes" 21 | "errors" 22 | "flag" 23 | "fmt" 24 | "io" 25 | "log" 26 | "os" 27 | "os/exec" 28 | "path/filepath" 29 | "strings" 30 | "time" 31 | 32 | "golang.org/x/mod/module" 33 | goimp "golang.org/x/tools/imports" 34 | ) 35 | 36 | // imports is the storage for -i flags 37 | // imports implements interface flag.Value. 38 | type imports struct { 39 | packages map[string]string // alias => import path 40 | modules map[string]string // module path => version 41 | onlySemVer bool 42 | } 43 | 44 | func (*imports) String() string { 45 | return "" // irrelevant 46 | } 47 | 48 | func (imp *imports) Set(s string) error { 49 | var alias, path, version string 50 | var ok bool 51 | if alias, path, ok = strings.Cut(s, "="); !ok { 52 | alias = "" 53 | path = s 54 | } else if alias == "" { 55 | return fmt.Errorf("%q: empty alias", s) 56 | } else if alias == "_" || alias == "." { 57 | tmpPath, _, _ := strings.Cut(path, "@") 58 | alias = alias + " " + tmpPath // special alias 59 | } else if strings.Contains(alias, " ") { 60 | return fmt.Errorf("%q: invalid alias", s) 61 | } 62 | var p2 string 63 | if p2, version, ok = strings.Cut(path, "@"); ok { 64 | if version == "" { 65 | return fmt.Errorf("%q: empty module version", s) 66 | } 67 | path = p2 68 | if err := module.CheckPath(path); err != nil { 69 | return fmt.Errorf("%q: %w", s, err) 70 | } 71 | // TODO check for duplicates 72 | if imp.modules == nil { 73 | imp.modules = make(map[string]string) 74 | } 75 | imp.modules[path] = version 76 | imp.onlySemVer = imp.onlySemVer && version == module.CanonicalVersion(version) 77 | } else if alias == "" { 78 | alias = " " + path // special alias 79 | } 80 | 81 | switch path { 82 | case "": 83 | return fmt.Errorf("%q: empty path", s) 84 | case "embed": 85 | return errors.New("use of package 'embed' is not allowed") 86 | 87 | default: 88 | if err := module.CheckImportPath(path); err != nil { 89 | return fmt.Errorf("%q: %w", s, err) 90 | } 91 | } 92 | 93 | if alias != "" { 94 | imp.packages[alias] = path 95 | } 96 | 97 | // log.Printf("alias=%s path=%s version=%s", alias, path, version) 98 | 99 | return nil 100 | } 101 | 102 | // Reference code for running the "go" command: 103 | // https://github.com/golang/dl/blob/master/internal/version/version.go#L58 104 | 105 | var run = runSilent 106 | 107 | func runSilent(cmd *exec.Cmd) error { 108 | return cmd.Run() 109 | } 110 | 111 | func runX(cmd *exec.Cmd) error { 112 | // Inject -x in go commands 113 | if cmd.Args[0] == goCmd && cmd.Args[1] != "env" { 114 | cmd.Args = append([]string{goCmd, cmd.Args[1], "-x"}, cmd.Args[2:]...) 115 | } 116 | fmt.Printf("%s\n", cmd.Args) 117 | return cmd.Run() 118 | } 119 | 120 | func runTime(cmd *exec.Cmd) error { 121 | defer func(start time.Time) { 122 | fmt.Fprintf(os.Stderr, "run %v %v\n", time.Since(start), cmd.Args) 123 | }(time.Now()) 124 | return cmd.Run() 125 | } 126 | 127 | var goCmd = "go" 128 | 129 | func getGOMODCACHE(env []string) (string, error) { 130 | var out bytes.Buffer 131 | cmd := exec.Command(goCmd, "env", "GOMODCACHE") 132 | cmd.Stderr = os.Stderr 133 | cmd.Stdout = &out 134 | cmd.Env = env 135 | err := run(cmd) 136 | if err != nil { 137 | return "", err 138 | } 139 | b := bytes.TrimRight(out.Bytes(), "\r\n") 140 | if len(b) == 0 { 141 | return "", errors.New("can't retrieve GOMODCACHE") 142 | } 143 | return string(b), nil 144 | } 145 | 146 | func main() { 147 | err := _main() 148 | if exit, ok := err.(*exec.ExitError); ok && exit.ExitCode() > 0 { 149 | os.Exit(exit.ExitCode()) 150 | } else if err != nil { 151 | log.Fatal(err) 152 | } 153 | } 154 | 155 | type actionBits uint 156 | 157 | const ( 158 | actionRun actionBits = iota 159 | actionDump // -E 160 | actionDumpPlay // -Eplay 161 | actionPlay // -play 162 | actionShare // -share 163 | ) 164 | 165 | var action actionBits 166 | 167 | func flagAction(name string, a actionBits, usage string) { 168 | flag.BoolFunc(name, usage, func(string) error { 169 | if action != actionRun { 170 | return errors.New("flags -Eplay, -play and -share are exclusive") 171 | } 172 | action = a 173 | return nil 174 | }) 175 | } 176 | 177 | func _main() error { 178 | imports := imports{ 179 | packages: map[string]string{" ": "os"}, 180 | onlySemVer: true, 181 | } 182 | flag.Var(&imports, "i", "* import package: [alias=]import-path\n* switch to Go module mode and import package: [alias=]import-path@version") 183 | 184 | var goimports string 185 | flag.StringVar(&goimports, "goimports", "goimports", "goimports tool name, to use an alternate tool or just disable it.") 186 | 187 | flag.StringVar(&goCmd, "go", "go", "go command path.") 188 | 189 | // -E, like "cc -E" 190 | flagAction("E", actionDump, "just dump the assembled source, without running it.") 191 | flagAction("Eplay", actionDumpPlay, "just dump the assembled source for posting on https://go.dev/play") 192 | // TODO allow to optionally set a different endpoint 193 | flagAction("play", actionPlay, "run the code remotely on https://go.dev/play") 194 | flagAction("share", actionShare, "share the code on https://go.dev/play and print the URL.") 195 | 196 | // TODO allow to optionally set a different endpoint for the Go Playground 197 | 198 | showCmds := flag.Bool("x", false, "print commands executed.") 199 | 200 | flag.Usage = func() { 201 | prog := os.Args[0] 202 | fmt.Fprintf(flag.CommandLine.Output(), ""+ 203 | "\n"+ 204 | "Usage: %s [...] [...]\n"+ 205 | "\n"+ 206 | "Options:\n", 207 | prog) 208 | flag.PrintDefaults() 209 | fmt.Fprintf(flag.CommandLine.Output(), ""+ 210 | "\n"+ 211 | "Example:\n"+ 212 | " %s -i fmt 'fmt.Println(\"Hello, world!\")'\n"+ 213 | "\n"+ 214 | "Copyright 2019-2025 Olivier Mengué.\n"+ 215 | "Source code: https://github.com/dolmen-go/goeval\n", 216 | prog) 217 | os.Exit(1) 218 | } 219 | flag.Parse() 220 | 221 | if flag.NArg() < 1 { 222 | flag.Usage() 223 | } 224 | code := flag.Arg(0) 225 | if code == "-" { 226 | b, err := io.ReadAll(os.Stdin) 227 | if err != nil { 228 | return err 229 | } 230 | code = string(b) 231 | } 232 | 233 | args := flag.Args()[1:] 234 | 235 | if goCmdResolved, err := exec.LookPath(goCmd); err != nil { 236 | return fmt.Errorf("%q: %v", goCmd, err) 237 | } else { 238 | goCmd = goCmdResolved 239 | } 240 | 241 | if *showCmds { 242 | run = runX 243 | } 244 | 245 | moduleMode := imports.modules != nil 246 | 247 | env := os.Environ() 248 | if moduleMode { 249 | env = append(env, "GO111MODULE=on") 250 | } else { 251 | // Run in GOPATH mode, ignoring any code in the current directory 252 | env = append(env, "GO111MODULE=off") 253 | } 254 | 255 | var dir, origDir string 256 | 257 | if moduleMode { 258 | // "go get" is not yet as smart as we want, so let's help 259 | // https://go.dev/issue/43646 260 | preferCache := imports.onlySemVer 261 | var gomodcache string 262 | if preferCache { 263 | var err error 264 | gomodcache, err = getGOMODCACHE(env) 265 | preferCache = err == nil 266 | } 267 | 268 | var err error 269 | if dir, err = os.MkdirTemp("", "goeval*"); err != nil { 270 | log.Fatal(err) 271 | } 272 | defer os.Remove(dir) 273 | 274 | moduleName := filepath.Base(dir) 275 | 276 | origDir, err = os.Getwd() 277 | if err != nil { 278 | log.Fatal("getwd:", err) 279 | } 280 | 281 | gomod := dir + "/go.mod" 282 | if err := os.WriteFile(gomod, []byte("module "+moduleName+"\n"), 0600); err != nil { 283 | log.Fatal("go.mod:", err) 284 | } 285 | defer os.Remove(gomod) 286 | 287 | var gogetArgs []string 288 | gogetArgs = append(gogetArgs, "get", "--") 289 | for mod, ver := range imports.modules { 290 | gogetArgs = append(gogetArgs, mod+"@"+ver) 291 | if preferCache { 292 | // Keep preferCache as long as we find modules in the cache. 293 | // Structure of the cache is documented here: https://go.dev/ref/mod#module-cache 294 | _, err := os.Stat(gomodcache + "/cache/download/" + mod + "/@v/" + ver + ".mod") 295 | preferCache = err == nil 296 | } 297 | } 298 | for _, path := range imports.packages { 299 | if _, seen := imports.modules[path]; !seen { 300 | gogetArgs = append(gogetArgs, path) 301 | } 302 | } 303 | 304 | // fmt.Println("preferCache", preferCache) 305 | if preferCache { 306 | // As we found all modules in the cache, tell "go get" and "go run" to not use the proxy. 307 | // See https://go.dev/issue/43646 308 | // env = append(env, "GOPROXY=file://"+filepath.ToSlash(gomodcache)+"/cache/download") 309 | env = append(env, "GOPROXY=off") 310 | } 311 | 312 | cmd := exec.Command(goCmd, gogetArgs...) 313 | cmd.Env = env 314 | cmd.Dir = dir 315 | cmd.Stdin = nil 316 | cmd.Stdout = nil 317 | cmd.Stdout = os.Stdout 318 | // go get is too verbose :( 319 | cmd.Stderr = nil 320 | if err = run(cmd); err != nil { 321 | log.Fatal("go get failure:", err) 322 | } 323 | // log.Println("go get OK.") 324 | defer os.Remove(dir + "/go.sum") 325 | } 326 | 327 | var src bytes.Buffer 328 | 329 | // If sending to the Go Playground, export GOEXPERIMENT as a comment 330 | if action >= actionDumpPlay { 331 | const alphaNum = "abcdefghijklmnopqrstuvwxyz0123456789" 332 | const alphaNumComma = alphaNum + "," 333 | if exp, ok := os.LookupEnv("GOEXPERIMENT"); ok && 334 | exp != "" && // Not empty 335 | strings.Trim(exp, ",") == exp && // No leading or trailing commas 336 | strings.Trim(exp, alphaNumComma) == "" { // only lower case alpha num and comma 337 | src.WriteString("// GOEXPERIMENT=") 338 | src.WriteString(exp) 339 | src.WriteString("\n\n") 340 | } 341 | } 342 | 343 | src.WriteString("package main\n") 344 | for alias, path := range imports.packages { 345 | if len(alias) > 2 && alias[1] == ' ' { 346 | switch alias[0] { 347 | case '.', '_': 348 | alias = alias[:1] 349 | case ' ': // no alias 350 | fmt.Fprintf(&src, "import %q\n", path) 351 | continue 352 | } 353 | } 354 | fmt.Fprintf(&src, "import %s %q\n", alias, path) 355 | } 356 | src.WriteString("func main() {\n") 357 | if action <= actionDump { 358 | src.WriteString("os.Args[1] = os.Args[0]\nos.Args = os.Args[1:]\n") 359 | if moduleMode { 360 | fmt.Fprintf(&src, "_ = os.Chdir(%q)\n", origDir) 361 | } 362 | src.WriteString("//line :1\n") 363 | } 364 | src.WriteString(code) 365 | src.WriteString("\n}\n") 366 | 367 | var ( 368 | srcFilename string 369 | srcFinal io.Writer // The final transformed source after goimports. Txtar format if in Go modules mode. 370 | tail func() error 371 | ) 372 | switch action { 373 | case actionRun: 374 | f, err := os.CreateTemp(dir, "*.go") 375 | if err != nil { 376 | log.Fatal(err) 377 | } 378 | defer f.Close() 379 | defer os.Remove(f.Name()) 380 | srcFinal = f 381 | srcFilename = f.Name() 382 | 383 | runArgs := make([]string, 0, 3+len(args)) 384 | runArgs = append(runArgs, "run", srcFilename, "--") 385 | runArgs = append(runArgs, args...) 386 | // log.Println(goCmd, runArgs) 387 | 388 | cmd := exec.Command(goCmd, runArgs...) 389 | cmd.Env = env 390 | cmd.Dir = dir // In Go module mode we run from the temp module dir 391 | cmd.Stdin = os.Stdin 392 | cmd.Stdout = os.Stdout 393 | cmd.Stderr = os.Stderr 394 | tail = func() error { 395 | if err = f.Close(); err != nil { 396 | return err 397 | } 398 | return run(cmd) 399 | } 400 | case actionPlay: 401 | var cleanup func() 402 | srcFinal, tail, cleanup = prepareSub(playClient) 403 | defer cleanup() 404 | case actionShare: 405 | var cleanup func() 406 | srcFinal, tail, cleanup = prepareSub(shareClient) 407 | defer cleanup() 408 | default: // actionDump, actionDumpPlay 409 | srcFinal = os.Stdout 410 | tail = func() error { return nil } 411 | } 412 | 413 | var err error 414 | switch goimports { 415 | case "goimports": 416 | var out []byte 417 | var filename string // filename is used to locate the relevant go.mod 418 | if imports.packages != nil { 419 | filename = srcFilename 420 | } 421 | out, err = goimp.Process(filename, src.Bytes(), &goimp.Options{ 422 | Fragment: false, 423 | AllErrors: false, 424 | Comments: true, 425 | TabIndent: true, 426 | TabWidth: 8, 427 | FormatOnly: false, 428 | }) 429 | if err == nil { 430 | _, err = srcFinal.Write(out) 431 | } 432 | case "": 433 | _, err = srcFinal.Write(src.Bytes()) 434 | default: 435 | cmd := exec.Command(goimports) 436 | cmd.Env = env 437 | cmd.Dir = dir 438 | cmd.Stdin = &src 439 | cmd.Stdout = srcFinal 440 | cmd.Stderr = os.Stderr 441 | err = run(cmd) 442 | } 443 | if err != nil { 444 | return err 445 | } 446 | 447 | /* 448 | // Do we need to run "go get" again after "goimports"? 449 | if moduleMode { 450 | goget := exec.Command(goCmd, "get", ".") 451 | goget.Env = env 452 | goget.Dir = dir 453 | goget.Stdout = os.Stdout 454 | goget.Stderr = os.Stderr 455 | run(goget) 456 | } 457 | */ 458 | 459 | // dump go.mod, go.sum 460 | if moduleMode && action != actionRun { 461 | gomod, err := os.Open(dir + "/go.mod") 462 | if err != nil { 463 | log.Fatal(err) 464 | } 465 | io.WriteString(srcFinal, "-- go.mod --\n") 466 | defer gomod.Close() 467 | io.Copy(srcFinal, gomod) 468 | 469 | gosum, err := os.Open(dir + "/go.sum") 470 | switch { 471 | case errors.Is(err, os.ErrNotExist): // ignore 472 | case err != nil: 473 | log.Fatal(err) 474 | default: 475 | io.WriteString(srcFinal, "-- go.sum --\n") 476 | defer gosum.Close() 477 | io.Copy(srcFinal, gosum) 478 | } 479 | } 480 | 481 | return tail() 482 | } 483 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019-2025 Olivier Mengué. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main_test 18 | 19 | import ( 20 | "os" 21 | "os/exec" 22 | ) 23 | 24 | func goeval(args ...string) { 25 | cmd := exec.Command("go", append([]string{"run", "."}, args...)...) 26 | cmd.Stdin = nil 27 | cmd.Stdout = os.Stdout 28 | cmd.Stderr = os.Stderr 29 | cmd.Run() 30 | } 31 | 32 | func Example() { 33 | goeval(`fmt.Println("OK")`) 34 | 35 | // Output: 36 | // OK 37 | } 38 | 39 | func Example_dump() { 40 | goeval("-E", `fmt.Println("OK")`) 41 | 42 | // Output: 43 | // package main 44 | // 45 | // import ( 46 | // "fmt" 47 | // "os" 48 | // ) 49 | // 50 | // func main() { 51 | // os.Args[1] = os.Args[0] 52 | // os.Args = os.Args[1:] 53 | // //line :1 54 | // fmt.Println("OK") 55 | // } 56 | } 57 | 58 | func Example_flag() { 59 | goeval(`fmt.Println(os.Args[1])`, `--`) 60 | goeval(`fmt.Println(os.Args[1])`, `-x`) // -x is also a "go run" flag 61 | goeval(`fmt.Println(os.Args[1])`, `toto.go`) // toto.go could be caught by "go run" 62 | 63 | // Output: 64 | // -- 65 | // -x 66 | // toto.go 67 | } 68 | -------------------------------------------------------------------------------- /sub.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 Olivier Mengué. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "bytes" 21 | _ "embed" 22 | "io" 23 | "log" 24 | "os" 25 | "os/exec" 26 | ) 27 | 28 | var ( 29 | //go:embed sub/play/play.go 30 | playClient string 31 | //go:embed sub/share/share.go 32 | shareClient string 33 | ) 34 | 35 | // prepareSub prepares execution of a sub command via a "go run". 36 | // The returned stdin buffer may be filled with data. 37 | // cleanup must be called after cmd.Run() to clean the tempoary go source created. 38 | func prepareSub(appCode string) (stdin *bytes.Buffer, tail func() error, cleanup func()) { 39 | f, err := os.CreateTemp("", "*.go") 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | defer f.Close() 44 | fName := f.Name() 45 | cleanup = func() { 46 | os.Remove(fName) 47 | } 48 | 49 | if _, err := io.WriteString(f, appCode); err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | // Prepare input that will be filled before executing the command 54 | stdin = new(bytes.Buffer) 55 | 56 | // Run "go run" with the code submitted on stdin and the userAgent as first argument 57 | cmd := exec.Command(goCmd, "run", fName, getUserAgent()) 58 | cmd.Env = append( 59 | os.Environ(), // We must not use the 'env' built for local run here 60 | "GO111MODULE=off", // Sub command use only stdlib 61 | "GOEXPERIMENT=", // Clear GOEXPERIMENT which has been forwarded in a comment 62 | ) 63 | cmd.Stdin = stdin 64 | cmd.Stdout = os.Stdout 65 | cmd.Stderr = os.Stderr 66 | 67 | tail = func() error { 68 | return run(cmd) 69 | } 70 | return 71 | } 72 | -------------------------------------------------------------------------------- /sub/.spectral.yml: -------------------------------------------------------------------------------- 1 | # OpenAPI rules 2 | # Reference: https://meta.stoplight.io/docs/spectral/ZG9jOjExNw-open-api-rules 3 | extends: "spectral:oas" 4 | rules: 5 | contact-properties: off 6 | operation-tags: off 7 | -------------------------------------------------------------------------------- /sub/Makefile: -------------------------------------------------------------------------------- 1 | # Tools for playground-openapi.yaml maintenance. 2 | # 3 | # Required tools: 4 | # - docker 5 | # - mise (see ../.config/mise.toml) https://mise.jdx.dev/ 6 | # 7 | # All other tools are installed via mise. 8 | 9 | .PHONY: openapi-validate 10 | 11 | openapi-validate: playground-openapi.yaml .spectral.yml 12 | docker run --rm -it -v "$(CURDIR):/src" stoplight/spectral lint -r /src/.spectral.yml /src/playground-openapi.yaml 13 | 14 | .PHONY: swagger-ui 15 | 16 | PORT ?= 8080 17 | 18 | swagger-ui: 19 | ( sleep 2 ; open http://localhost:$(PORT)/ ) & 20 | docker run --rm -p 127.0.0.1:$(PORT):8080 -v "$(CURDIR):/src" -e SWAGGER_JSON=/src/playground-openapi.yaml swaggerapi/swagger-ui 21 | -------------------------------------------------------------------------------- /sub/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 Olivier Mengué. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // This directory contains sub commands of [github.com/dolmen-go/goeval]. 18 | // 19 | // Sub commands have the following constraints: 20 | // - only stdlib dependencies 21 | // - compiled in GOPATH mode (GO111MODULE=off) 22 | // 23 | // The source code of each command is embedded (see [embed]) in the goeval binary and commands are launched with "go run". 24 | package sub 25 | -------------------------------------------------------------------------------- /sub/play/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 Olivier Mengué. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Command play is the sub command launched by "goeval -play". 18 | // 19 | // play sends the program code to the Go Playground at https://play.golang.org/compile 20 | // and replays the received events respecting event delays. 21 | // 22 | // $ curl -s -X POST --data-urlencode body@- https://play.golang.org/compile < 21 | Server source code: https://go.googlesource.com/playground 22 | https://pkg.go.dev/golang.org/x/playground 23 | 24 | Author of this OpenAPI specification: [Olivier Mengué](https://github.com/dolmen-go). 25 | contact: 26 | url: https://github.com/dolmen-go/goeval/issues 27 | version: '0.20250419.0' 28 | # The license applies only to this document, not to the Go Playground. 29 | license: 30 | name: Apache License 2.0 31 | identifier: Apache-2.0 32 | # identifier and url are exclusive 33 | # url: https://www.apache.org/licenses/LICENSE-2.0 34 | servers: 35 | # The main api is at play.golang.org 36 | # go.dev/_ exposes only some endpoints with a different API (for example /compile has a different schema) 37 | - url: https://play.golang.org 38 | description: play.golang.org is also the backend of https://go.dev/_ which exposes a subset of this API and some variations in output. 39 | paths: 40 | # See https://go.googlesource.com/playground/+/refs/heads/master/server.go#51 41 | /version: 42 | get: # also: post 43 | operationId: version 44 | summary: Show server's Go version. 45 | description: > 46 | Server source code: https://go.googlesource.com/playground/+/refs/heads/master/fmt.go#23 47 | security: [{}] 48 | responses: 49 | '200': 50 | description: OK 51 | headers: 52 | Access-Control-Allow-Origin: 53 | schema: 54 | type: string 55 | enum: ["*"] 56 | content: 57 | "application/json": 58 | schema: 59 | type: object 60 | properties: 61 | Version: 62 | type: string 63 | Release: 64 | type: string 65 | Name: 66 | type: string 67 | example: {"Version":"go1.24.2","Release":"go1.24","Name":"Go 1.24"} 68 | /compile: 69 | post: 70 | operationId: play 71 | summary: Compile and run the provided Go code. 72 | description: > 73 | Curl example: 74 | ```console 75 | $ curl -s -X POST --data-urlencode body@- https://play.golang.org/compile < 143 | See https://go.googlesource.com/playground/+/refs/heads/master/play.go#62 144 | properties: 145 | Delay: 146 | type: string 147 | description: Go's [time.Duration](https://pkg.go.dev/time#ParseDuration). 148 | Kind: 149 | type: string 150 | enum: 151 | - stdout 152 | - stderr 153 | Message: 154 | type: string 155 | '500': 156 | description: Internal server error. 157 | headers: 158 | Access-Control-Allow-Origin: 159 | schema: 160 | type: string 161 | enum: ["*"] 162 | /share: 163 | # https://go.googlesource.com/playground/+/refs/heads/master/share.go 164 | post: 165 | operationId: share 166 | summary: Save and share the code. 167 | description: > 168 | Curl example: 169 | ```console 170 | $ curl --data-binary '@-' -H 'Content-Type: text/plain; charset=utf-8' 'https://play.golang.org/_/share' < 222 | Curl examples: 223 | ```console 224 | $ curl -s --data-urlencode body@- https://play.golang.org/fmt < 284 | Server source code: https://go.googlesource.com/playground/+/refs/heads/master/edit.go 285 | parameters: 286 | - name: id 287 | description: Snippet identifier, as returned by `/share`. 288 | in: path 289 | schema: 290 | type: string 291 | required: true 292 | - name: download 293 | description: If `true`, set the Content-Disposition header. 294 | in: query # https://pkg.go.dev/net/http#Request.FormValue 295 | schema: 296 | type: boolean 297 | security: [{}] 298 | responses: 299 | '200': 300 | description: Snippet content. 301 | headers: 302 | Access-Control-Allow-Origin: 303 | schema: 304 | type: string 305 | enum: ["*"] 306 | content: 307 | 'text/plain; charset=utf-8': 308 | schema: 309 | type: string 310 | '404': 311 | description: Snippet not found. 312 | headers: 313 | Access-Control-Allow-Origin: 314 | schema: 315 | type: string 316 | enum: ["*"] 317 | content: 318 | 'text/plain': 319 | schema: 320 | type: string 321 | enum: ["Snippet not found"] -------------------------------------------------------------------------------- /sub/share/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 Olivier Mengué. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Command share is the sub command launched by "goeval -share". 18 | package main 19 | -------------------------------------------------------------------------------- /sub/share/share.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | type uaTransport struct { 11 | *http.Transport 12 | UserAgent string 13 | } 14 | 15 | func (t *uaTransport) RoundTrip(req *http.Request) (*http.Response, error) { 16 | req.Header.Set("User-Agent", t.UserAgent) 17 | return t.Transport.RoundTrip(req) 18 | } 19 | 20 | func main() { 21 | http.DefaultTransport = &uaTransport{Transport: http.DefaultTransport.(*http.Transport), UserAgent: os.Args[1]} 22 | 23 | resp, err := http.Post("https://play.golang.org/share", "text/plain; charset=utf-8", os.Stdin) 24 | if err != nil { 25 | log.Fatal("share:", err) 26 | } 27 | defer resp.Body.Close() 28 | id, err := io.ReadAll(resp.Body) 29 | if err != nil { 30 | log.Fatal("share:", err) 31 | } 32 | io.WriteString(os.Stdout, "https://go.dev/play/p/"+string(id)+"\n") 33 | } 34 | -------------------------------------------------------------------------------- /testdata/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | .PHONY: all 4 | 5 | all: go-logo-blue.png 6 | 7 | go-logo-blue.png: go-logo-blue.svg 8 | magick -background none $< -colors 2 $@ 9 | 10 | go-logo-blue.svg: 11 | curl -O https://go.dev/images/go-logo-blue.svg 12 | -------------------------------------------------------------------------------- /testdata/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dolmen-go/goeval/283b029b1ee1b07f85e5d44554d21fcaedcd730f/testdata/favicon.ico -------------------------------------------------------------------------------- /testdata/go-favicon-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dolmen-go/goeval/283b029b1ee1b07f85e5d44554d21fcaedcd730f/testdata/go-favicon-0.png -------------------------------------------------------------------------------- /testdata/go-favicon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dolmen-go/goeval/283b029b1ee1b07f85e5d44554d21fcaedcd730f/testdata/go-favicon-1.png -------------------------------------------------------------------------------- /testdata/go-logo-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dolmen-go/goeval/283b029b1ee1b07f85e5d44554d21fcaedcd730f/testdata/go-logo-blue.png -------------------------------------------------------------------------------- /testdata/go-logo-blue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /useragent_buildinfo.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 Olivier Mengué. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "runtime/debug" 21 | "strings" 22 | ) 23 | 24 | var version = "v1.2.0" // FIXME set at compile time with -ldflags="-X main.version=" 25 | 26 | // getUserAgent returns the HTTP User-Agent header value to use for -play and -share. 27 | // 28 | // Reference: https://www.rfc-editor.org/rfc/rfc9110#name-user-agent 29 | func getUserAgent() string { 30 | bi, ok := debug.ReadBuildInfo() 31 | if !ok { 32 | // The HTTP specification allows comments in header values: they are enclosed by parenthesis. 33 | return "goeval/" + version + " (github.com/dolmen-go/goeval)" 34 | } 35 | if !ok || bi.Main.Path == "" { 36 | // The HTTP specification allows comments in header values: they are enclosed by parenthesis. 37 | return "goeval/" + version + " (" + bi.Path + ")" 38 | } 39 | // "go run" reports "(devel)" as version but in header value parenthesis are reserved chars (for comments). 40 | return "goeval/" + strings.Trim(bi.Main.Version, "()") + " (" + bi.Main.Path + ")" 41 | } 42 | --------------------------------------------------------------------------------