UNPKG

curlconverter

Version:

convert curl commands to Python, JavaScript, Go, PHP and more

294 lines (262 loc) 9.03 kB
import { Word, eq } from "../shell/Word.js"; import { parse, getFirst, COMMON_SUPPORTED_ARGS } from "../parse.js"; import type { Request, Warnings } from "../parse.js"; import { parseQueryString } from "../Query.js"; import { esc as jsesc } from "./javascript/javascript.js"; export const supportedArgs = new Set([ ...COMMON_SUPPORTED_ARGS, "compressed", "form", "form-string", "no-compressed", ]); function escape(value: string, quote: '"' | "'"): string { // Escape Dart's $string interpolation syntax // TODO: does Dart have the same escape sequences as JS? return jsesc(value, quote).replace(/\$/g, "\\$"); } function reprStr(value: string): string { const quote = value.includes("'") && !value.includes('"') ? '"' : "'"; return quote + escape(value, quote) + quote; } function repr(value: Word, imports: Set<string>): string { const ret: string[] = []; for (const t of value.tokens) { if (typeof t === "string") { ret.push(reprStr(t)); } else if (t.type === "variable") { ret.push("(Platform.environment[" + reprStr(t.value) + "] ?? '')"); imports.add("dart:io"); } else { ret.push( "(await Process.run(" + reprStr(t.value) + ", runInShell: true)).stdout", ); imports.add("dart:io"); } } return ret.join(" + "); } function reprWords(value: Word | Word[], imports: Set<string>): string { if (value instanceof Word) return repr(value, imports); const ret: string[] = []; for (const v of value) { ret.push(repr(v, imports)); } return "[" + ret.join(", ") + "]"; } export function _toDart(requests: Request[], warnings: Warnings = []): string { const request = getFirst(requests, warnings); const imports = new Set<string>(); if (request.urls[0].auth || request.isDataBinary) imports.add("dart:convert"); let s = "void main() async {\n"; if (request.urls[0].auth) { const [uname, pword] = request.urls[0].auth; s += " final uname = " + repr(uname, imports) + ";\n" + " final pword = " + repr(pword, imports) + ";\n" + " final authn = 'Basic ${base64Encode(utf8.encode('$uname:$pword'))}';\n" + "\n"; } const methods = ["HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"]; const rawRequestObj = request.multipartUploads || !methods.includes(request.urls[0].method.toString()); const hasHeaders = request.headers.length || request.compressed || request.isDataBinary || request.urls[0].method.toLowerCase().toString() === "put"; if (hasHeaders && !rawRequestObj) { s += " final headers = {\n"; for (const [hname, hval] of request.headers) { s += " " + repr(hname, imports) + ": " + repr(hval ?? new Word(), imports) + ",\n"; } if (request.urls[0].auth) s += " 'Authorization': authn,\n"; // TODO: headers might already have Accept-Encoding if (request.compressed) s += " 'Accept-Encoding': 'gzip',\n"; s += " };\n"; s += "\n"; } if (request.urls[0].queryDict) { s += " final params = {\n"; for (const [paramName, rawValue] of request.urls[0].queryDict) { s += " " + repr(paramName, imports) + ": " + reprWords(rawValue, imports) + ",\n"; } s += " };\n"; s += "\n"; } const hasData = request.data && !request.multipartUploads; if (hasData && request.data) { const [parsedQuery] = parseQueryString(request.data); if (parsedQuery && parsedQuery.length) { s += " final data = {\n"; for (const param of parsedQuery) { const [key, val] = param; s += " " + repr(key, imports) + ": " + repr(val, imports) + ",\n"; } s += " };\n"; s += "\n"; } else { s += ` final data = ${repr(request.data, imports)};\n\n`; } } if (request.urls[0].queryDict) { const urlString = repr(request.urls[0].urlWithoutQueryList, imports); s += " final url = Uri.parse(" + urlString + ")\n" + " .replace(queryParameters: params);\n"; } else { s += " final url = Uri.parse(" + repr(request.urls[0].url, imports) + ");\n"; } s += "\n"; if (rawRequestObj) { let multipart = "http."; if (request.multipartUploads) { multipart += "MultipartRequest"; } else { multipart += "Request"; } multipart += "(" + repr(request.urls[0].method, imports) + ", url)"; for (const m of request.multipartUploads || []) { // MultipartRequest syntax looks like this: // ..fields['user'] = 'nweiz@google.com' // or // ..files.add(await http.MultipartFile.fromPath( // 'package', 'build/package.tar.gz', // contentType: MediaType('application', 'x-tar'))) const name = repr(m.name, imports); // TODO: what if name is empty string? const sentFilename = "filename" in m && m.filename; if ("contentFile" in m) { multipart += "\n ..files.add(await http.MultipartFile."; if (eq(m.contentFile, "-")) { if (request.stdinFile) { multipart += "fromPath(\n"; multipart += " " + name + ", " + repr(request.stdinFile, imports); if (sentFilename && request.stdinFile !== sentFilename) { multipart += ",\n"; multipart += " filename: " + repr(sentFilename, imports); } multipart += "))"; } else if (request.stdin) { multipart += "fromString(\n"; multipart += " " + name + ", " + repr(request.stdin, imports); if (sentFilename) { multipart += ",\n"; multipart += " filename: " + repr(sentFilename, imports); } multipart += "))"; } else { multipart += "fromString(\n"; // TODO: read the entire thing, not one line. multipart += " " + name + ", stdin.readLineSync(encoding: utf8)"; if (sentFilename) { multipart += ",\n"; multipart += " filename: " + repr(sentFilename, imports); } multipart += "))"; imports.add("dart:io"); imports.add("dart:convert"); } } else { multipart += "fromPath(\n"; multipart += " " + name + ", " + repr(m.contentFile, imports); if (sentFilename && m.contentFile !== sentFilename) { multipart += ",\n"; multipart += " filename: " + repr(sentFilename, imports); } multipart += "))"; } } else { multipart += "\n ..fields[" + name + "] = " + repr(m.content, imports); } } multipart += ";\n\n"; if (hasHeaders || request.urls[0].auth) { s += " final req = " + multipart; if (request.headers.length) { for (const [hname, hval] of request.headers) { s += " req.headers[" + repr(hname, imports) + "] = " + repr(hval || new Word(), imports) + ";\n"; } s += "\n"; } if (request.urls[0].auth) { s += " req.headers['Authorization'] = authn;\n"; s += "\n"; } s += " final stream = await req.send();\n"; s += " final res = await http.Response.fromStream(stream);\n"; } else { s += " final req = " + multipart; s += " final stream = await req.send();\n"; s += " final res = await http.Response.fromStream(stream);\n"; } s += " final status = res.statusCode;\n" + " if (status != 200) throw Exception('http.send" + " error: statusCode= $status');\n" + "\n" + " print(res.body);\n" + "}"; } else { s += " final res = await http." + request.urls[0].method.toLowerCase().toString() + "(url"; if (hasHeaders) s += ", headers: headers"; else if (request.urls[0].auth) s += ", headers: {'Authorization': authn}"; if (hasData) s += ", body: data"; s += ");\n"; s += " final status = res.statusCode;\n" + " if (status != 200) throw Exception('http." + request.urls[0].method.toLowerCase().toString() + " error: statusCode= $status');\n" + "\n" + " print(res.body);\n" + "}"; } let importString = ""; for (const imp of Array.from(imports).sort()) { importString += "import '" + imp + "';\n"; } importString += "import 'package:http/http.dart' as http;\n"; return importString + "\n" + s + "\n"; } export function toDartWarn( curlCommand: string | string[], warnings: Warnings = [], ): [string, Warnings] { const requests = parse(curlCommand, supportedArgs, warnings); const dart = _toDart(requests, warnings); return [dart, warnings]; } export function toDart(curlCommand: string | string[]): string { return toDartWarn(curlCommand)[0]; }