├── .gitignore ├── nim.cfg ├── conf.json ├── setup.sh ├── docker ├── ignored ├── Dockerfile_nopackages ├── updateimages.sh ├── Dockerfile └── packages.nimble ├── nim_playground.nimble ├── docker_timeout.sh ├── test └── script.sh ├── README.md └── src └── nim_playground.nim /.gitignore: -------------------------------------------------------------------------------- 1 | .history 2 | nimcache 3 | nim_playground -------------------------------------------------------------------------------- /nim.cfg: -------------------------------------------------------------------------------- 1 | --threads:on 2 | --gc:orc 3 | -d:ssl 4 | #-d:useStdLib 5 | -------------------------------------------------------------------------------- /conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "log_fname": "/tmp/nim_playground.log", 3 | "tmp_dir": "/tmp/nim_playground" 4 | } 5 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Install Docker 4 | sudo apt update 5 | sudo apt install docker.io 6 | sudo systemctl start docker 7 | 8 | # Create Docker image 9 | echo "Creating Docker Image" 10 | docker build -t 'virtual_machine' - < Dockerfile 11 | echo "Retrieving Installed Docker Images" 12 | docker images 13 | -------------------------------------------------------------------------------- /docker/ignored: -------------------------------------------------------------------------------- 1 | v0.10.2 2 | v0.11.0 3 | v0.11.2 4 | v0.12.0 5 | v0.13.0 6 | v0.14.0 7 | v0.14.2 8 | v0.15.0 9 | v0.15.2 10 | v0.16.0 11 | v0.17.0 12 | v0.17.2 13 | v0.18.0 14 | v0.19.0 15 | v0.19.2 16 | v0.19.4 17 | v0.19.6 18 | v0.20.0 19 | v0.20.2 20 | v0.9.0 21 | v0.9.4 22 | v0.9.6 23 | v1.0.0 24 | v1.0.10 25 | v1.0.2 26 | v1.0.4 27 | v1.0.6 28 | v1.0.8 29 | -------------------------------------------------------------------------------- /nim_playground.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.0" 4 | author = "Zachary Carter" 5 | description = "API for play.nim-lang.org" 6 | license = "MIT" 7 | 8 | srcdir = "src" 9 | bin = @["nim_playground"] 10 | 11 | # Dependencies 12 | 13 | requires "nim >= 0.16.1" 14 | requires "jester >= 0.1.1" 15 | requires "nuuid >= 0.1.0" 16 | requires "ansiparse >= 0.4.0" 17 | requires "ansitohtml >= 0.1.0" 18 | -------------------------------------------------------------------------------- /docker_timeout.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | to=$1 5 | shift 6 | 7 | cont=$(docker run --memory=100m --memory-swap=100m --cpus=".8" -d "$@") 8 | code=$(timeout "$to" docker wait "$cont" || true) 9 | docker kill $cont &> /dev/null 10 | echo -n 'status: ' 11 | if [ -z "$code" ]; then 12 | echo timeout 13 | else 14 | echo exited: $code 15 | fi 16 | 17 | echo output: 18 | # pipe to sed simply for pretty nice indentation 19 | docker logs $cont | sed 's/^/\t/' 20 | 21 | docker rm $cont &> /dev/null 22 | -------------------------------------------------------------------------------- /test/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | file=$1 4 | compilationTarget=$2 5 | 6 | #exec 1> $"/usercode/logfile.txt" 7 | #exec 2> $"/usercode/errors.txt" 8 | exec < /dev/null 9 | 10 | chmod 777 /usercode/logfile.txt 11 | chmod 777 /usercode/errors.txt 12 | 13 | nim $compilationTarget --colors:on --NimblePath:/playground/nimble --nimcache:/usercode/nimcache /usercode/$file &> /usercode/errors.txt 14 | if [ $? -eq 0 ]; then 15 | /usercode/${file/.nim/""} &> /usercode/logfile.txt 16 | else 17 | echo "" &> /usercode/logfile.txt 18 | fi 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nim-playground 2 | 3 | This is the back-end for the [Nim playground](https://play.nim-lang.org). The front-end can be found [here](https://github.com/PMunch/nim-playground-frontend). All code that is executed by the playground is run within a container. This container always run the latest release of Nim, and comes with a series of Nimble packages installed. The list of packages can be found in [this list](https://github.com/PMunch/nim-playground/blob/master/docker/packages.nimble), and if you want to add or remove one simply make a PR to this repo that changes this file. 4 | -------------------------------------------------------------------------------- /docker/Dockerfile_nopackages: -------------------------------------------------------------------------------- 1 | FROM alpine:3.17 as builder 2 | 3 | RUN apk add git gcc musl-dev 4 | 5 | WORKDIR /build 6 | 7 | RUN git clone https://github.com/nim-lang/Nim.git 8 | 9 | COPY curtag Nim 10 | 11 | RUN cd Nim && git checkout $(cat curtag) 12 | 13 | WORKDIR /build/Nim 14 | 15 | RUN git clone https://github.com/nim-lang/csources_v1.git 16 | 17 | WORKDIR /build/Nim/csources_v1 18 | 19 | COPY curtag . 20 | 21 | RUN git checkout $(cat curtag) || git checkout $(echo $(git tag) $(cat curtag) | sed 's/ /\n/g' | sed 's/v/0./' | sort -t. -n -k1,1 -k2,2 -k3,3 -k4,4 | sed 's/0./v/' | sed -n "/$(cat curtag)/q;p" | tail -n1) 22 | 23 | RUN sh build.sh 24 | 25 | WORKDIR /build/Nim 26 | 27 | RUN bin/nim c --skipUserCfg --skipParentCfg koch 28 | 29 | RUN ./koch boot -d:release 30 | 31 | RUN mkdir /build/result 32 | 33 | RUN ./koch install /build/result 34 | 35 | ################################## 36 | 37 | FROM alpine:3.17 as playground 38 | 39 | RUN apk add gcc g++ musl-dev 40 | 41 | RUN apk add pcre # Requirement for regex 42 | 43 | WORKDIR /playground 44 | 45 | COPY --from=builder /build/result/* ./nim/ 46 | 47 | ENV PATH=$PATH:/playground/nim/bin 48 | 49 | RUN mkdir /usercode && chown nobody:nobody /usercode 50 | -------------------------------------------------------------------------------- /docker/updateimages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | location=$(dirname $0) 4 | processes="$(ps -e)" 5 | 6 | if [ "$(echo "$processes" | grep updateimages.sh | wc -l)" -gt 1 ] ; then 7 | echo "Detected running script, quitting" 8 | exit 0 9 | fi 10 | 11 | missing=$(uniq -u <(sort <(cat "$location/ignored" <(git ls-remote --tags https://github.com/nim-lang/nim | sed -n 's/.*refs\/tags\/\(.*\)^{}/\1/p') <(docker images | sed -n 's/virtual_machine *\(v[^ ]*\).*/\1/p')))) 12 | latest=$(git ls-remote --tags https://github.com/nim-lang/nim | sed -n 's/.*refs\/tags\/\(.*\)^{}/\1/p' | sed 's/v/0./' | sort -t. -n -k1,1 -k2,2 -k3,3 -k4,4 | sed 's/0./v/' | tail -n1) 13 | 14 | while read -r line; do 15 | if [ ! -z "$line" ]; then 16 | echo $line > "$location/curtag" 17 | cat "$location/curtag" 18 | if [ "$line" == "$latest" ]; then 19 | docker build --progress=plain --no-cache -t "virtual_machine:$line" "$location" 20 | else 21 | docker build --no-cache -t "virtual_machine:$line" -f "$location/Dockerfile_nopackages" "$location" 22 | fi 23 | docker system prune -f --volumes 24 | fi 25 | done <<< $missing 26 | 27 | rm "$location/curtag" 28 | 29 | docker tag virtual_machine:$(docker images | sed -n 's/virtual_machine *\(v[^ ]*\).*/\1/p' | sort --version-sort | tail -n1) virtual_machine:latest 30 | docker system prune -f --volumes 31 | docker container prune -f 32 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.17 as builder 2 | 3 | RUN apk add git gcc musl-dev 4 | 5 | WORKDIR /build 6 | 7 | RUN git clone https://github.com/nim-lang/Nim.git 8 | 9 | COPY curtag Nim 10 | 11 | RUN cd Nim && git checkout -f $(cat curtag) 12 | 13 | WORKDIR /build/Nim 14 | 15 | RUN git clone "https://github.com/nim-lang/csources_$(cat curtag | head -c 2).git" csources 16 | 17 | WORKDIR /build/Nim/csources 18 | 19 | RUN ls 20 | RUN pwd 21 | 22 | COPY curtag . 23 | 24 | RUN git checkout -f $(cat curtag) || git checkout -f $(echo $(git tag) $(cat curtag) | sed 's/ /\n/g' | sed 's/v/0./' | sort -t. -n -k1,1 -k2,2 -k3,3 -k4,4 | sed 's/0./v/' | sed -n "/$(cat curtag)/q;p" | tail -n1) 25 | 26 | RUN sh build.sh 27 | 28 | WORKDIR /build/Nim 29 | 30 | RUN bin/nim c --skipUserCfg --skipParentCfg koch 31 | 32 | RUN ./koch boot -d:release 33 | 34 | RUN mkdir /build/result 35 | 36 | RUN ./koch install /build/result 37 | 38 | RUN ./koch nimble 39 | 40 | ################################## 41 | 42 | FROM alpine:3.17 as installer 43 | 44 | RUN apk add git gcc musl-dev libressl-dev 45 | 46 | WORKDIR /installer 47 | 48 | COPY --from=builder /build/result/* ./nim/ 49 | 50 | COPY --from=builder /build/Nim/bin/nimble ./nim/bin 51 | 52 | ENV PATH=$PATH:/installer/nim/bin 53 | 54 | COPY packages.nimble ./ 55 | 56 | RUN nimble --version 57 | 58 | RUN nimble install --nimbleDir:/installer/nimble -y --depsOnly --verbose 59 | 60 | ################################## 61 | 62 | FROM alpine:3.17 as playground 63 | 64 | RUN apk add gcc g++ musl-dev 65 | 66 | RUN apk add lapack # Requirement for arraymancer 67 | 68 | RUN apk add pcre # Requirement for regex 69 | 70 | WORKDIR /playground 71 | 72 | COPY --from=builder /build/result/* ./nim/ 73 | 74 | COPY --from=installer /installer/nimble/pkgs* ./nimble 75 | 76 | ENV PATH=$PATH:/playground/nim/bin 77 | 78 | RUN mkdir /usercode && chown nobody:nobody /usercode 79 | 80 | RUN chown -R nobody:nobody /playground/nimble 81 | -------------------------------------------------------------------------------- /docker/packages.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.0.0" 4 | author = "Peter Munch-Ellingsen" 5 | description = "Pseduo-package to list dependencies to install" 6 | license = "MIT" 7 | 8 | # Dependencies 9 | 10 | requires "termstyle" 11 | requires "NimData" 12 | requires "argparse" 13 | requires "arraymancer" 14 | requires "ast_pattern_matching" 15 | requires "bigints" 16 | requires "binaryheap" 17 | requires "blscurve" 18 | requires "bncurve" 19 | requires "c2nim" 20 | requires "cascade" 21 | requires "chroma" 22 | requires "chronicles" 23 | requires "chronos" 24 | requires "cligen" 25 | requires "combparser" 26 | requires "compactdict" 27 | requires "criterion" 28 | requires "dashing" 29 | requires "docopt" 30 | requires "elvis" 31 | requires "gara" 32 | # requires "glob" # Version pinned to 0.17.x: https://github.com/citycide/glob/issues/55 33 | requires "gnuplot" 34 | requires "hts" 35 | requires "illwill" 36 | requires "inim" 37 | requires "itertools" 38 | requires "iterutils" 39 | requires "jstin" 40 | requires "karax" 41 | requires "loopfusion" 42 | requires "macroutils" 43 | requires "markdown" 44 | requires "msgpack4nim" 45 | requires "mustache" 46 | requires "neo" 47 | requires "nesm" 48 | requires "nicy" 49 | requires "nimcrypto" 50 | requires "nimes" 51 | requires "nimfp" 52 | requires "nimgen" 53 | requires "nimly" 54 | requires "nimpy" 55 | requires "nimquery" 56 | requires "nimsl" 57 | requires "nimsvg" 58 | # requires "norm" # taskRequires not available? 59 | requires "npeg" 60 | requires "optionsutils" 61 | requires "ormin" 62 | requires "parsetoml" 63 | requires "patty" 64 | requires "plotly" 65 | requires "pnm" 66 | # requires "polypbren" # threadpool type error 67 | requires "protobuf" 68 | requires "rbtree" 69 | requires "react" 70 | requires "regex" 71 | requires "result" 72 | requires "rosencrantz" 73 | requires "stint" 74 | requires "strunicode" 75 | requires "telebot" 76 | requires "tempdir" 77 | requires "tiny_sqlite" 78 | requires "timeit" 79 | requires "unicodedb" 80 | requires "unicodeplus" 81 | requires "unpack" 82 | requires "with" 83 | requires "ws" 84 | requires "yaml" 85 | requires "zero_functional" 86 | requires "https://github.com/nim-lang/fusion" 87 | -------------------------------------------------------------------------------- /src/nim_playground.nim: -------------------------------------------------------------------------------- 1 | import jester, asyncdispatch, os, osproc, strutils, json, threadpool, asyncfile, asyncnet, posix, logging, nuuid, tables, httpclient, streams, uri 2 | import ansitohtml, ansiparse, sequtils, options 3 | 4 | type 5 | Config = object 6 | tmpDir: ptr string 7 | logFile: ptr string 8 | 9 | APIToken = object 10 | gist: string 11 | 12 | ParsedRequest = object 13 | code: string 14 | compilationTarget: string 15 | 16 | RequestConfig = object 17 | tmpDir: string 18 | 19 | OutputFormat = enum 20 | Raw = "raw", HTML = "html", Ansi = "ansi", AnsiParsed = "ansiparsed" 21 | 22 | const configFileName = "conf.json" 23 | 24 | onSignal(SIGABRT): 25 | ## Handle SIGABRT from systemd 26 | # Lines printed to stdout will be received by systemd and logged 27 | # Start with "" from 0 to 7 28 | echo "<2>Received SIGABRT" 29 | quit(1) 30 | 31 | var conf = createShared(Config) 32 | let parsedConfig = parseFile(configFileName) 33 | var 34 | tmpDir = parsedConfig["tmp_dir"].str 35 | logFile = parsedConfig["log_fname"].str 36 | 37 | discard existsOrCreateDir(tmpDir) 38 | 39 | conf.tmpDir = tmpDir.addr 40 | conf.logFile = logFile.addr 41 | 42 | let fl = newFileLogger(conf.logFile[], fmtStr = "$datetime $levelname ") 43 | fl.addHandler 44 | 45 | proc `%`(c: char): JsonNode = 46 | %($c) 47 | 48 | proc respondOnReady(fv: FlowVar[TaintedString], requestConfig: ptr RequestConfig, output: OutputFormat): Future[string] {.async.} = 49 | while true: 50 | if fv.isReady: 51 | info(^fv) 52 | let 53 | truncMsg = "\e[0m\n\nOutput truncated" 54 | errors = try: 55 | var errorsFile = openAsync("$1/errors.txt" % requestConfig.tmpDir, fmRead) 56 | try: 57 | var errors = await errorsFile.read(2048 + truncMsg.len) 58 | if errors.len > 2048: 59 | errors.setLen(2048 + truncMsg.len) 60 | errors[2048..^1] = truncMsg 61 | errors 62 | except: "Unable to read error log" 63 | finally: errorsFile.close() 64 | except: "Unable to open error log" 65 | log = try: 66 | var logFile = openAsync("$1/logfile.txt" % requestConfig.tmpDir, fmRead) 67 | try: 68 | var log = await logFile.read(2048 + truncMsg.len) 69 | if log.len > 2048: 70 | log.setLen(2048 + truncMsg.len) 71 | log[2048..^1] = truncMsg 72 | log 73 | except: "Unable to read output log" 74 | finally: logFile.close() 75 | except: "Unable to open output log" 76 | 77 | proc cleanAndColourize(x: string): string = 78 | result = x.multiReplace([("<", "<"), (">", ">"), ("\n", "
")]) 79 | while true: 80 | try: 81 | result = result.ansiToHtml({"31": "color: red", "32": "color: #66d9ef", "36": "color: #50fa7b"}.toTable) 82 | break 83 | except FinalByteError, InsufficientInputError: 84 | result.setLen(result.rfind("\e") - 1) 85 | 86 | proc clearAnsi(y: string): string = 87 | result = y 88 | var ansiData: seq[AnsiData] 89 | while true: 90 | try: 91 | ansiData = result.parseAnsi 92 | break 93 | except FinalByteError, InsufficientInputError: 94 | result.setLen(result.rfind("\e") - 1) 95 | result = ansiData 96 | .filter(proc (x: AnsiData): bool = x.kind == String) 97 | .map(proc (x: AnsiData): string = x.str) 98 | .join() 99 | 100 | var ret: JsonNode 101 | 102 | case output: 103 | of HTML: 104 | ret = %* {"compileLog": cleanAndColourize(errors), 105 | "log": cleanAndColourize(log)} 106 | of Ansi: 107 | ret = %* {"compileLog": errors, "log": log} 108 | of AnsiParsed: 109 | ret = %* {"compileLog": errors.parseAnsi, "log": log.parseAnsi} 110 | of Raw: 111 | ret = %* {"compileLog": errors.clearAnsi, "log": log.clearAnsi} 112 | 113 | discard execProcess("sudo -u nobody /bin/chmod a+w $1/*" % [requestConfig.tmpDir]) 114 | removeDir(requestConfig.tmpDir) 115 | freeShared(requestConfig) 116 | return $ret 117 | 118 | 119 | await sleepAsync(100) 120 | 121 | proc prepareAndCompile(code, compilationTarget: string, requestConfig: ptr RequestConfig, version: string): TaintedString = 122 | try: 123 | discard existsOrCreateDir(requestConfig.tmpDir) 124 | copyFileWithPermissions("./test/script.sh", "$1/script.sh" % requestConfig.tmpDir) 125 | writeFile("$1/in.nim" % requestConfig.tmpDir, code) 126 | echo execProcess("chmod a+w $1" % [requestConfig.tmpDir]) 127 | 128 | let cmd = """ 129 | ./docker_timeout.sh 15s -i -t --net=none -v "$1":/usercode --user nobody virtual_machine:$2 /usercode/script.sh in.nim $3 130 | """ % [requestConfig.tmpDir, version, compilationTarget] 131 | 132 | execProcess(cmd) 133 | except: 134 | error(getCurrentExceptionMsg()) 135 | "".TaintedString 136 | 137 | proc loadUrl(url: string): Future[string] {.async.} = 138 | let client = newAsyncHttpClient() 139 | try: 140 | client.onProgressChanged = proc (total, progress, speed: BiggestInt) {.async.} = 141 | if total > 1048576 or progress > 1048576 or (progress > 4000 and speed < 100): 142 | client.close() 143 | return await client.getContent(url) 144 | finally: 145 | client.close() 146 | 147 | proc createIx(code: string): Future[Option[string]] {.async.} = 148 | var client: AsyncHttpClient 149 | try: 150 | client = newAsyncHttpClient() 151 | var data = newMultipartData() 152 | data["f:1"] = code 153 | some((await client.postContent("http://ix.io", multipart = data))[0..^2] & "/nim") 154 | except: 155 | none(string) 156 | finally: 157 | client.close() 158 | 159 | proc loadIx(ixid: string): Future[string] {.async.} = 160 | try: 161 | return await loadUrl("http://ix.io/$1" % ixid) 162 | except: 163 | return "Unable to load ix paste, file too large, or download is too slow" 164 | 165 | proc createPasty(code: string): Future[Option[string]] {.async.} = 166 | var client: AsyncHttpClient 167 | try: 168 | let headers = newHttpHeaders(@[("Accept", "application/json"), ("Origin", "https://play.nim-lang.org")]) 169 | client = newAsyncHttpClient(headers = headers) 170 | let 171 | response = await client.postContent("https://pasty.ee", code) 172 | parsed = parseJson(response) 173 | url = getOrDefault(parsed, "url") 174 | if url.isNil: 175 | none(string) 176 | else: 177 | some(getStr(url)) 178 | except: 179 | none(string) 180 | finally: 181 | client.close() 182 | 183 | proc loadPasty(id: string): Future[string] {.async.} = 184 | try: 185 | return await loadUrl("https://pasty.ee/$1" % id) 186 | except: 187 | return "Unable to load pasty.ee paste, file too large, or download is too slow" 188 | 189 | proc compile(code, compilationTarget: string, output: OutputFormat, requestConfig: ptr RequestConfig, version: string): Future[string] = 190 | let fv = spawn prepareAndCompile(code, compilationTarget, requestConfig, version) 191 | return respondOnReady(fv, requestConfig, output) 192 | 193 | proc isDigit(x: string): bool = x.allCharsInSet(Digits) 194 | 195 | proc isVersion(ver: string): bool = 196 | let parts = ver.split('.') 197 | if parts.len != 3: 198 | return false 199 | if parts[0][0] != 'v': 200 | return false 201 | else: 202 | if not parts[0][1..^1].isDigit or not parts[1].isDigit or not parts[2].isDigit: 203 | return false 204 | return ver in execProcess("docker images | sed -n 's/virtual_machine *\\(v[^ ]*\\).*/\\1/p' | sort --version-sort").split("\n")[0..^2] 205 | 206 | routes: 207 | get "/index.html#@extra": 208 | redirect "/#" & @"extra" 209 | 210 | get "/index.html": 211 | redirect "/" 212 | 213 | get "/": 214 | resp readFile("public/index.html") 215 | 216 | get "/versions": 217 | resp $(%*{"versions": execProcess("docker images | sed -n 's/virtual_machine *\\(v[^ ]*\\).*/\\1/p' | sort --version-sort").split("\n")[0..^2]}) 218 | 219 | get "/tour/@url": 220 | resp(Http200, [("Content-Type","text/plain")], await loadUrl(decodeUrl(@"url"))) 221 | 222 | get "/ix/@ixid": 223 | resp(Http200, await loadIx(@"ixid")) 224 | 225 | post "/ix": 226 | var parsedRequest: ParsedRequest 227 | let parsed = parseJson(request.body) 228 | if getOrDefault(parsed, "code").isNil: 229 | resp(Http400) 230 | parsedRequest = to(parsed, ParsedRequest) 231 | 232 | let ixUrl = await createIx(parsedRequest.code) 233 | 234 | if ixUrl.isSome: 235 | resp(Http200, @[("Access-Control-Allow-Origin", "*"), ("Access-Control-Allow-Methods", "POST")], ixUrl.get) 236 | else: 237 | resp(Http500, "Something went wrong while uploading, please try again") 238 | 239 | get "/pasty/@pastyid": 240 | resp(Http200, await loadPasty(@"pastyid")) 241 | 242 | post "/pasty": 243 | var parsedRequest: ParsedRequest 244 | let parsed = parseJson(request.body) 245 | if getOrDefault(parsed, "code").isNil: 246 | resp(Http400) 247 | parsedRequest = to(parsed, ParsedRequest) 248 | 249 | let pastyUrl = await createPasty(parsedRequest.code) 250 | 251 | if pastyUrl.isSome: 252 | resp(Http200, @[("Access-Control-Allow-Origin", "*"), ("Access-Control-Allow-Methods", "POST")], pastyUrl.get) 253 | else: 254 | resp(Http500, "Something went wrong while uploading, please try again") 255 | 256 | post "/compile": 257 | var parsedRequest: ParsedRequest 258 | 259 | var 260 | outputFormat = Raw 261 | version = "latest" 262 | if request.params.len > 0: 263 | if request.params.hasKey("code"): 264 | parsedRequest.code = request.params["code"] 265 | if request.params.hasKey("compilationTarget"): 266 | parsedRequest.compilationTarget = request.params["compilationTarget"] 267 | if request.params.hasKey("outputFormat"): 268 | try: 269 | outputFormat = parseEnum[OutputFormat](request.params["outputFormat"].toLowerAscii) 270 | except: 271 | resp(Http400) 272 | if request.params.hasKey("version"): 273 | version = request.params["version"] 274 | else: 275 | let parsed = parseJson(request.body) 276 | if getOrDefault(parsed, "code").isNil: 277 | resp(Http400, "{\"error\":\"No code\"}") 278 | if getOrDefault(parsed, "compilationTarget").isNil: 279 | resp(Http400, "{\"error\":\"No compilation target\"}") 280 | parsedRequest = to(parsed, ParsedRequest) 281 | if parsed.hasKey("outputFormat"): 282 | try: 283 | outputFormat = parseEnum[OutputFormat](parsed["outputFormat"].str.toLowerAscii) 284 | except: 285 | resp(Http400, "{\"error\":\"Invalid output format\"}") 286 | if parsed.hasKey("version"): 287 | version = parsed["version"].str 288 | 289 | if version != "latest" and not version.isVersion: 290 | resp(Http400, "{\"error\":\"Unknown version\"}") 291 | 292 | if parsedRequest.compilationTarget notin ["c", "cpp"]: 293 | resp(Http400, "{\"error\":\"Unknown compilation target\"}") 294 | 295 | let requestConfig = createShared(RequestConfig) 296 | requestConfig.tmpDir = conf.tmpDir[] & "/" & generateUUID() 297 | let compileResult = await compile(parsedRequest.code, parsedRequest.compilationTarget, outputFormat, requestConfig, version) 298 | 299 | resp(Http200, [("Access-Control-Allow-Origin", "*"), ("Access-Control-Allow-Methods", "POST")], compileResult) 300 | 301 | 302 | info "Starting!" 303 | runForever() 304 | freeShared(conf) 305 | --------------------------------------------------------------------------------